Skip to content

Commit 4f4e87b

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 e1c25e8 commit 4f4e87b

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
@@ -150,14 +150,15 @@ def self.root_name
150150
name.demodulize.underscore.sub(/_serializer$/, '') if name
151151
end
152152

153-
attr_accessor :object, :root, :meta, :meta_key, :scope
153+
attr_accessor :object, :root, :meta, :meta_key, :links, :scope
154154

155155
def initialize(object, options = {})
156156
@object = object
157157
@options = options
158158
@root = options[:root]
159159
@meta = options[:meta]
160160
@meta_key = options[:meta_key]
161+
@links = options[:links]
161162
@scope = options[:scope]
162163

163164
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
@@ -57,6 +57,17 @@ def render_array_using_implicit_serializer_and_meta
5757
end
5858
end
5959

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

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