Skip to content

Commit 220f058

Browse files
committed
Add support to top-level links objects
it's a generic object which can be used to declare a top-level links object as specified on jsonapi http://jsonapi.org/format/#document-top-level
1 parent 03ddeee commit 220f058

File tree

7 files changed

+160
-3
lines changed

7 files changed

+160
-3
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ render json: @post, meta: { total: 10 }, meta_key: "custom_meta"
138138

139139
`meta` will only be included in your response if you are using an Adapter that supports `root`, as JsonAPI and Json adapters, the default adapter (FlattenJson) doesn't have `root`.
140140

141+
### Links
142+
143+
A top-level `links` object may be specified in the `render` call:
144+
145+
```ruby
146+
render json: @post, links: { self: "/posts/1" }
147+
```
148+
141149
### Overriding association methods
142150

143151
If you want to override any association, you can use:

lib/active_model/serializer.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,15 @@ def self.root_name
105105
name.demodulize.underscore.sub(/_serializer$/, '') if name
106106
end
107107

108-
attr_accessor :object, :root, :meta, :meta_key, :scope
108+
attr_accessor :object, :root, :meta, :meta_key, :links, :scope
109109

110110
def initialize(object, options = {})
111111
@object = object
112112
@options = options
113113
@root = options[:root]
114114
@meta = options[:meta]
115115
@meta_key = options[:meta_key]
116+
@links = options[:links]
116117
@scope = options[:scope]
117118

118119
scope_name = options[:scope_name]

lib/active_model/serializer/adapter.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ def serializable_hash(options = nil)
2222

2323
def as_json(options = nil)
2424
hash = serializable_hash(options)
25-
include_meta(hash) unless self.class == FlattenJson
25+
unless self.class == FlattenJson
26+
include_meta(hash)
27+
include_links(hash)
28+
end
2629
hash
2730
end
2831

@@ -86,6 +89,10 @@ def meta_key
8689
serializer.meta_key || "meta"
8790
end
8891

92+
def links
93+
serializer.links if serializer.respond_to?(:links)
94+
end
95+
8996
def root
9097
serializer.json_key.to_sym if serializer.json_key
9198
end
@@ -94,6 +101,11 @@ def include_meta(json)
94101
json[meta_key] = meta if meta
95102
json
96103
end
104+
105+
def include_links(json)
106+
json["links"] = links if links
107+
json
108+
end
97109
end
98110
end
99111
end

lib/active_model/serializer/array_serializer.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class ArraySerializer
55
include Enumerable
66
delegate :each, to: :@objects
77

8-
attr_reader :root, :meta, :meta_key
8+
attr_reader :root, :meta, :meta_key, :links
99

1010
def initialize(objects, options = {})
1111
@root = options[:root]
@@ -24,6 +24,7 @@ def initialize(objects, options = {})
2424
end
2525
@meta = options[:meta]
2626
@meta_key = options[:meta_key]
27+
@links = options[:links]
2728
end
2829

2930
def json_key

test/action_controller/serialization_test.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ def render_array_using_implicit_serializer_and_meta
5858
end
5959
end
6060

61+
def render_array_using_implicit_serializer_and_links
62+
with_adapter ActiveModel::Serializer::Adapter::JsonApi do
63+
64+
@profiles = [
65+
Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
66+
]
67+
68+
render json: @profiles, links: { self: "/profiles/1" }
69+
end
70+
end
71+
6172
def render_object_with_cache_enabled
6273
@comment = Comment.new({ id: 1, body: 'ZOMG A COMMENT' })
6374
@author = Author.new(id: 1, name: 'Joao Moura.')
@@ -266,6 +277,29 @@ def test_render_array_using_implicit_serializer_and_meta
266277
assert_equal expected.to_json, @response.body
267278
end
268279

280+
def test_render_array_using_implicit_serializer_and_links
281+
get :render_array_using_implicit_serializer_and_links
282+
283+
expected = {
284+
data: [
285+
{
286+
id: assigns(:profiles).first.id.to_s,
287+
type: "profiles",
288+
attributes: {
289+
name: "Name 1",
290+
description: "Description 1"
291+
}
292+
}
293+
],
294+
links: {
295+
self: "/profiles/1"
296+
}
297+
}
298+
299+
assert_equal 'application/json', @response.content_type
300+
assert_equal expected.to_json, @response.body
301+
end
302+
269303
def test_render_with_cache_enable
270304
expected = {
271305
id: 1,

test/array_serializer_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ def test_meta_and_meta_key_attr_readers
4545
assert_equal @serializer.meta_key, "the meta key"
4646
end
4747

48+
def test_links_attr_reader
49+
@serializer = ArraySerializer.new([@comment, @post], links: {"self": "/array/1"})
50+
51+
assert_equal @serializer.links, {"self": "/array/1"}
52+
end
53+
4854
def test_root_default
4955
@serializer = ArraySerializer.new([@comment, @post])
5056
assert_equal @serializer.root, nil

test/serializers/links_test.rb

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
require 'test_helper'
2+
3+
module ActiveModel
4+
class Serializer
5+
class LinksTest < Minitest::Test
6+
def setup
7+
ActionController::Base.cache_store.clear
8+
@blog = Blog.new(id: 1,
9+
name: 'AMS Hints',
10+
writer: Author.new(id: 2, name: "Steve"),
11+
articles: [Post.new(id: 3, title: "AMS")])
12+
end
13+
14+
def test_links_is_present_with_root
15+
serializer = AlternateBlogSerializer.new(@blog, links: {"self": "/blogs/1"})
16+
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
17+
expected = {
18+
blog: {
19+
id: 1,
20+
title: "AMS Hints"
21+
},
22+
"links" => {
23+
self: "/blogs/1"
24+
}
25+
}
26+
assert_equal expected, adapter.as_json
27+
end
28+
29+
def test_links_is_not_included_when_root_is_missing
30+
# load_adapter uses FlattenJson Adapter
31+
adapter = load_adapter(links: {"self": "/blogs/1"})
32+
expected = {
33+
id: 1,
34+
title: "AMS Hints"
35+
}
36+
assert_equal expected, adapter.as_json
37+
end
38+
39+
def test_links_is_not_present_on_arrays_without_root
40+
serializer = ArraySerializer.new([@blog], links: {"self": "/blogs/1"})
41+
# FlattenJSON doesn't have support to root
42+
adapter = ActiveModel::Serializer::Adapter::FlattenJson.new(serializer)
43+
expected = [{
44+
id: 1,
45+
name: "AMS Hints",
46+
writer: {
47+
id: 2,
48+
name: "Steve"
49+
},
50+
articles: [{
51+
id: 3,
52+
title: "AMS",
53+
body: nil
54+
}]
55+
}]
56+
assert_equal expected, adapter.as_json
57+
end
58+
59+
def test_links_is_present_on_arrays_with_root
60+
serializer = ArraySerializer.new([@blog], links: {"self": "/blogs/1"})
61+
# JSON adapter adds root by default
62+
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
63+
expected = {
64+
blogs: [{
65+
id: 1,
66+
name: "AMS Hints",
67+
writer: {
68+
id: 2,
69+
name: "Steve"
70+
},
71+
articles: [{
72+
id: 3,
73+
title: "AMS",
74+
body: nil
75+
}]
76+
}],
77+
"links" => {
78+
self: "/blogs/1"
79+
}
80+
}
81+
assert_equal expected, adapter.as_json
82+
end
83+
84+
private
85+
86+
def load_adapter(options)
87+
adapter_opts, serializer_opts =
88+
options.partition { |k, _| ActionController::Serialization::ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
89+
90+
serializer = AlternateBlogSerializer.new(@blog, serializer_opts)
91+
ActiveModel::Serializer::Adapter::FlattenJson.new(serializer, adapter_opts)
92+
end
93+
end
94+
end
95+
end

0 commit comments

Comments
 (0)