@@ -47,6 +47,8 @@ class Serializer
47
47
#
48
48
# So you can inspect reflections in your Adapters.
49
49
class Reflection < Field
50
+ REFLECTION_OPTIONS = %i( key links polymorphic meta serializer virtual_value namespace ) . freeze
51
+
50
52
def initialize ( *)
51
53
super
52
54
options [ :links ] = { }
@@ -71,8 +73,8 @@ def initialize(*)
71
73
# meta ids: ids
72
74
# end
73
75
# 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
76
78
:nil
77
79
end
78
80
@@ -86,8 +88,8 @@ def link(name, value = nil, &block)
86
88
# href object.blog.id.to_s
87
89
# meta(id: object.blog.id)
88
90
# 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
91
93
:nil
92
94
end
93
95
@@ -119,23 +121,6 @@ def include_data(value = true)
119
121
:nil
120
122
end
121
123
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
-
139
124
# Build association. This method is used internally to
140
125
# build serializer's association by its reflection.
141
126
#
@@ -157,43 +142,66 @@ def value(serializer, include_slice)
157
142
#
158
143
# @api private
159
144
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 ]
161
149
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?
170
151
171
152
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 ) )
181
154
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
182
163
end
183
164
elsif !association_value . nil? && !association_value . instance_of? ( Object )
184
165
reflection_options [ :virtual_value ] = association_value
185
166
end
186
167
187
- block = nil
188
- Association . new ( name , reflection_options , block )
168
+ association_block = nil
169
+ Association . new ( name , reflection_options , association_block )
189
170
end
190
171
191
172
protected
192
173
193
174
# used in instance exec
194
175
attr_accessor :object , :scope
195
176
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
197
205
198
206
def include_data? ( include_slice )
199
207
include_data_setting = options [ :include_data_setting ]
@@ -205,13 +213,49 @@ def include_data?(include_slice)
205
213
end
206
214
end
207
215
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
210
222
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
215
259
end
216
260
end
217
261
end
0 commit comments