Skip to content

Commit 7d7329b

Browse files
committed
Merge pull request #1426 from brigade/default-include
Add a default_include_tree config variable to ActiveModel::Serializer
2 parents 9cffc10 + 8c18d18 commit 7d7329b

File tree

7 files changed

+127
-33
lines changed

7 files changed

+127
-33
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Breaking changes:
66

77
Features:
8+
- [#1426](https://github.com/rails-api/active_model_serializers/pull/1426) Add ActiveModelSerializers.config.default_includes (@empact)
89

910
Fixes:
1011
- [#1710](https://github.com/rails-api/active_model_serializers/pull/1710) Prevent association loading when `include_data` option

docs/general/configuration_options.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ Each adapter has a default key transform configured:
5252
`config.key_transform` is a global override of the adapter default. Adapters
5353
still prefer the render option `:key_transform` over this setting.
5454

55+
##### default_includes
56+
What relationships to serialize by default. Default: `'*'`, which includes one level of related
57+
objects. See [includes](adapters.md#included) for more info.
5558

5659
## JSON API
5760

58-
5961
##### jsonapi_resource_type
6062

6163
Sets whether the [type](http://jsonapi.org/format/#document-resource-identifier-objects)

lib/active_model/serializer/associations.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ class Serializer
1010
module Associations
1111
extend ActiveSupport::Concern
1212

13-
DEFAULT_INCLUDE_TREE = ActiveModel::Serializer::IncludeTree.from_string('*')
14-
1513
included do
1614
with_options instance_writer: false, instance_reader: true do |serializer|
1715
serializer.class_attribute :_reflections
@@ -80,10 +78,11 @@ def associate(reflection)
8078
end
8179
end
8280

83-
# @param [IncludeTree] include_tree (defaults to all associations when not provided)
81+
# @param [IncludeTree] include_tree (defaults to the
82+
# default_includes config value when not provided)
8483
# @return [Enumerator<Association>]
8584
#
86-
def associations(include_tree = DEFAULT_INCLUDE_TREE)
85+
def associations(include_tree = ActiveModelSerializers.default_include_tree)
8786
return unless object
8887

8988
Enumerator.new do |y|

lib/active_model/serializer/configuration.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def config.array_serializer
1919
collection_serializer
2020
end
2121

22+
config.default_includes = '*'
2223
config.adapter = :attributes
2324
config.jsonapi_resource_type = :plural
2425
config.jsonapi_version = '1.0'

lib/active_model_serializers.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ def self.location_of_caller
3131
[file, lineno]
3232
end
3333

34+
# Memoized default include tree
35+
# @return [ActiveModel::Serializer::IncludeTree]
36+
def self.default_include_tree
37+
@default_include_tree ||= ActiveModel::Serializer::IncludeTree
38+
.from_include_args(config.default_includes)
39+
end
40+
3441
require 'active_model/serializer/version'
3542
require 'active_model/serializer'
3643
require 'active_model/serializable_resource'

lib/active_model_serializers/adapter/attributes.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ module Adapter
33
class Attributes < Base
44
def initialize(serializer, options = {})
55
super
6-
@include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include] || '*')
76
@cached_attributes = options[:cache_attributes] || {}
7+
@include_tree =
8+
if options[:include]
9+
ActiveModel::Serializer::IncludeTree.from_include_args(options[:include])
10+
else
11+
ActiveModelSerializers.default_include_tree
12+
end
813
end
914

1015
def serializable_hash(options = nil)

test/action_controller/json/include_test.rb

Lines changed: 106 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ module ActionController
44
module Serialization
55
class Json
66
class IncludeTest < ActionController::TestCase
7+
INCLUDE_STRING = 'posts.comments'.freeze
8+
INCLUDE_HASH = { posts: :comments }.freeze
9+
DEEP_INCLUDE = 'posts.comments.author'.freeze
10+
711
class IncludeTestController < ActionController::Base
812
def setup_data
913
ActionController::Base.cache_store.clear
@@ -38,17 +42,28 @@ def render_without_include
3842

3943
def render_resource_with_include_hash
4044
setup_data
41-
render json: @author, include: { posts: :comments }, adapter: :json
45+
render json: @author, include: INCLUDE_HASH, adapter: :json
4246
end
4347

4448
def render_resource_with_include_string
4549
setup_data
46-
render json: @author, include: 'posts.comments', adapter: :json
50+
render json: @author, include: INCLUDE_STRING, adapter: :json
4751
end
4852

4953
def render_resource_with_deep_include
5054
setup_data
51-
render json: @author, include: 'posts.comments.author', adapter: :json
55+
render json: @author, include: DEEP_INCLUDE, adapter: :json
56+
end
57+
58+
def render_without_recursive_relationships
59+
# testing recursive includes ('**') can't have any cycles in the
60+
# relationships, or we enter an infinite loop.
61+
author = Author.new(id: 11, name: 'Jane Doe')
62+
post = Post.new(id: 12, title: 'Hello World', body: 'My first post')
63+
comment = Comment.new(id: 13, body: 'Commentary')
64+
author.posts = [post]
65+
post.comments = [comment]
66+
render json: author
5267
end
5368
end
5469

@@ -77,34 +92,90 @@ def test_render_without_include
7792
def test_render_resource_with_include_hash
7893
get :render_resource_with_include_hash
7994
response = JSON.parse(@response.body)
80-
expected = {
81-
'author' => {
82-
'id' => 1,
83-
'name' => 'Steve K.',
95+
96+
assert_equal(expected_include_response, response)
97+
end
98+
99+
def test_render_resource_with_include_string
100+
get :render_resource_with_include_string
101+
102+
response = JSON.parse(@response.body)
103+
104+
assert_equal(expected_include_response, response)
105+
end
106+
107+
def test_render_resource_with_deep_include
108+
get :render_resource_with_deep_include
109+
110+
response = JSON.parse(@response.body)
111+
112+
assert_equal(expected_deep_include_response, response)
113+
end
114+
115+
def test_render_with_empty_default_includes
116+
with_default_includes '' do
117+
get :render_without_include
118+
response = JSON.parse(@response.body)
119+
expected = {
120+
'author' => {
121+
'id' => 1,
122+
'name' => 'Steve K.'
123+
}
124+
}
125+
assert_equal(expected, response)
126+
end
127+
end
128+
129+
def test_render_with_recursive_default_includes
130+
with_default_includes '**' do
131+
get :render_without_recursive_relationships
132+
response = JSON.parse(@response.body)
133+
134+
expected = {
135+
'id' => 11,
136+
'name' => 'Jane Doe',
137+
'roles' => nil,
138+
'bio' => nil,
84139
'posts' => [
85140
{
86-
'id' => 42, 'title' => 'New Post', 'body' => 'Body',
141+
'id' => 12,
142+
'title' => 'Hello World',
143+
'body' => 'My first post',
87144
'comments' => [
88145
{
89-
'id' => 1, 'body' => 'ZOMG A COMMENT'
90-
},
91-
{
92-
'id' => 2, 'body' => 'ZOMG ANOTHER COMMENT'
146+
'id' => 13,
147+
'body' => 'Commentary',
148+
'post' => nil, # not set to avoid infinite recursion
149+
'author' => nil, # not set to avoid infinite recursion
93150
}
94-
]
151+
],
152+
'blog' => {
153+
'id' => 999,
154+
'name' => 'Custom blog',
155+
'writer' => nil,
156+
'articles' => nil
157+
},
158+
'author' => nil # not set to avoid infinite recursion
95159
}
96160
]
97161
}
98-
}
162+
assert_equal(expected, response)
163+
end
164+
end
99165

100-
assert_equal(expected, response)
166+
def test_render_with_includes_overrides_default_includes
167+
with_default_includes '' do
168+
get :render_resource_with_include_hash
169+
response = JSON.parse(@response.body)
170+
171+
assert_equal(expected_include_response, response)
172+
end
101173
end
102174

103-
def test_render_resource_with_include_string
104-
get :render_resource_with_include_string
175+
private
105176

106-
response = JSON.parse(@response.body)
107-
expected = {
177+
def expected_include_response
178+
{
108179
'author' => {
109180
'id' => 1,
110181
'name' => 'Steve K.',
@@ -123,15 +194,10 @@ def test_render_resource_with_include_string
123194
]
124195
}
125196
}
126-
127-
assert_equal(expected, response)
128197
end
129198

130-
def test_render_resource_with_deep_include
131-
get :render_resource_with_deep_include
132-
133-
response = JSON.parse(@response.body)
134-
expected = {
199+
def expected_deep_include_response
200+
{
135201
'author' => {
136202
'id' => 1,
137203
'name' => 'Steve K.',
@@ -158,8 +224,21 @@ def test_render_resource_with_deep_include
158224
]
159225
}
160226
}
227+
end
161228

162-
assert_equal(expected, response)
229+
def with_default_includes(include_tree)
230+
original = ActiveModelSerializers.config.default_includes
231+
ActiveModelSerializers.config.default_includes = include_tree
232+
clear_include_tree_cache
233+
yield
234+
ensure
235+
ActiveModelSerializers.config.default_includes = original
236+
clear_include_tree_cache
237+
end
238+
239+
def clear_include_tree_cache
240+
ActiveModelSerializers
241+
.instance_variable_set(:@default_include_tree, nil)
163242
end
164243
end
165244
end

0 commit comments

Comments
 (0)