Skip to content

Commit 326f978

Browse files
authored
Merge pull request #294 from nikz/add-links-option-to-relationship-serializer
Adds a :links option to the relationship macros
2 parents 9d19f32 + 1ad20d6 commit 326f978

5 files changed

Lines changed: 122 additions & 9 deletions

File tree

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,15 +245,38 @@ class MovieSerializer
245245
end
246246
```
247247

248-
### Meta Per Resource
249-
250-
For every resource in the collection, you can include a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship.
248+
#### Links on a Relationship
251249

250+
You can specify [relationship links](http://jsonapi.org/format/#document-resource-object-relationships) by using the `links:` option on the serializer. Relationship links in JSON API are useful if you want to load a parent document and then load associated documents later due to size constraints (see [related resource links](http://jsonapi.org/format/#document-resource-object-related-resource-links))
252251

253252
```ruby
254253
class MovieSerializer
255254
include FastJsonapi::ObjectSerializer
256255

256+
has_many :actors, links: {
257+
self: :url,
258+
related: -> (object) {
259+
"https://movies.com/#{object.id}/actors"
260+
}
261+
}
262+
end
263+
```
264+
265+
This will create a `self` reference for the relationship, and a `related` link for loading the actors relationship later. NB: This will not automatically disable loading the data in the relationship, you'll need to do that using the `lazy_load_data` option:
266+
267+
```ruby
268+
has_many :actors, lazy_load_data: true, links: {
269+
self: :url,
270+
related: -> (object) {
271+
"https://movies.com/#{object.id}/actors"
272+
}
273+
}
274+
```
275+
276+
### Meta Per Resource
277+
278+
For every resource in the collection, you can include a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship.
279+
```ruby
257280
meta do |movie|
258281
{
259282
years_since_release: Date.current.year - movie.year

lib/fast_jsonapi/object_serializer.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ def create_relationship(base_key, relationship_type, options, block)
257257
cached: options[:cached],
258258
polymorphic: fetch_polymorphic_option(options),
259259
conditional_proc: options[:if],
260-
transform_method: @transform_method
260+
transform_method: @transform_method,
261+
links: options[:links],
262+
lazy_load_data: options[:lazy_load_data]
261263
)
262264
end
263265

lib/fast_jsonapi/relationship.rb

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module FastJsonapi
22
class Relationship
3-
attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method
3+
attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :lazy_load_data
44

55
def initialize(
66
key:,
@@ -14,7 +14,9 @@ def initialize(
1414
cached: false,
1515
polymorphic:,
1616
conditional_proc:,
17-
transform_method:
17+
transform_method:,
18+
links:,
19+
lazy_load_data: false
1820
)
1921
@key = key
2022
@name = name
@@ -28,14 +30,19 @@ def initialize(
2830
@polymorphic = polymorphic
2931
@conditional_proc = conditional_proc
3032
@transform_method = transform_method
33+
@links = links || {}
34+
@lazy_load_data = lazy_load_data
3135
end
3236

3337
def serialize(record, serialization_params, output_hash)
3438
if include_relationship?(record, serialization_params)
3539
empty_case = relationship_type == :has_many ? [] : nil
36-
output_hash[key] = {
37-
data: ids_hash_from_record_and_relationship(record, serialization_params) || empty_case
38-
}
40+
41+
output_hash[key] = {}
42+
unless lazy_load_data
43+
output_hash[key][:data] = ids_hash_from_record_and_relationship(record, serialization_params) || empty_case
44+
end
45+
add_links_hash(record, serialization_params, output_hash) if links.present?
3946
end
4047
end
4148

@@ -96,6 +103,12 @@ def fetch_id(record, params)
96103
record.public_send(id_method_name)
97104
end
98105

106+
def add_links_hash(record, params, output_hash)
107+
output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash|
108+
Link.new(key: key, method: method).serialize(record, params, hash)\
109+
end
110+
end
111+
99112
def run_key_transform(input)
100113
if self.transform_method.present?
101114
input.to_s.send(*self.transform_method).to_sym
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
require 'spec_helper'
2+
3+
describe FastJsonapi::ObjectSerializer do
4+
include_context 'movie class'
5+
6+
context "params option" do
7+
let(:hash) { serializer.serializable_hash }
8+
9+
context "generating links for a serializer relationship" do
10+
let(:params) { { } }
11+
let(:options_with_params) { { params: params } }
12+
let(:relationship_url) { "http://movies.com/#{movie.id}/relationships/actors" }
13+
let(:related_url) { "http://movies.com/movies/#{movie.name.parameterize}/actors/" }
14+
15+
before(:context) do
16+
class MovieSerializer
17+
has_many :actors, lazy_load_data: false, links: {
18+
self: :actors_relationship_url,
19+
related: -> (object, params = {}) {
20+
"#{params.has_key?(:secure) ? "https" : "http"}://movies.com/movies/#{object.name.parameterize}/actors/"
21+
}
22+
}
23+
end
24+
end
25+
26+
context "with a single record" do
27+
let(:serializer) { MovieSerializer.new(movie, options_with_params) }
28+
let(:links) { hash[:data][:relationships][:actors][:links] }
29+
30+
it "handles relationship links that call a method" do
31+
expect(links).to be_present
32+
expect(links[:self]).to eq(relationship_url)
33+
end
34+
35+
it "handles relationship links that call a proc" do
36+
expect(links).to be_present
37+
expect(links[:related]).to eq(related_url)
38+
end
39+
40+
context "with serializer params" do
41+
let(:params) { { secure: true } }
42+
let(:secure_related_url) { related_url.gsub("http", "https") }
43+
44+
it "passes the params to the link serializer correctly" do
45+
expect(links).to be_present
46+
expect(links[:related]).to eq(secure_related_url)
47+
end
48+
end
49+
end
50+
51+
end
52+
53+
context "lazy loading relationship data" do
54+
before(:context) do
55+
class LazyLoadingMovieSerializer < MovieSerializer
56+
has_many :actors, lazy_load_data: true, links: {
57+
related: :actors_relationship_url
58+
}
59+
end
60+
end
61+
62+
let(:serializer) { LazyLoadingMovieSerializer.new(movie) }
63+
let(:actor_hash) { hash[:data][:relationships][:actors] }
64+
65+
it "does not include the :data key" do
66+
expect(actor_hash).to be_present
67+
expect(actor_hash).not_to have_key(:data)
68+
end
69+
end
70+
end
71+
end

spec/shared/contexts/movie_context.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ def local_name(locale = :english)
6161
def url
6262
"http://movies.com/#{id}"
6363
end
64+
65+
def actors_relationship_url
66+
"#{url}/relationships/actors"
67+
end
6468
end
6569

6670
class Actor

0 commit comments

Comments
 (0)