Skip to content

Commit 20e394d

Browse files
bf4NullVoxPopuli
authored andcommitted
Refactor Association into Field like everything else (#1897)
* Make assocations asserts easier to understand * Refactor Association into Field like everything else * Make assocation serializer/links/meta lazier * Push association deeper into relationship * Simplify association usage in relationships * Better naming of reflection parent serializer * Easier to read association method
1 parent 7d2997b commit 20e394d

File tree

6 files changed

+94
-47
lines changed

6 files changed

+94
-47
lines changed

lib/active_model/serializer/association.rb

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,32 @@ class Serializer
33
# This class hold all information about serializer's association.
44
#
55
# @attr [Symbol] name
6-
# @attr [ActiveModel::Serializer] serializer
76
# @attr [Hash{Symbol => Object}] options
7+
# @attr [block]
88
#
99
# @example
10-
# Association.new(:comments, CommentSummarySerializer)
10+
# Association.new(:comments, { serializer: CommentSummarySerializer })
1111
#
12-
Association = Struct.new(:name, :serializer, :options, :links, :meta) do
12+
class Association < Field
1313
# @return [Symbol]
1414
def key
1515
options.fetch(:key, name)
1616
end
17+
18+
# @return [ActiveModel::Serializer, nil]
19+
def serializer
20+
options[:serializer]
21+
end
22+
23+
# @return [Hash]
24+
def links
25+
options.fetch(:links) || {}
26+
end
27+
28+
# @return [Hash, nil]
29+
def meta
30+
options[:meta]
31+
end
1732
end
1833
end
1934
end

lib/active_model/serializer/concerns/associations.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ def has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
7474
# @api private
7575
#
7676
def associate(reflection)
77-
key = reflection.options[:key]
78-
key ? self._reflections[key] = reflection : self._reflections[reflection.name] = reflection
77+
key = reflection.options[:key] || reflection.name
78+
self._reflections[key] = reflection
7979
end
8080
end
8181

lib/active_model/serializer/reflection.rb

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def value(serializer)
8888
# Build association. This method is used internally to
8989
# build serializer's association by its reflection.
9090
#
91-
# @param [Serializer] subject is a parent serializer for given association
91+
# @param [Serializer] parent_serializer for given association
9292
# @param [Hash{Symbol => Object}] parent_serializer_options
9393
#
9494
# @example
@@ -106,17 +106,19 @@ def value(serializer)
106106
#
107107
# @api private
108108
#
109-
def build_association(subject, parent_serializer_options)
110-
association_value = value(subject)
109+
def build_association(parent_serializer, parent_serializer_options)
110+
association_value = value(parent_serializer)
111111
reflection_options = options.dup
112-
serializer_class = subject.class.serializer_for(association_value, reflection_options)
112+
serializer_class = parent_serializer.class.serializer_for(association_value, reflection_options)
113113
reflection_options[:include_data] = @_include_data
114+
reflection_options[:links] = @_links
115+
reflection_options[:meta] = @_meta
114116

115117
if serializer_class
116118
begin
117-
serializer = serializer_class.new(
119+
reflection_options[:serializer] = serializer_class.new(
118120
association_value,
119-
serializer_options(subject, parent_serializer_options, reflection_options)
121+
serializer_options(parent_serializer, parent_serializer_options, reflection_options)
120122
)
121123
rescue ActiveModel::Serializer::CollectionSerializer::NoSerializerError
122124
reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
@@ -125,7 +127,8 @@ def build_association(subject, parent_serializer_options)
125127
reflection_options[:virtual_value] = association_value
126128
end
127129

128-
Association.new(name, serializer, reflection_options, @_links, @_meta)
130+
block = nil
131+
Association.new(name, reflection_options, block)
129132
end
130133

131134
protected
@@ -134,12 +137,12 @@ def build_association(subject, parent_serializer_options)
134137

135138
private
136139

137-
def serializer_options(subject, parent_serializer_options, reflection_options)
140+
def serializer_options(parent_serializer, parent_serializer_options, reflection_options)
138141
serializer = reflection_options.fetch(:serializer, nil)
139142

140143
serializer_options = parent_serializer_options.except(:serializer)
141144
serializer_options[:serializer] = serializer if serializer
142-
serializer_options[:serializer_context_class] = subject.class
145+
serializer_options[:serializer_context_class] = parent_serializer.class
143146
serializer_options
144147
end
145148
end

lib/active_model_serializers/adapter/json_api/relationship.rb

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,55 @@ class Relationship
77
# {http://jsonapi.org/format/#document-resource-object-linkage Document Resource Relationship Linkage}
88
# {http://jsonapi.org/format/#document-meta Document Meta}
99
def initialize(parent_serializer, serializable_resource_options, association)
10-
serializer = association.serializer
11-
options = association.options
12-
links = association.links
13-
meta = association.meta
14-
@object = parent_serializer.object
15-
@scope = parent_serializer.scope
16-
@association_options = options || {}
10+
@parent_serializer = parent_serializer
11+
@association = association
1712
@serializable_resource_options = serializable_resource_options
18-
@data = data_for(serializer)
19-
@links = (links || {}).each_with_object({}) do |(key, value), hash|
20-
result = Link.new(parent_serializer, value).as_json
21-
hash[key] = result if result
22-
end
23-
@meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
2413
end
2514

2615
def as_json
2716
hash = {}
28-
hash[:data] = data if association_options[:include_data]
29-
links = self.links
17+
18+
if association.options[:include_data]
19+
hash[:data] = data_for(association)
20+
end
21+
22+
links = links_for(association)
3023
hash[:links] = links if links.any?
31-
meta = self.meta
24+
25+
meta = meta_for(association)
3226
hash[:meta] = meta if meta
3327

3428
hash
3529
end
3630

3731
protected
3832

39-
attr_reader :object, :scope, :data, :serializable_resource_options,
40-
:association_options, :links, :meta
33+
attr_reader :parent_serializer, :serializable_resource_options, :association
4134

4235
private
4336

44-
def data_for(serializer)
37+
def data_for(association)
38+
serializer = association.serializer
4539
if serializer.respond_to?(:each)
4640
serializer.map { |s| ResourceIdentifier.new(s, serializable_resource_options).as_json }
47-
elsif association_options[:virtual_value]
48-
association_options[:virtual_value]
41+
elsif (virtual_value = association.options[:virtual_value])
42+
virtual_value
4943
elsif serializer && serializer.object
5044
ResourceIdentifier.new(serializer, serializable_resource_options).as_json
5145
end
5246
end
47+
48+
def links_for(association)
49+
association.links.each_with_object({}) do |(key, value), hash|
50+
result = Link.new(parent_serializer, value).as_json
51+
hash[key] = result if result
52+
end
53+
end
54+
55+
def meta_for(association)
56+
meta = association.meta
57+
meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
58+
end
5359
end
5460
end
5561
end

test/adapter/json_api/relationship_test.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,17 @@ def test_relationship(expected, test_options = {})
155155

156156
serializable_resource_options = {} # adapter.instance_options
157157

158-
meta = test_options.delete(:meta)
159-
options = test_options.delete(:options)
160-
links = test_options.delete(:links)
158+
options = test_options.delete(:options) || {}
159+
options[:links] = test_options.delete(:links)
160+
options[:meta] = test_options.delete(:meta)
161161
association_serializer = @serializer
162162
if association_serializer && association_serializer.object
163163
association_name = association_serializer.json_key.to_sym
164-
association = ::ActiveModel::Serializer::Association.new(association_name, association_serializer, options, links, meta)
164+
options[:serializer] = association_serializer
165+
association = ::ActiveModel::Serializer::Association.new(association_name, options, nil)
165166
else
166-
association = ::ActiveModel::Serializer::Association.new(:association_name_not_used, association, options, links, meta)
167+
options[:serializer] = association
168+
association = ::ActiveModel::Serializer::Association.new(:association_name_not_used, options, nil)
167169
end
168170

169171
relationship = Relationship.new(parent_serializer, serializable_resource_options, association)

test/serializers/associations_test.rb

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ def test_has_many_and_has_one
3131

3232
case key
3333
when :posts
34-
assert_equal({ include_data: true }, options)
34+
assert_equal true, options.fetch(:include_data)
3535
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
3636
when :bio
37-
assert_equal({ include_data: true }, options)
37+
assert_equal true, options.fetch(:include_data)
3838
assert_nil serializer
3939
when :roles
40-
assert_equal({ include_data: true }, options)
40+
assert_equal true, options.fetch(:include_data)
4141
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
4242
else
4343
flunk "Unknown association: #{key}"
@@ -79,7 +79,7 @@ def test_belongs_to
7979
flunk "Unknown association: #{key}"
8080
end
8181

82-
assert_equal({ include_data: true }, association.options)
82+
assert_equal true, association.options.fetch(:include_data)
8383
end
8484
end
8585

@@ -291,11 +291,23 @@ def test_illegal_conditional_associations
291291
end
292292

293293
class InheritedSerializerTest < ActiveSupport::TestCase
294+
class PostSerializer < ActiveModel::Serializer
295+
belongs_to :author
296+
has_many :comments
297+
belongs_to :blog
298+
end
299+
294300
class InheritedPostSerializer < PostSerializer
295301
belongs_to :author, polymorphic: true
296302
has_many :comments, key: :reviews
297303
end
298304

305+
class AuthorSerializer < ActiveModel::Serializer
306+
has_many :posts
307+
has_many :roles
308+
has_one :bio
309+
end
310+
299311
class InheritedAuthorSerializer < AuthorSerializer
300312
has_many :roles, polymorphic: true
301313
has_one :bio, polymorphic: true
@@ -333,9 +345,18 @@ def setup
333345
end
334346

335347
test 'a serializer inheriting from another serializer can redefine belongs_to associations' do
336-
expected = [:author, :comments, :blog].sort
337-
result = (@inherited_post_associations - @post_associations).map(&:name).sort
338-
assert_equal(result, expected)
348+
assert_equal [:author, :comments, :blog], @post_associations.map(&:name)
349+
assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name)
350+
351+
refute @post_associations.detect { |assoc| assoc.name == :author }.options.key?(:polymorphic)
352+
assert_equal true, @inherited_post_associations.detect { |assoc| assoc.name == :author }.options.fetch(:polymorphic)
353+
354+
refute @post_associations.detect { |assoc| assoc.name == :comments }.options.key?(:key)
355+
original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments }
356+
refute original_comment_assoc.options.key?(:key)
357+
assert_equal :reviews, new_comments_assoc.options.fetch(:key)
358+
359+
assert_equal @post_associations.detect { |assoc| assoc.name == :blog }, @inherited_post_associations.detect { |assoc| assoc.name == :blog }
339360
end
340361

341362
test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do

0 commit comments

Comments
 (0)