jsonapi_compliable 0.7.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 725d1d0d6d2f31535a981d6894f6f49fa4d59686
4
- data.tar.gz: 04d9c2419e99b379cb2884eaaf63f3f022bb8e9e
3
+ metadata.gz: 10190f5b7e0cd15456e21552f847b9a41d5fae80
4
+ data.tar.gz: 002b8c3ffb2fe962e682182199519a8182a909a0
5
5
  SHA512:
6
- metadata.gz: 8b2ac3294051a12d5fc84e508be50a6d8b890109ef253e27ac975ed545e649e007b1035e8a6ff2c7da58861a0c27e5ade4f96a90c07564649ea084783d058dfc
7
- data.tar.gz: afef6fafdc7ef0a4b4b36ad3c2ad0f0d8c23286c55dd61b20bec29688e2e3b1d7c3bbb9ff4ead5d3c3f1aeb7637682bb66cfe1d7a5a0bfadf9147fa752b39ebc
6
+ metadata.gz: 5744a2c7eba2af24ea476210cf9804fdbc16d0c636b6e9d55c586e198a6ddf196122f8e6e27029d7f723cd07670efbea3d6aa21d4368ece68152c212249dd0ca
7
+ data.tar.gz: dbae4b77df449427b11d0dd5135a722b713a11c8f7fd05d62192dbd13c043ce48fcd3517179fb72afef9a05d460aadc6a4137fda0ef200f1bd1b6f9a99eca2c8
@@ -237,6 +237,46 @@ module JsonapiCompliable
237
237
  raise 'you must override #associate in an adapter subclass'
238
238
  end
239
239
 
240
+ # Remove the association without destroying objects
241
+ #
242
+ # This is NOT needed in the standard use case. The standard use case would be:
243
+ #
244
+ # def update(attrs)
245
+ # # attrs[:the_foreign_key] is nil, so updating the record disassociates
246
+ # end
247
+ #
248
+ # However, sometimes you need side-effect or elsewise non-standard behavior. Consider
249
+ # using {{https://github.com/mbleigh/acts-as-taggable-on acts_as_taggable_on}} gem:
250
+ #
251
+ # # Not actually needed, just an example
252
+ # def disassociate(parent, child, association_name, association_type)
253
+ # parent.tag_list.remove(child.name)
254
+ # end
255
+ #
256
+ # @example Basic accessor
257
+ # def disassociate(parent, child, association_name, association_type)
258
+ # if association_type == :has_many
259
+ # parent.send(association_name).delete(child)
260
+ # else
261
+ # child.send(:"#{association_name}=", nil)
262
+ # end
263
+ # end
264
+ #
265
+ # +association_name+ and +association_type+ come from your sideload
266
+ # configuration:
267
+ #
268
+ # allow_sideload :the_name, type: the_type do
269
+ # # ... code.
270
+ # end
271
+ #
272
+ # @param parent The parent object (via the JSONAPI 'relationships' graph)
273
+ # @param child The child object (via the JSONAPI 'relationships' graph)
274
+ # @param association_name The 'relationships' key we are processing
275
+ # @param association_type The Sideload type (see Sideload#type). Usually :has_many/:belongs_to/etc
276
+ def disassociate(parent, child, association_name, association_type)
277
+ raise 'you must override #disassociate in an adapter subclass'
278
+ end
279
+
240
280
  # This module gets mixed in to Sideload classes
241
281
  # This is where you define methods like has_many,
242
282
  # belongs_to etc that wrap the lower-level Sideload#allow_sideload
@@ -75,11 +75,24 @@ module JsonapiCompliable
75
75
  if association_type == :has_many
76
76
  parent.association(association_name).loaded!
77
77
  parent.association(association_name).add_to_target(child, :skip_callbacks)
78
+ elsif association_type == :habtm
79
+ parent.send(association_name) << child
78
80
  else
79
81
  child.send("#{association_name}=", parent)
80
82
  end
81
83
  end
82
84
 
85
+ # When a has_and_belongs_to_many relationship, we don't have a foreign
86
+ # key that can be null'd. Instead, go through the ActiveRecord API.
87
+ # @see Adapters::Abstract#disassociate
88
+ def disassociate(parent, child, association_name, association_type)
89
+ if association_type == :habtm
90
+ parent.send(association_name).delete(child)
91
+ else
92
+ # Nothing to do here, happened when we merged foreign key
93
+ end
94
+ end
95
+
83
96
  # (see Adapters::Abstract#create)
84
97
  def create(model_class, create_params)
85
98
  instance = model_class.new(create_params)
@@ -543,10 +543,28 @@ module JsonapiCompliable
543
543
  adapter.destroy(model, id)
544
544
  end
545
545
 
546
+ # Delegates #associate to adapter. Built for overriding.
547
+ #
548
+ # @see .use_adapter
549
+ # @see Adapters::Abstract#associate
550
+ # @see Adapters::ActiveRecord#associate
551
+ def associate(parent, child, association_name, type)
552
+ adapter.associate(parent, child, association_name, type)
553
+ end
554
+
555
+ # Delegates #disassociate to adapter. Built for overriding.
556
+ #
557
+ # @see .use_adapter
558
+ # @see Adapters::Abstract#disassociate
559
+ # @see Adapters::ActiveRecord#disassociate
560
+ def disassociate(parent, child, association_name, type)
561
+ adapter.disassociate(parent, child, association_name, type)
562
+ end
563
+
546
564
  # @api private
547
- def persist_with_relationships(meta, attributes, relationships)
565
+ def persist_with_relationships(meta, attributes, relationships, caller_model = nil)
548
566
  persistence = JsonapiCompliable::Util::Persistence \
549
- .new(self, meta, attributes, relationships)
567
+ .new(self, meta, attributes, relationships, caller_model)
550
568
  persistence.run
551
569
  end
552
570
 
@@ -170,24 +170,25 @@ module JsonapiCompliable
170
170
  end
171
171
 
172
172
  # Configure how to associate parent and child records.
173
- #
174
- # @example Basic attr_accessor
175
- # def associate(parent, child)
176
- # if type == :has_many
177
- # parent.send(:"#{name}").push(child)
178
- # else
179
- # child.send(:"#{name}=", parent)
180
- # end
181
- # end
173
+ # Delegates to #resource
182
174
  #
183
175
  # @see #name
184
176
  # @see #type
177
+ # @api private
185
178
  def associate(parent, child)
186
179
  association_name = @parent ? @parent.name : name
187
- resource_class.config[:adapter].associate parent,
188
- child,
189
- association_name,
190
- type
180
+ resource.associate(parent, child, association_name, type)
181
+ end
182
+
183
+ # Configure how to disassociate parent and child records.
184
+ # Delegates to #resource
185
+ #
186
+ # @see #name
187
+ # @see #type
188
+ # @api private
189
+ def disassociate(parent, child)
190
+ association_name = @parent ? @parent.name : name
191
+ resource.disassociate(parent, child, association_name, type)
191
192
  end
192
193
 
193
194
  # Define an attribute that groups the parent records. For instance, with
@@ -5,11 +5,12 @@ class JsonapiCompliable::Util::Persistence
5
5
  # @param [Hash] meta see (Deserializer#meta)
6
6
  # @param [Hash] attributes see (Deserializer#attributes)
7
7
  # @param [Hash] relationships see (Deserializer#relationships)
8
- def initialize(resource, meta, attributes, relationships)
8
+ def initialize(resource, meta, attributes, relationships, caller_model)
9
9
  @resource = resource
10
10
  @meta = meta
11
11
  @attributes = attributes
12
12
  @relationships = relationships
13
+ @caller_model = caller_model
13
14
  end
14
15
 
15
16
  # Perform the actual save logic.
@@ -37,7 +38,7 @@ class JsonapiCompliable::Util::Persistence
37
38
  assign_temp_id(persisted, @meta[:temp_id])
38
39
  associate_parents(persisted, parents)
39
40
 
40
- children = process_has_many(@relationships) do |x|
41
+ children = process_has_many(@relationships, persisted) do |x|
41
42
  update_foreign_key(persisted, x[:attributes], x)
42
43
  end
43
44
 
@@ -49,7 +50,11 @@ class JsonapiCompliable::Util::Persistence
49
50
 
50
51
  # The child's attributes should be modified to nil-out the
51
52
  # foreign_key when the parent is being destroyed or disassociated
53
+ #
54
+ # This is not the case for HABTM, whose "foreign key" is a join table
52
55
  def update_foreign_key(parent_object, attrs, x)
56
+ return if x[:sideload].type == :habtm
57
+
53
58
  if [:destroy, :disassociate].include?(x[:meta][:method])
54
59
  attrs[x[:foreign_key]] = nil
55
60
  update_foreign_type(attrs, x, null: true) if x[:is_polymorphic]
@@ -72,33 +77,45 @@ class JsonapiCompliable::Util::Persistence
72
77
 
73
78
  def associate_parents(object, parents)
74
79
  parents.each do |x|
75
- x[:sideload].associate(x[:object], object) if x[:object] && object
80
+ if x[:object] && object
81
+ if x[:meta][:method] == :disassociate
82
+ x[:sideload].disassociate(x[:object], object)
83
+ else
84
+ x[:sideload].associate(x[:object], object)
85
+ end
86
+ end
76
87
  end
77
88
  end
78
89
 
79
90
  def associate_children(object, children)
80
91
  children.each do |x|
81
- x[:sideload].associate(object, x[:object]) if x[:object] && object
92
+ if x[:object] && object
93
+ if x[:meta][:method] == :disassociate
94
+ x[:sideload].disassociate(object, x[:object])
95
+ else
96
+ x[:sideload].associate(object, x[:object])
97
+ end
98
+ end
82
99
  end
83
100
  end
84
101
 
85
102
  def persist_object(method, attributes)
86
103
  case method
87
104
  when :destroy
88
- @resource.destroy(attributes[:id])
89
- when :disassociate, nil
90
- @resource.update(attributes)
105
+ call_resource_method(:destroy, attributes[:id], @caller_model)
106
+ when :update, nil, :disassociate
107
+ call_resource_method(:update, attributes, @caller_model)
91
108
  else
92
- @resource.send(method, attributes)
109
+ call_resource_method(:create, attributes, @caller_model)
93
110
  end
94
111
  end
95
112
 
96
- def process_has_many(relationships)
113
+ def process_has_many(relationships, caller_model)
97
114
  [].tap do |processed|
98
115
  iterate(except: [:polymorphic_belongs_to, :belongs_to]) do |x|
99
116
  yield x
100
117
  x[:object] = x[:sideload].resource
101
- .persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
118
+ .persist_with_relationships(x[:meta], x[:attributes], x[:relationships], caller_model)
102
119
  processed << x
103
120
  end
104
121
  end
@@ -128,4 +145,23 @@ class JsonapiCompliable::Util::Persistence
128
145
  yield x
129
146
  end
130
147
  end
148
+
149
+ # In the Resource, we want to allow:
150
+ #
151
+ # def create(attrs)
152
+ #
153
+ # and
154
+ #
155
+ # def create(attrs, parent = nil)
156
+ #
157
+ # 'parent' is an optional parameter that should not be part of the
158
+ # method signature in most use cases.
159
+ def call_resource_method(method_name, attributes, caller_model)
160
+ method = @resource.method(method_name)
161
+ if [2,-2].include?(method.arity)
162
+ method.call(attributes, caller_model)
163
+ else
164
+ method.call(attributes)
165
+ end
166
+ end
131
167
  end
@@ -1,3 +1,3 @@
1
1
  module JsonapiCompliable
2
- VERSION = "0.7.9"
2
+ VERSION = "0.8.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi_compliable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.9
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond