Skip to content

Commit 7697d9f

Browse files
committed
Refactor: introduce lazy association
1 parent 34d55e4 commit 7697d9f

File tree

7 files changed

+99
-50
lines changed

7 files changed

+99
-50
lines changed

lib/active_model/serializer/association.rb

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'active_model/serializer/lazy_association'
2+
13
module ActiveModel
24
class Serializer
35
# This class holds all information about serializer's association.
@@ -10,14 +12,22 @@ class Serializer
1012
# Association.new(:comments, { serializer: CommentSummarySerializer })
1113
#
1214
class Association < Field
15+
attr_reader :lazy_association
16+
delegate :include_data?, :virtual_value, to: :lazy_association
17+
18+
def initialize(*)
19+
super
20+
@lazy_association = LazyAssociation.new(name, options, block)
21+
end
22+
1323
# @return [Symbol]
1424
def key
1525
options.fetch(:key, name)
1626
end
1727

18-
# @return [ActiveModel::Serializer, nil]
19-
def serializer
20-
options[:serializer]
28+
# @return [True,False]
29+
def key?
30+
options.key?(:key)
2131
end
2232

2333
# @return [Hash]
@@ -30,21 +40,30 @@ def meta
3040
options[:meta]
3141
end
3242

43+
def polymorphic?
44+
true == options[:polymorphic]
45+
end
46+
3347
# @api private
3448
def serializable_hash(adapter_options, adapter_instance)
35-
return options[:virtual_value] if options[:virtual_value]
36-
object = serializer && serializer.object
37-
return unless object
49+
association_serializer = lazy_association.serializer
50+
return virtual_value if virtual_value
51+
association_object = association_serializer && association_serializer.object
52+
return unless association_object
3853

39-
serialization = serializer.serializable_hash(adapter_options, {}, adapter_instance)
54+
serialization = association_serializer.serializable_hash(adapter_options, {}, adapter_instance)
4055

41-
if options[:polymorphic] && serialization
42-
polymorphic_type = object.class.name.underscore
56+
if polymorphic? && serialization
57+
polymorphic_type = association_object.class.name.underscore
4358
serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization }
4459
end
4560

4661
serialization
4762
end
63+
64+
private
65+
66+
delegate :reflection, to: :lazy_association
4867
end
4968
end
5069
end

lib/active_model/serializer/concerns/caching.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,13 @@ def object_cache_keys(collection_serializer, adapter_instance, include_directive
193193
cache_keys << object_cache_key(serializer, adapter_instance)
194194

195195
serializer.associations(include_directive).each do |association|
196-
if association.serializer.respond_to?(:each)
197-
association.serializer.each do |sub_serializer|
196+
association_serializer = association.lazy_association.serializer
197+
if association_serializer.respond_to?(:each)
198+
association_serializer.each do |sub_serializer|
198199
cache_keys << object_cache_key(sub_serializer, adapter_instance)
199200
end
200201
else
201-
cache_keys << object_cache_key(association.serializer, adapter_instance)
202+
cache_keys << object_cache_key(association_serializer, adapter_instance)
202203
end
203204
end
204205
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module ActiveModel
2+
class Serializer
3+
class LazyAssociation < Field
4+
5+
def serializer
6+
options[:serializer]
7+
end
8+
9+
def include_data?
10+
options[:include_data]
11+
end
12+
13+
def virtual_value
14+
options[:virtual_value]
15+
end
16+
17+
def reflection
18+
options[:reflection]
19+
end
20+
end
21+
end
22+
end

lib/active_model/serializer/reflection.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,11 @@ def build_association(parent_serializer, parent_serializer_options, include_slic
170170
end
171171

172172
association_block = nil
173-
Association.new(name, reflection_options, association_block)
173+
reflection_options[:reflection] = self
174+
reflection_options[:parent_serializer] = parent_serializer
175+
reflection_options[:parent_serializer_options] = parent_serializer_options
176+
reflection_options[:include_slice] = include_slice
177+
Association.new(name, reflection_options, block)
174178
end
175179

176180
protected

lib/active_model_serializers/adapter/json_api.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ def process_resource(serializer, primary, include_slice = {})
257257

258258
def process_relationships(serializer, include_slice)
259259
serializer.associations(include_slice).each do |association|
260-
process_relationship(association.serializer, include_slice[association.key])
260+
process_relationship(association.lazy_association.serializer, include_slice[association.key])
261261
end
262262
end
263263

lib/active_model_serializers/adapter/json_api/relationship.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ def initialize(parent_serializer, serializable_resource_options, association)
1515
def as_json
1616
hash = {}
1717

18-
if association.options[:include_data]
19-
hash[:data] = data_for(association)
20-
end
18+
hash[:data] = data_for(association) if association.include_data?
2119

2220
links = links_for(association)
2321
hash[:links] = links if links.any?
@@ -36,10 +34,10 @@ def as_json
3634
private
3735

3836
def data_for(association)
39-
serializer = association.serializer
37+
serializer = association.lazy_association.serializer
4038
if serializer.respond_to?(:each)
4139
serializer.map { |s| ResourceIdentifier.new(s, serializable_resource_options).as_json }
42-
elsif (virtual_value = association.options[:virtual_value])
40+
elsif (virtual_value = association.virtual_value)
4341
virtual_value
4442
elsif serializer && serializer.object
4543
ResourceIdentifier.new(serializer, serializable_resource_options).as_json

test/serializers/associations_test.rb

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,17 @@ def setup
3030
def test_has_many_and_has_one
3131
@author_serializer.associations.each do |association|
3232
key = association.key
33-
serializer = association.serializer
34-
options = association.options
33+
serializer = association.lazy_association.serializer
3534

3635
case key
3736
when :posts
38-
assert_equal true, options.fetch(:include_data)
37+
assert_equal true, association.include_data?
3938
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
4039
when :bio
41-
assert_equal true, options.fetch(:include_data)
40+
assert_equal true, association.include_data?
4241
assert_nil serializer
4342
when :roles
44-
assert_equal true, options.fetch(:include_data)
43+
assert_equal true, association.include_data?
4544
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
4645
else
4746
flunk "Unknown association: #{key}"
@@ -56,12 +55,11 @@ def test_has_many_with_no_serializer
5655
end
5756
post_serializer_class.new(@post).associations.each do |association|
5857
key = association.key
59-
serializer = association.serializer
60-
options = association.options
58+
serializer = association.lazy_association.serializer
6159

6260
assert_equal :tags, key
6361
assert_nil serializer
64-
assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, options[:virtual_value].to_json
62+
assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, association.virtual_value.to_json
6563
end
6664
end
6765

@@ -70,7 +68,7 @@ def test_serializer_options_are_passed_into_associations_serializers
7068
.associations
7169
.detect { |assoc| assoc.key == :comments }
7270

73-
comment_serializer = association.serializer.first
71+
comment_serializer = association.lazy_association.serializer.first
7472
class << comment_serializer
7573
def custom_options
7674
instance_options
@@ -82,7 +80,7 @@ def custom_options
8280
def test_belongs_to
8381
@comment_serializer.associations.each do |association|
8482
key = association.key
85-
serializer = association.serializer
83+
serializer = association.lazy_association.serializer
8684

8785
case key
8886
when :post
@@ -93,7 +91,7 @@ def test_belongs_to
9391
flunk "Unknown association: #{key}"
9492
end
9593

96-
assert_equal true, association.options.fetch(:include_data)
94+
assert_equal true, association.include_data?
9795
end
9896
end
9997

@@ -203,11 +201,11 @@ def test_associations_namespaced_resources
203201
@post_serializer.associations.each do |association|
204202
case association.key
205203
when :comments
206-
assert_instance_of(ResourceNamespace::CommentSerializer, association.serializer.first)
204+
assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first)
207205
when :author
208-
assert_instance_of(ResourceNamespace::AuthorSerializer, association.serializer)
206+
assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer)
209207
when :description
210-
assert_instance_of(ResourceNamespace::DescriptionSerializer, association.serializer)
208+
assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer)
211209
else
212210
flunk "Unknown association: #{key}"
213211
end
@@ -245,11 +243,11 @@ def test_associations_namespaced_resources
245243
@post_serializer.associations.each do |association|
246244
case association.key
247245
when :comments
248-
assert_instance_of(PostSerializer::CommentSerializer, association.serializer.first)
246+
assert_instance_of(PostSerializer::CommentSerializer, association.lazy_association.serializer.first)
249247
when :author
250-
assert_instance_of(PostSerializer::AuthorSerializer, association.serializer)
248+
assert_instance_of(PostSerializer::AuthorSerializer, association.lazy_association.serializer)
251249
when :description
252-
assert_instance_of(PostSerializer::DescriptionSerializer, association.serializer)
250+
assert_instance_of(PostSerializer::DescriptionSerializer, association.lazy_association.serializer)
253251
else
254252
flunk "Unknown association: #{key}"
255253
end
@@ -260,7 +258,7 @@ def test_associations_namespaced_resources
260258
def test_conditional_associations
261259
model = Class.new(::Model) do
262260
attributes :true, :false
263-
associations :association
261+
associations :something
264262
end.new(true: true, false: false)
265263

266264
scenarios = [
@@ -284,7 +282,7 @@ def test_conditional_associations
284282

285283
scenarios.each do |s|
286284
serializer = Class.new(ActiveModel::Serializer) do
287-
belongs_to :association, s[:options]
285+
belongs_to :something, s[:options]
288286

289287
def true
290288
true
@@ -296,7 +294,7 @@ def false
296294
end
297295

298296
hash = serializable(model, serializer: serializer).serializable_hash
299-
assert_equal(s[:included], hash.key?(:association), "Error with #{s[:options]}")
297+
assert_equal(s[:included], hash.key?(:something), "Error with #{s[:options]}")
300298
end
301299
end
302300

@@ -341,8 +339,8 @@ def setup
341339
@author_serializer = AuthorSerializer.new(@author)
342340
@inherited_post_serializer = InheritedPostSerializer.new(@post)
343341
@inherited_author_serializer = InheritedAuthorSerializer.new(@author)
344-
@author_associations = @author_serializer.associations.to_a
345-
@inherited_author_associations = @inherited_author_serializer.associations.to_a
342+
@author_associations = @author_serializer.associations.to_a.sort_by(&:name)
343+
@inherited_author_associations = @inherited_author_serializer.associations.to_a.sort_by(&:name)
346344
@post_associations = @post_serializer.associations.to_a
347345
@inherited_post_associations = @inherited_post_serializer.associations.to_a
348346
end
@@ -361,28 +359,35 @@ def setup
361359

362360
test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do
363361
expected = [:roles, :bio].sort
364-
result = (@inherited_author_associations - @author_associations).map(&:name).sort
362+
result = (@inherited_author_associations.map(&:reflection) - @author_associations.map(&:reflection)).map(&:name)
365363
assert_equal(result, expected)
364+
assert_equal [true, false, true], @inherited_author_associations.map(&:polymorphic?)
365+
assert_equal [false, false, false], @author_associations.map(&:polymorphic?)
366366
end
367367

368368
test 'a serializer inheriting from another serializer can redefine belongs_to associations' do
369369
assert_equal [:author, :comments, :blog], @post_associations.map(&:name)
370370
assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name)
371371

372-
refute @post_associations.detect { |assoc| assoc.name == :author }.options.key?(:polymorphic)
373-
assert_equal true, @inherited_post_associations.detect { |assoc| assoc.name == :author }.options.fetch(:polymorphic)
372+
refute @post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
373+
assert @inherited_post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
374374

375-
refute @post_associations.detect { |assoc| assoc.name == :comments }.options.key?(:key)
375+
refute @post_associations.detect { |assoc| assoc.name == :comments }.key?
376376
original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments }
377-
refute original_comment_assoc.options.key?(:key)
378-
assert_equal :reviews, new_comments_assoc.options.fetch(:key)
379-
380-
assert_equal @post_associations.detect { |assoc| assoc.name == :blog }, @inherited_post_associations.detect { |assoc| assoc.name == :blog }
377+
refute original_comment_assoc.key?
378+
assert_equal :reviews, new_comments_assoc.key
379+
380+
original_blog = @post_associations.detect { |assoc| assoc.name == :blog }
381+
inherited_blog = @inherited_post_associations.detect { |assoc| assoc.name == :blog }
382+
original_parent_serializer = original_blog.lazy_association.options.delete(:parent_serializer)
383+
inherited_parent_serializer = inherited_blog.lazy_association.options.delete(:parent_serializer)
384+
assert_equal PostSerializer, original_parent_serializer.class
385+
assert_equal InheritedPostSerializer, inherited_parent_serializer.class
381386
end
382387

383388
test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do
384389
expected = [:author, :comments, :blog, :reviews].sort
385-
result = @inherited_post_serializer.associations.map { |a| a.options.fetch(:key, a.name) }.sort
390+
result = @inherited_post_serializer.associations.map(&:key).sort
386391
assert_equal(result, expected)
387392
end
388393
end

0 commit comments

Comments
 (0)