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 +4 -4
- data/lib/jsonapi_compliable/adapters/abstract.rb +40 -0
- data/lib/jsonapi_compliable/adapters/active_record.rb +13 -0
- data/lib/jsonapi_compliable/resource.rb +20 -2
- data/lib/jsonapi_compliable/sideload.rb +14 -13
- data/lib/jsonapi_compliable/util/persistence.rb +46 -10
- data/lib/jsonapi_compliable/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10190f5b7e0cd15456e21552f847b9a41d5fae80
|
4
|
+
data.tar.gz: 002b8c3ffb2fe962e682182199519a8182a909a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
89
|
-
when :
|
90
|
-
|
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
|
-
|
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
|