@@ -22,6 +22,9 @@ class Resource
2222 def initialize ( model , context )
2323 @model = model
2424 @context = context
25+ @reload_needed = false
26+ @changing = false
27+ @save_needed = false
2528 end
2629
2730 def _model
@@ -63,39 +66,39 @@ def remove
6366 end
6467 end
6568
66- def create_to_many_links ( relationship_type , relationship_key_values )
69+ def create_to_many_links ( relationship_type , relationship_key_values , options = { } )
6770 change :create_to_many_link do
68- _create_to_many_links ( relationship_type , relationship_key_values )
71+ _create_to_many_links ( relationship_type , relationship_key_values , options )
6972 end
7073 end
7174
72- def replace_to_many_links ( relationship_type , relationship_key_values )
75+ def replace_to_many_links ( relationship_type , relationship_key_values , options = { } )
7376 change :replace_to_many_links do
74- _replace_to_many_links ( relationship_type , relationship_key_values )
77+ _replace_to_many_links ( relationship_type , relationship_key_values , options )
7578 end
7679 end
7780
78- def replace_to_one_link ( relationship_type , relationship_key_value )
81+ def replace_to_one_link ( relationship_type , relationship_key_value , options = { } )
7982 change :replace_to_one_link do
80- _replace_to_one_link ( relationship_type , relationship_key_value )
83+ _replace_to_one_link ( relationship_type , relationship_key_value , options )
8184 end
8285 end
8386
84- def replace_polymorphic_to_one_link ( relationship_type , relationship_key_value , relationship_key_type )
87+ def replace_polymorphic_to_one_link ( relationship_type , relationship_key_value , relationship_key_type , options = { } )
8588 change :replace_polymorphic_to_one_link do
86- _replace_polymorphic_to_one_link ( relationship_type , relationship_key_value , relationship_key_type )
89+ _replace_polymorphic_to_one_link ( relationship_type , relationship_key_value , relationship_key_type , options )
8790 end
8891 end
8992
90- def remove_to_many_link ( relationship_type , key )
93+ def remove_to_many_link ( relationship_type , key , options = { } )
9194 change :remove_to_many_link do
92- _remove_to_many_link ( relationship_type , key )
95+ _remove_to_many_link ( relationship_type , key , options )
9396 end
9497 end
9598
96- def remove_to_one_link ( relationship_type )
99+ def remove_to_one_link ( relationship_type , options = { } )
97100 change :remove_to_one_link do
98- _remove_to_one_link ( relationship_type )
101+ _remove_to_one_link ( relationship_type , options )
99102 end
100103 end
101104
@@ -189,6 +192,7 @@ def _save(validation_context = nil)
189192
190193 if defined? @model . save
191194 saved = @model . save ( validate : false )
195+
192196 unless saved
193197 if @model . errors . present?
194198 fail JSONAPI ::Exceptions ::ValidationErrors . new ( self )
@@ -199,6 +203,8 @@ def _save(validation_context = nil)
199203 else
200204 saved = true
201205 end
206+ @model . reload if @reload_needed
207+ @reload_needed = false
202208
203209 @save_needed = !saved
204210
@@ -215,34 +221,87 @@ def _remove
215221 fail JSONAPI ::Exceptions ::RecordLocked . new ( e . message )
216222 end
217223
218- def _create_to_many_links ( relationship_type , relationship_key_values )
224+ def reflect_relationship? ( relationship , options )
225+ return false if !relationship . reflect ||
226+ ( !JSONAPI . configuration . use_relationship_reflection || options [ :reflected_source ] )
227+
228+ inverse_relationship = relationship . resource_klass . _relationships [ relationship . inverse_relationship ]
229+ if inverse_relationship . nil?
230+ warn "Inverse relationship could not be found for #{ self . class . name } .#{ relationship . name } . Relationship reflection disabled."
231+ return false
232+ end
233+ true
234+ end
235+
236+ def _create_to_many_links ( relationship_type , relationship_key_values , options )
219237 relationship = self . class . _relationships [ relationship_type ]
220238
221- relationship_key_values . each do |relationship_key_value |
222- related_resource = relationship . resource_klass . find_by_key ( relationship_key_value , context : @context )
239+ # check if relationship_key_values are already members of this relationship
240+ relation_name = relationship . relation_name ( context : @context )
241+ existing_relations = @model . public_send ( relation_name ) . where ( relationship . primary_key => relationship_key_values )
242+ if existing_relations . count > 0
243+ # todo: obscure id so not to leak info
244+ fail JSONAPI ::Exceptions ::HasManyRelationExists . new ( existing_relations . first . id )
245+ end
223246
224- relation_name = relationship . relation_name ( context : @context )
225- # TODO: Add option to skip relations that already exist instead of returning an error?
226- relation = @model . public_send ( relation_name ) . where ( relationship . primary_key => relationship_key_value ) . first
227- if relation . nil?
228- @model . public_send ( relation_name ) << related_resource . _model
247+ if options [ :reflected_source ]
248+ @model . public_send ( relation_name ) << options [ :reflected_source ] . _model
249+ return :completed
250+ end
251+
252+ # load requested related resources
253+ # make sure they all exist (also based on context) and add them to relationship
254+
255+ related_resources = relationship . resource_klass . find_by_keys ( relationship_key_values , context : @context )
256+
257+ if related_resources . count != relationship_key_values . count
258+ # todo: obscure id so not to leak info
259+ fail JSONAPI ::Exceptions ::RecordNotFound . new ( 'unspecified' )
260+ end
261+
262+ reflect = reflect_relationship? ( relationship , options )
263+
264+ related_resources . each do |related_resource |
265+ if reflect
266+ if related_resource . class . _relationships [ relationship . inverse_relationship ] . is_a? ( JSONAPI ::Relationship ::ToMany )
267+ related_resource . create_to_many_links ( relationship . inverse_relationship , [ id ] , reflected_source : self )
268+ else
269+ related_resource . replace_to_one_link ( relationship . inverse_relationship , id , reflected_source : self )
270+ end
271+ @reload_needed = true
229272 else
230- fail JSONAPI :: Exceptions :: HasManyRelationExists . new ( relationship_key_value )
273+ @model . public_send ( relation_name ) << related_resource . _model
231274 end
232275 end
233276
234277 :completed
235278 end
236279
237- def _replace_to_many_links ( relationship_type , relationship_key_values )
280+ def _replace_to_many_links ( relationship_type , relationship_key_values , options )
238281 relationship = self . class . _relationships [ relationship_type ]
239- send ( "#{ relationship . foreign_key } =" , relationship_key_values )
240- @save_needed = true
282+
283+ reflect = reflect_relationship? ( relationship , options )
284+
285+ if reflect
286+ existing = send ( "#{ relationship . foreign_key } " )
287+ to_delete = existing - ( relationship_key_values & existing )
288+ to_delete . each do |key |
289+ _remove_to_many_link ( relationship_type , key , reflected_source : self )
290+ end
291+
292+ to_add = relationship_key_values - ( relationship_key_values & existing )
293+ _create_to_many_links ( relationship_type , to_add , { } )
294+
295+ @reload_needed = true
296+ else
297+ send ( "#{ relationship . foreign_key } =" , relationship_key_values )
298+ @save_needed = true
299+ end
241300
242301 :completed
243302 end
244303
245- def _replace_to_one_link ( relationship_type , relationship_key_value )
304+ def _replace_to_one_link ( relationship_type , relationship_key_value , options )
246305 relationship = self . class . _relationships [ relationship_type ]
247306
248307 send ( "#{ relationship . foreign_key } =" , relationship_key_value )
@@ -251,7 +310,7 @@ def _replace_to_one_link(relationship_type, relationship_key_value)
251310 :completed
252311 end
253312
254- def _replace_polymorphic_to_one_link ( relationship_type , key_value , key_type )
313+ def _replace_polymorphic_to_one_link ( relationship_type , key_value , key_type , options )
255314 relationship = self . class . _relationships [ relationship_type . to_sym ]
256315
257316 _model . public_send ( "#{ relationship . foreign_key } =" , key_value )
@@ -262,10 +321,29 @@ def _replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
262321 :completed
263322 end
264323
265- def _remove_to_many_link ( relationship_type , key )
266- relation_name = self . class . _relationships [ relationship_type ] . relation_name ( context : @context )
324+ def _remove_to_many_link ( relationship_type , key , options )
325+ relationship = self . class . _relationships [ relationship_type ]
326+
327+ reflect = reflect_relationship? ( relationship , options )
328+
329+ if reflect
330+
331+ related_resource = relationship . resource_klass . find_by_key ( key , context : @context )
332+
333+ if related_resource . nil?
334+ fail JSONAPI ::Exceptions ::RecordNotFound . new ( key )
335+ else
336+ if related_resource . class . _relationships [ relationship . inverse_relationship ] . is_a? ( JSONAPI ::Relationship ::ToMany )
337+ related_resource . remove_to_many_link ( relationship . inverse_relationship , id , reflected_source : self )
338+ else
339+ related_resource . remove_to_one_link ( relationship . inverse_relationship , reflected_source : self )
340+ end
341+ end
267342
268- @model . public_send ( relation_name ) . delete ( key )
343+ @reload_needed = true
344+ else
345+ @model . public_send ( relationship . relation_name ( context : @context ) ) . delete ( key )
346+ end
269347
270348 :completed
271349
@@ -275,7 +353,7 @@ def _remove_to_many_link(relationship_type, key)
275353 fail JSONAPI ::Exceptions ::RecordNotFound . new ( key )
276354 end
277355
278- def _remove_to_one_link ( relationship_type )
356+ def _remove_to_one_link ( relationship_type , options )
279357 relationship = self . class . _relationships [ relationship_type ]
280358
281359 send ( "#{ relationship . foreign_key } =" , nil )
@@ -662,14 +740,21 @@ def find(filters, options = {})
662740 end
663741
664742 def resources_for ( records , context )
665- resources = [ ]
666743 resource_classes = { }
667- records . each do |model |
744+ records . collect do |model |
668745 resource_class = resource_classes [ model . class ] ||= self . resource_for_model ( model )
669- resources . push resource_class . new ( model , context )
746+ resource_class . new ( model , context )
670747 end
748+ end
671749
672- resources
750+ def find_by_keys ( keys , options = { } )
751+ context = options [ :context ]
752+ records = records ( options )
753+ records = apply_includes ( records , options )
754+ models = records . where ( { _primary_key => keys } )
755+ models . collect do |model |
756+ self . resource_for_model ( model ) . new ( model , context )
757+ end
673758 end
674759
675760 def find_by_key ( key , options = { } )
0 commit comments