graphiti-rb 1.0.alpha.1
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +20 -0
- data/.yardopts +2 -0
- data/Appraisals +11 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +12 -0
- data/Guardfile +32 -0
- data/LICENSE.txt +21 -0
- data/README.md +75 -0
- data/Rakefile +15 -0
- data/bin/appraisal +17 -0
- data/bin/console +14 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/gemfiles/rails_4.gemfile +17 -0
- data/gemfiles/rails_5.gemfile +17 -0
- data/graphiti.gemspec +34 -0
- data/lib/generators/jsonapi/resource_generator.rb +169 -0
- data/lib/generators/jsonapi/templates/application_resource.rb.erb +15 -0
- data/lib/generators/jsonapi/templates/controller.rb.erb +61 -0
- data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +30 -0
- data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +20 -0
- data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +22 -0
- data/lib/generators/jsonapi/templates/resource.rb.erb +11 -0
- data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
- data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
- data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +21 -0
- data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +34 -0
- data/lib/graphiti-rb.rb +1 -0
- data/lib/graphiti.rb +121 -0
- data/lib/graphiti/adapters/abstract.rb +516 -0
- data/lib/graphiti/adapters/active_record.rb +6 -0
- data/lib/graphiti/adapters/active_record/base.rb +249 -0
- data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_many_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_one_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/inferrence.rb +12 -0
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +30 -0
- data/lib/graphiti/adapters/null.rb +236 -0
- data/lib/graphiti/base.rb +70 -0
- data/lib/graphiti/configuration.rb +21 -0
- data/lib/graphiti/context.rb +16 -0
- data/lib/graphiti/deserializer.rb +208 -0
- data/lib/graphiti/errors.rb +309 -0
- data/lib/graphiti/extensions/boolean_attribute.rb +33 -0
- data/lib/graphiti/extensions/extra_attribute.rb +70 -0
- data/lib/graphiti/extensions/temp_id.rb +26 -0
- data/lib/graphiti/filter_operators.rb +25 -0
- data/lib/graphiti/hash_renderer.rb +57 -0
- data/lib/graphiti/jsonapi_serializable_ext.rb +50 -0
- data/lib/graphiti/query.rb +251 -0
- data/lib/graphiti/rails.rb +28 -0
- data/lib/graphiti/railtie.rb +74 -0
- data/lib/graphiti/renderer.rb +60 -0
- data/lib/graphiti/resource.rb +110 -0
- data/lib/graphiti/resource/configuration.rb +239 -0
- data/lib/graphiti/resource/dsl.rb +138 -0
- data/lib/graphiti/resource/interface.rb +32 -0
- data/lib/graphiti/resource/polymorphism.rb +68 -0
- data/lib/graphiti/resource/sideloading.rb +102 -0
- data/lib/graphiti/resource_proxy.rb +127 -0
- data/lib/graphiti/responders.rb +19 -0
- data/lib/graphiti/runner.rb +25 -0
- data/lib/graphiti/scope.rb +98 -0
- data/lib/graphiti/scoping/base.rb +99 -0
- data/lib/graphiti/scoping/default_filter.rb +58 -0
- data/lib/graphiti/scoping/extra_attributes.rb +29 -0
- data/lib/graphiti/scoping/filter.rb +93 -0
- data/lib/graphiti/scoping/filterable.rb +36 -0
- data/lib/graphiti/scoping/paginate.rb +87 -0
- data/lib/graphiti/scoping/sort.rb +64 -0
- data/lib/graphiti/sideload.rb +281 -0
- data/lib/graphiti/sideload/belongs_to.rb +34 -0
- data/lib/graphiti/sideload/has_many.rb +16 -0
- data/lib/graphiti/sideload/has_one.rb +9 -0
- data/lib/graphiti/sideload/many_to_many.rb +24 -0
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +108 -0
- data/lib/graphiti/stats/dsl.rb +89 -0
- data/lib/graphiti/stats/payload.rb +49 -0
- data/lib/graphiti/types.rb +172 -0
- data/lib/graphiti/util/attribute_check.rb +88 -0
- data/lib/graphiti/util/field_params.rb +16 -0
- data/lib/graphiti/util/hash.rb +51 -0
- data/lib/graphiti/util/hooks.rb +33 -0
- data/lib/graphiti/util/include_params.rb +39 -0
- data/lib/graphiti/util/persistence.rb +219 -0
- data/lib/graphiti/util/relationship_payload.rb +64 -0
- data/lib/graphiti/util/serializer_attributes.rb +97 -0
- data/lib/graphiti/util/sideload.rb +33 -0
- data/lib/graphiti/util/validation_response.rb +78 -0
- data/lib/graphiti/version.rb +3 -0
- metadata +317 -0
@@ -0,0 +1,516 @@
|
|
1
|
+
module Graphiti
|
2
|
+
module Adapters
|
3
|
+
# Adapters DRY up common resource logic.
|
4
|
+
#
|
5
|
+
# For instance, there's no reason to write ActiveRecord logic like this in
|
6
|
+
# every Resource:
|
7
|
+
#
|
8
|
+
# allow_filter :title do |scope, value|
|
9
|
+
# scope.where(title: value)
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# sort do |scope, att, dir|
|
13
|
+
# scope.order(att => dir)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# paginate do |scope, current_page, per_page|
|
17
|
+
# scope.page(current_page).per(per_page)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# This logic can be re-used through an *Adapter*:
|
21
|
+
#
|
22
|
+
# use_adapter Graphiti::Adapters::ActiveRecord
|
23
|
+
# allow_filter :title
|
24
|
+
#
|
25
|
+
# Adapters are pretty simple to write. The corresponding code for the above
|
26
|
+
# ActiveRecord adapter, which should look pretty familiar:
|
27
|
+
#
|
28
|
+
# class Graphiti::Adapters::ActiveRecord
|
29
|
+
# def filter(scope, attribute, value)
|
30
|
+
# scope.where(attribute => value)
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def order(scope, attribute, direction)
|
34
|
+
# scope.order(attribute => direction)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def paginate(scope, current_page, per_page)
|
38
|
+
# scope.page(current_page).per(per_page)
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# An adapter can have a corresponding +sideloading_module+. This module
|
43
|
+
# gets mixed in to a Sideload. In other words, *Resource* is to
|
44
|
+
# *Adapter* as *Sideload* is to *Adapter#sideloading_module*. Use this
|
45
|
+
# module to define DSL methods that wrap #allow_sideload:
|
46
|
+
#
|
47
|
+
# class MyAdapter < Graphiti::Adapters::Abstract
|
48
|
+
# # ... code ...
|
49
|
+
# def sideloading_module
|
50
|
+
# MySideloadingAdapter
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# module MySideloadingAdapter
|
55
|
+
# def belongs_to(association_name)
|
56
|
+
# allow_sideload association_name do
|
57
|
+
# # ... code ...
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# # And now in your Resource:
|
63
|
+
# class MyResource < ApplicationResource
|
64
|
+
# # ... code ...
|
65
|
+
# use_adapter MyAdapter
|
66
|
+
#
|
67
|
+
# belongs_to :my_association
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# If you need the adapter to do *nothing*, because perhaps the API you
|
71
|
+
# are hitting does not support sorting,
|
72
|
+
# use +Graphiti::Adapters::Null+.
|
73
|
+
#
|
74
|
+
# @see Resource.use_adapter
|
75
|
+
# @see Adapters::ActiveRecord
|
76
|
+
# @see Adapters::ActiveRecordSideloading
|
77
|
+
# @see Adapters::Null
|
78
|
+
class Abstract
|
79
|
+
def filter_string_eq(scope, attribute, value)
|
80
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_eq)
|
81
|
+
end
|
82
|
+
|
83
|
+
def filter_string_eql(scope, attribute, value)
|
84
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_eql)
|
85
|
+
end
|
86
|
+
|
87
|
+
def filter_string_not_eq(scope, attribute, value)
|
88
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_not_eq)
|
89
|
+
end
|
90
|
+
|
91
|
+
def filter_string_not_eql(scope, attribute, value)
|
92
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_not_eql)
|
93
|
+
end
|
94
|
+
|
95
|
+
def filter_string_prefix(scope, attribute, value)
|
96
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_prefix)
|
97
|
+
end
|
98
|
+
|
99
|
+
def filter_string_not_prefix(scope, attribute, value)
|
100
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_not_prefix)
|
101
|
+
end
|
102
|
+
|
103
|
+
def filter_string_suffix(scope, attribute, value)
|
104
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_suffix)
|
105
|
+
end
|
106
|
+
|
107
|
+
def filter_string_not_suffix(scope, attribute, value)
|
108
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_not_suffix)
|
109
|
+
end
|
110
|
+
|
111
|
+
def filter_string_match(scope, attribute, value)
|
112
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_match)
|
113
|
+
end
|
114
|
+
|
115
|
+
def filter_string_not_match(scope, attribute, value)
|
116
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_string_not_match)
|
117
|
+
end
|
118
|
+
|
119
|
+
def filter_integer_eq(scope, attribute, value)
|
120
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_integer_eq)
|
121
|
+
end
|
122
|
+
|
123
|
+
def filter_integer_not_eq(scope, attribute, value)
|
124
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_integer_not_eq)
|
125
|
+
end
|
126
|
+
|
127
|
+
def filter_integer_gt(scope, attribute, value)
|
128
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_integer_gt)
|
129
|
+
end
|
130
|
+
|
131
|
+
def filter_integer_gte(scope, attribute, value)
|
132
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_integer_gte)
|
133
|
+
end
|
134
|
+
|
135
|
+
def filter_integer_lt(scope, attribute, value)
|
136
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_integer_lt)
|
137
|
+
end
|
138
|
+
|
139
|
+
def filter_integer_lte(scope, attribute, value)
|
140
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_integer_lte)
|
141
|
+
end
|
142
|
+
|
143
|
+
def filter_datetime_eq(scope, attribute, value)
|
144
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_eq)
|
145
|
+
end
|
146
|
+
|
147
|
+
def filter_datetime_not_eq(scope, attribute, value)
|
148
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_not_eq)
|
149
|
+
end
|
150
|
+
|
151
|
+
def filter_datetime_lte(scope, attribute, value)
|
152
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_lte)
|
153
|
+
end
|
154
|
+
|
155
|
+
def filter_float_eq(scope, attribute, value)
|
156
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_float_eq)
|
157
|
+
end
|
158
|
+
|
159
|
+
def filter_float_not_eq(scope, attribute, value)
|
160
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_float_not_eq)
|
161
|
+
end
|
162
|
+
|
163
|
+
def filter_float_gt(scope, attribute, value)
|
164
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_float_gt)
|
165
|
+
end
|
166
|
+
|
167
|
+
def filter_float_gte(scope, attribute, value)
|
168
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_float_gte)
|
169
|
+
end
|
170
|
+
|
171
|
+
def filter_float_lt(scope, attribute, value)
|
172
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_float_lt)
|
173
|
+
end
|
174
|
+
|
175
|
+
def filter_float_lte(scope, attribute, value)
|
176
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_float_lte)
|
177
|
+
end
|
178
|
+
|
179
|
+
def filter_big_decimal_eq(scope, attribute, value)
|
180
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_decimal_eq)
|
181
|
+
end
|
182
|
+
|
183
|
+
def filter_big_decimal_not_eq(scope, attribute, value)
|
184
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_decimal_not_eq)
|
185
|
+
end
|
186
|
+
|
187
|
+
def filter_big_decimal_gt(scope, attribute, value)
|
188
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_decimal_gt)
|
189
|
+
end
|
190
|
+
|
191
|
+
def filter_big_decimal_gte(scope, attribute, value)
|
192
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_decimal_gte)
|
193
|
+
end
|
194
|
+
|
195
|
+
def filter_big_decimal_lt(scope, attribute, value)
|
196
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_decimal_lt)
|
197
|
+
end
|
198
|
+
|
199
|
+
def filter_big_decimal_lte(scope, attribute, value)
|
200
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_decimal_lte)
|
201
|
+
end
|
202
|
+
|
203
|
+
def filter_datetime_eq(scope, attribute, value)
|
204
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_eq)
|
205
|
+
end
|
206
|
+
|
207
|
+
def filter_datetime_not_eq(scope, attribute, value)
|
208
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_not_eq)
|
209
|
+
end
|
210
|
+
|
211
|
+
def filter_datetime_gt(scope, attribute, value)
|
212
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_gt)
|
213
|
+
end
|
214
|
+
|
215
|
+
def filter_datetime_gte(scope, attribute, value)
|
216
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_gte)
|
217
|
+
end
|
218
|
+
|
219
|
+
def filter_datetime_lt(scope, attribute, value)
|
220
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_lt)
|
221
|
+
end
|
222
|
+
|
223
|
+
def filter_datetime_lte(scope, attribute, value)
|
224
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_datetime_lte)
|
225
|
+
end
|
226
|
+
|
227
|
+
def filter_date_eq(scope, attribute, value)
|
228
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_date_eq)
|
229
|
+
end
|
230
|
+
|
231
|
+
def filter_date_not_eq(scope, attribute, value)
|
232
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_date_not_eq)
|
233
|
+
end
|
234
|
+
|
235
|
+
def filter_date_gt(scope, attribute, value)
|
236
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_date_gt)
|
237
|
+
end
|
238
|
+
|
239
|
+
def filter_date_gte(scope, attribute, value)
|
240
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_date_gte)
|
241
|
+
end
|
242
|
+
|
243
|
+
def filter_date_lt(scope, attribute, value)
|
244
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_date_lt)
|
245
|
+
end
|
246
|
+
|
247
|
+
def filter_date_lte(scope, attribute, value)
|
248
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_date_lte)
|
249
|
+
end
|
250
|
+
|
251
|
+
def filter_boolean_eq(scope, attribute, value)
|
252
|
+
raise Errors::AdapterNotImplemented.new(self, attribute, :filter_boolean_eq)
|
253
|
+
end
|
254
|
+
|
255
|
+
def base_scope(model)
|
256
|
+
raise 'you must override #base_scope in an adapter subclass'
|
257
|
+
end
|
258
|
+
|
259
|
+
# @param scope The scope object we are chaining
|
260
|
+
# @param [Symbol] attribute The attribute name we are sorting
|
261
|
+
# @param [Symbol] direction The direction we are sorting (asc/desc)
|
262
|
+
# @return the scope
|
263
|
+
#
|
264
|
+
# @example ActiveRecord default
|
265
|
+
# def order(scope, attribute, direction)
|
266
|
+
# scope.order(attribute => direction)
|
267
|
+
# end
|
268
|
+
def order(scope, attribute, direction)
|
269
|
+
raise 'you must override #order in an adapter subclass'
|
270
|
+
end
|
271
|
+
|
272
|
+
# @param scope The scope object we are chaining
|
273
|
+
# @param [Integer] current_page The current page number
|
274
|
+
# @param [Integer] per_page The number of results per page
|
275
|
+
# @return the scope
|
276
|
+
#
|
277
|
+
# @example ActiveRecord default
|
278
|
+
# # via kaminari gem
|
279
|
+
# def paginate(scope, current_page, per_page)
|
280
|
+
# scope.page(current_page).per(per_page)
|
281
|
+
# end
|
282
|
+
def paginate(scope, current_page, per_page)
|
283
|
+
raise 'you must override #paginate in an adapter subclass'
|
284
|
+
end
|
285
|
+
|
286
|
+
# @param scope the scope object we are chaining
|
287
|
+
# @param [Symbol] attr corresponding stat attribute name
|
288
|
+
# @return [Numeric] the count of the scope
|
289
|
+
# @example ActiveRecord default
|
290
|
+
# def count(scope, attr)
|
291
|
+
# column = attr == :total ? :all : attr
|
292
|
+
# scope.uniq.count(column)
|
293
|
+
# end
|
294
|
+
def count(scope, attr)
|
295
|
+
raise 'you must override #count in an adapter subclass'
|
296
|
+
end
|
297
|
+
|
298
|
+
# @param scope the scope object we are chaining
|
299
|
+
# @param [Symbol] attr corresponding stat attribute name
|
300
|
+
# @return [Float] the average of the scope
|
301
|
+
# @example ActiveRecord default
|
302
|
+
# def average(scope, attr)
|
303
|
+
# scope.average(attr).to_f
|
304
|
+
# end
|
305
|
+
def average(scope, attr)
|
306
|
+
raise 'you must override #average in an adapter subclass'
|
307
|
+
end
|
308
|
+
|
309
|
+
# @param scope the scope object we are chaining
|
310
|
+
# @param [Symbol] attr corresponding stat attribute name
|
311
|
+
# @return [Numeric] the sum of the scope
|
312
|
+
# @example ActiveRecord default
|
313
|
+
# def sum(scope, attr)
|
314
|
+
# scope.sum(attr)
|
315
|
+
# end
|
316
|
+
def sum(scope, attr)
|
317
|
+
raise 'you must override #sum in an adapter subclass'
|
318
|
+
end
|
319
|
+
|
320
|
+
# @param scope the scope object we are chaining
|
321
|
+
# @param [Symbol] attr corresponding stat attribute name
|
322
|
+
# @return [Numeric] the maximum value of the scope
|
323
|
+
# @example ActiveRecord default
|
324
|
+
# def maximum(scope, attr)
|
325
|
+
# scope.maximum(attr)
|
326
|
+
# end
|
327
|
+
def maximum(scope, attr)
|
328
|
+
raise 'you must override #maximum in an adapter subclass'
|
329
|
+
end
|
330
|
+
|
331
|
+
# @param scope the scope object we are chaining
|
332
|
+
# @param [Symbol] attr corresponding stat attribute name
|
333
|
+
# @return [Numeric] the maximum value of the scope
|
334
|
+
# @example ActiveRecord default
|
335
|
+
# def maximum(scope, attr)
|
336
|
+
# scope.maximum(attr)
|
337
|
+
# end
|
338
|
+
def minimum(scope, attr)
|
339
|
+
raise 'you must override #maximum in an adapter subclass'
|
340
|
+
end
|
341
|
+
|
342
|
+
# This method must +yield+ the code to run within the transaction.
|
343
|
+
# This method should roll back the transaction if an error is raised.
|
344
|
+
#
|
345
|
+
# @param [Class] model_class The class we're operating on
|
346
|
+
# @example ActiveRecord default
|
347
|
+
# def transaction(model_class)
|
348
|
+
# model_class.transaction do
|
349
|
+
# yield
|
350
|
+
# end
|
351
|
+
# end
|
352
|
+
#
|
353
|
+
# @see Resource.model
|
354
|
+
def transaction(model_class)
|
355
|
+
raise 'you must override #transaction in an adapter subclass, it must yield'
|
356
|
+
end
|
357
|
+
|
358
|
+
# Resolve the scope. This is where you'd actually fire SQL,
|
359
|
+
# actually make an HTTP call, etc.
|
360
|
+
#
|
361
|
+
# @example ActiveRecordDefault
|
362
|
+
# def resolve(scope)
|
363
|
+
# scope.to_a
|
364
|
+
# end
|
365
|
+
#
|
366
|
+
# @example Suggested Customization
|
367
|
+
# # When making a service call, we suggest this abstraction
|
368
|
+
# # 'scope' here is a hash
|
369
|
+
# def resolve(scope)
|
370
|
+
# # The implementation of .where can be whatever you want
|
371
|
+
# SomeModelClass.where(scope)
|
372
|
+
# end
|
373
|
+
#
|
374
|
+
# @see Adapters::ActiveRecord#resolve
|
375
|
+
# @param scope The scope object to resolve
|
376
|
+
# @return an array of Model instances
|
377
|
+
def resolve(scope)
|
378
|
+
scope
|
379
|
+
end
|
380
|
+
|
381
|
+
def associate_all(parent, children, association_name, association_type)
|
382
|
+
if activerecord_associate?(parent, children[0], association_name)
|
383
|
+
activerecord_adapter.associate_all parent,
|
384
|
+
children, association_name, association_type
|
385
|
+
else
|
386
|
+
children.each do |c|
|
387
|
+
associate(parent, c, association_name, association_type)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def associate(parent, child, association_name, association_type)
|
393
|
+
if activerecord_associate?(parent, child, association_name)
|
394
|
+
activerecord_adapter.associate \
|
395
|
+
parent, child, association_name, association_type
|
396
|
+
else
|
397
|
+
if [:has_many, :many_to_many].include?(association_type)
|
398
|
+
parent.send(:"#{association_name}") << child
|
399
|
+
else
|
400
|
+
parent.send(:"#{association_name}=", child)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# Remove the association without destroying objects
|
406
|
+
#
|
407
|
+
# This is NOT needed in the standard use case. The standard use case would be:
|
408
|
+
#
|
409
|
+
# def update(attrs)
|
410
|
+
# # attrs[:the_foreign_key] is nil, so updating the record disassociates
|
411
|
+
# end
|
412
|
+
#
|
413
|
+
# However, sometimes you need side-effect or elsewise non-standard behavior. Consider
|
414
|
+
# using {{https://github.com/mbleigh/acts-as-taggable-on acts_as_taggable_on}} gem:
|
415
|
+
#
|
416
|
+
# # Not actually needed, just an example
|
417
|
+
# def disassociate(parent, child, association_name, association_type)
|
418
|
+
# parent.tag_list.remove(child.name)
|
419
|
+
# end
|
420
|
+
#
|
421
|
+
# @example Basic accessor
|
422
|
+
# def disassociate(parent, child, association_name, association_type)
|
423
|
+
# if association_type == :has_many
|
424
|
+
# parent.send(association_name).delete(child)
|
425
|
+
# else
|
426
|
+
# child.send(:"#{association_name}=", nil)
|
427
|
+
# end
|
428
|
+
# end
|
429
|
+
#
|
430
|
+
# +association_name+ and +association_type+ come from your sideload
|
431
|
+
# configuration:
|
432
|
+
#
|
433
|
+
# allow_sideload :the_name, type: the_type do
|
434
|
+
# # ... code.
|
435
|
+
# end
|
436
|
+
#
|
437
|
+
# @param parent The parent object (via the JSONAPI 'relationships' graph)
|
438
|
+
# @param child The child object (via the JSONAPI 'relationships' graph)
|
439
|
+
# @param association_name The 'relationships' key we are processing
|
440
|
+
# @param association_type The Sideload type (see Sideload#type). Usually :has_many/:belongs_to/etc
|
441
|
+
def disassociate(parent, child, association_name, association_type)
|
442
|
+
raise 'you must override #disassociate in an adapter subclass'
|
443
|
+
end
|
444
|
+
|
445
|
+
# You want to override this!
|
446
|
+
# Map of association_type => sideload_class
|
447
|
+
# e.g.
|
448
|
+
# { has_many: Adapters::ActiveRecord::HasManySideload }
|
449
|
+
def sideloading_classes
|
450
|
+
{
|
451
|
+
has_many: ::Graphiti::Sideload::HasMany,
|
452
|
+
belongs_to: ::Graphiti::Sideload::BelongsTo,
|
453
|
+
has_one: ::Graphiti::Sideload::HasOne,
|
454
|
+
many_to_many: ::Graphiti::Sideload::ManyToMany,
|
455
|
+
polymorphic_belongs_to: ::Graphiti::Sideload::PolymorphicBelongsTo
|
456
|
+
}
|
457
|
+
end
|
458
|
+
|
459
|
+
# @param [Class] model_class The configured model class (see Resource.model)
|
460
|
+
# @param [Hash] create_params Attributes + id
|
461
|
+
# @return the model instance just created
|
462
|
+
# @see Resource.model
|
463
|
+
# @example ActiveRecord default
|
464
|
+
# def create(model_class, create_params)
|
465
|
+
# instance = model_class.new(create_params)
|
466
|
+
# instance.save
|
467
|
+
# instance
|
468
|
+
# end
|
469
|
+
def create(model_class, create_params)
|
470
|
+
raise 'you must override #create in an adapter subclass'
|
471
|
+
end
|
472
|
+
|
473
|
+
# @param [Class] model_class The configured model class (see Resource.model)
|
474
|
+
# @param [Hash] update_params Attributes + id
|
475
|
+
# @return the model instance just created
|
476
|
+
# @see Resource.model
|
477
|
+
# @example ActiveRecord default
|
478
|
+
# def update(model_class, update_params)
|
479
|
+
# instance = model_class.find(update_params.delete(:id))
|
480
|
+
# instance.update_attributes(update_params)
|
481
|
+
# instance
|
482
|
+
# end
|
483
|
+
def update(model_class, update_params)
|
484
|
+
raise 'you must override #update in an adapter subclass'
|
485
|
+
end
|
486
|
+
|
487
|
+
# @param [Class] model_class The configured model class (see Resource.model)
|
488
|
+
# @param [Integer] id the id for this model
|
489
|
+
# @return the model instance just destroyed
|
490
|
+
# @see Resource.model
|
491
|
+
# @example ActiveRecord default
|
492
|
+
# def destroy(model_class, id)
|
493
|
+
# instance = model_class.find(id)
|
494
|
+
# instance.destroy
|
495
|
+
# instance
|
496
|
+
# end
|
497
|
+
def destroy(model_class, id)
|
498
|
+
raise 'you must override #destroy in an adapter subclass'
|
499
|
+
end
|
500
|
+
|
501
|
+
private
|
502
|
+
|
503
|
+
def activerecord_adapter
|
504
|
+
@activerecord_adapter ||=
|
505
|
+
::Graphiti::Adapters::ActiveRecord::Base.new
|
506
|
+
end
|
507
|
+
|
508
|
+
def activerecord_associate?(parent, child, association_name)
|
509
|
+
defined?(::ActiveRecord) &&
|
510
|
+
parent.is_a?(::ActiveRecord::Base) &&
|
511
|
+
child.is_a?(::ActiveRecord::Base) &&
|
512
|
+
parent.class.reflect_on_association(association_name)
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|