@@ -47,6 +47,8 @@ class Serializer
4747 #
4848 # So you can inspect reflections in your Adapters.
4949 class Reflection < Field
50+ REFLECTION_OPTIONS = %i( key links polymorphic meta serializer virtual_value namespace ) . freeze
51+
5052 def initialize ( *)
5153 super
5254 options [ :links ] = { }
@@ -71,8 +73,8 @@ def initialize(*)
7173 # meta ids: ids
7274 # end
7375 # end
74- def link ( name , value = nil , & block )
75- options [ :links ] [ name ] = block || value
76+ def link ( name , value = nil )
77+ options [ :links ] [ name ] = block_given? ? Proc . new : value
7678 :nil
7779 end
7880
@@ -86,8 +88,8 @@ def link(name, value = nil, &block)
8688 # href object.blog.id.to_s
8789 # meta(id: object.blog.id)
8890 # end
89- def meta ( value = nil , & block )
90- options [ :meta ] = block || value
91+ def meta ( value = nil )
92+ options [ :meta ] = block_given? ? Proc . new : value
9193 :nil
9294 end
9395
@@ -119,23 +121,6 @@ def include_data(value = true)
119121 :nil
120122 end
121123
122- # @param serializer [ActiveModel::Serializer]
123- # @yield [ActiveModel::Serializer]
124- # @return [:nil, associated resource or resource collection]
125- def value ( serializer , include_slice )
126- @object = serializer . object
127- @scope = serializer . scope
128-
129- block_value = instance_exec ( serializer , &block ) if block
130- return unless include_data? ( include_slice )
131-
132- if block && block_value != :nil
133- block_value
134- else
135- serializer . read_attribute_for_serialization ( name )
136- end
137- end
138-
139124 # Build association. This method is used internally to
140125 # build serializer's association by its reflection.
141126 #
@@ -157,43 +142,66 @@ def value(serializer, include_slice)
157142 #
158143 # @api private
159144 def build_association ( parent_serializer , parent_serializer_options , include_slice = { } )
160- reflection_options = options . dup
145+ reflection_options = settings . merge ( include_data : include_data? ( include_slice ) ) unless block?
146+ association_options = build_association_options ( parent_serializer , parent_serializer_options [ :namespace ] , include_slice )
147+ association_value = association_options [ :association_value ]
148+ serializer_class = association_options [ :association_serializer ]
161149
162- # Pass the parent's namespace onto the child serializer
163- reflection_options [ :namespace ] ||= parent_serializer_options [ :namespace ]
164-
165- association_value = value ( parent_serializer , include_slice )
166- serializer_class = parent_serializer . class . serializer_for ( association_value , reflection_options )
167- reflection_options [ :include_data ] = include_data? ( include_slice )
168- reflection_options [ :links ] = options [ :links ]
169- reflection_options [ :meta ] = options [ :meta ]
150+ reflection_options ||= settings . merge ( include_data : include_data? ( include_slice ) ) # Needs to be after association_value is evaluated unless reflection.block.nil?
170151
171152 if serializer_class
172- serializer = catch ( :no_serializer ) do
173- serializer_class . new (
174- association_value ,
175- serializer_options ( parent_serializer , parent_serializer_options , reflection_options )
176- )
177- end
178- if serializer . nil?
179- reflection_options [ :virtual_value ] = association_value . try ( :as_json ) || association_value
180- else
153+ if ( serializer = build_association_serializer ( parent_serializer , parent_serializer_options , association_value , serializer_class ) )
181154 reflection_options [ :serializer ] = serializer
155+ else
156+ # BUG: per #2027, JSON API resource relationships are only id and type, and hence either
157+ # *require* a serializer or we need to be a little clever about figuring out the id/type.
158+ # In either case, returning the raw virtual value will almost always be incorrect.
159+ #
160+ # Should be reflection_options[:virtual_value] or adapter needs to figure out what to do
161+ # with an object that is non-nil and has no defined serializer.
162+ reflection_options [ :virtual_value ] = association_value . try ( :as_json ) || association_value
182163 end
183164 elsif !association_value . nil? && !association_value . instance_of? ( Object )
184165 reflection_options [ :virtual_value ] = association_value
185166 end
186167
187- block = nil
188- Association . new ( name , reflection_options , block )
168+ association_block = nil
169+ Association . new ( name , reflection_options , association_block )
189170 end
190171
191172 protected
192173
193174 # used in instance exec
194175 attr_accessor :object , :scope
195176
196- private
177+ def settings
178+ options . dup . reject { |k , _ | !REFLECTION_OPTIONS . include? ( k ) }
179+ end
180+
181+ # Evaluation of the reflection.block will mutate options.
182+ # So, the settings cannot be used until the block is evaluated.
183+ # This means that each time the block is evaluated, it may set a new
184+ # value in the reflection instance. This is not thread-safe.
185+ # @example
186+ # has_many :likes do
187+ # meta liked: object.likes.any?
188+ # include_data: object.loaded?
189+ # end
190+ def block?
191+ !block . nil?
192+ end
193+
194+ def serializer?
195+ options . key? ( :serializer )
196+ end
197+
198+ def serializer
199+ options [ :serializer ]
200+ end
201+
202+ def namespace
203+ options [ :namespace ]
204+ end
197205
198206 def include_data? ( include_slice )
199207 include_data_setting = options [ :include_data_setting ]
@@ -205,13 +213,49 @@ def include_data?(include_slice)
205213 end
206214 end
207215
208- def serializer_options ( parent_serializer , parent_serializer_options , reflection_options )
209- serializer = reflection_options . fetch ( :serializer , nil )
216+ # @param serializer [ActiveModel::Serializer]
217+ # @yield [ActiveModel::Serializer]
218+ # @return [:nil, associated resource or resource collection]
219+ def value ( serializer , include_slice )
220+ @object = serializer . object
221+ @scope = serializer . scope
210222
211- serializer_options = parent_serializer_options . except ( :serializer )
212- serializer_options [ :serializer ] = serializer if serializer
213- serializer_options [ :serializer_context_class ] = parent_serializer . class
214- serializer_options
223+ block_value = instance_exec ( serializer , &block ) if block
224+ return unless include_data? ( include_slice )
225+
226+ if block && block_value != :nil
227+ block_value
228+ else
229+ serializer . read_attribute_for_serialization ( name )
230+ end
231+ end
232+
233+ def build_association_options ( parent_serializer , parent_serializer_namespace_option , include_slice )
234+ serializer_for_options = {
235+ # Pass the parent's namespace onto the child serializer
236+ namespace : namespace || parent_serializer_namespace_option
237+ }
238+ serializer_for_options [ :serializer ] = serializer if serializer?
239+ association_value = value ( parent_serializer , include_slice )
240+ {
241+ association_value : association_value ,
242+ association_serializer : parent_serializer . class . serializer_for ( association_value , serializer_for_options )
243+ }
244+ end
245+
246+ # NOTE(BF): This serializer throw/catch should only happen when the serializer is a collection
247+ # serializer. This is a good reason for the reflection to have a to_many? or collection? type method.
248+ #
249+ # @return [ActiveModel::Serializer, nil]
250+ def build_association_serializer ( parent_serializer , parent_serializer_options , association_value , serializer_class )
251+ catch ( :no_serializer ) do
252+ # Make all the parent serializer instance options available to associations
253+ # except ActiveModelSerializers-specific ones we don't want.
254+ serializer_options = parent_serializer_options . except ( :serializer )
255+ serializer_options [ :serializer_context_class ] = parent_serializer . class
256+ serializer_options [ :serializer ] = serializer if serializer
257+ serializer_class . new ( association_value , serializer_options )
258+ end
215259 end
216260 end
217261 end
0 commit comments