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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +20 -0
  6. data/.yardopts +2 -0
  7. data/Appraisals +11 -0
  8. data/CODE_OF_CONDUCT.md +49 -0
  9. data/Gemfile +12 -0
  10. data/Guardfile +32 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +75 -0
  13. data/Rakefile +15 -0
  14. data/bin/appraisal +17 -0
  15. data/bin/console +14 -0
  16. data/bin/rspec +17 -0
  17. data/bin/setup +8 -0
  18. data/gemfiles/rails_4.gemfile +17 -0
  19. data/gemfiles/rails_5.gemfile +17 -0
  20. data/graphiti.gemspec +34 -0
  21. data/lib/generators/jsonapi/resource_generator.rb +169 -0
  22. data/lib/generators/jsonapi/templates/application_resource.rb.erb +15 -0
  23. data/lib/generators/jsonapi/templates/controller.rb.erb +61 -0
  24. data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +30 -0
  25. data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +20 -0
  26. data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +22 -0
  27. data/lib/generators/jsonapi/templates/resource.rb.erb +11 -0
  28. data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
  29. data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
  30. data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +21 -0
  31. data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +34 -0
  32. data/lib/graphiti-rb.rb +1 -0
  33. data/lib/graphiti.rb +121 -0
  34. data/lib/graphiti/adapters/abstract.rb +516 -0
  35. data/lib/graphiti/adapters/active_record.rb +6 -0
  36. data/lib/graphiti/adapters/active_record/base.rb +249 -0
  37. data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +17 -0
  38. data/lib/graphiti/adapters/active_record/has_many_sideload.rb +17 -0
  39. data/lib/graphiti/adapters/active_record/has_one_sideload.rb +17 -0
  40. data/lib/graphiti/adapters/active_record/inferrence.rb +12 -0
  41. data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +30 -0
  42. data/lib/graphiti/adapters/null.rb +236 -0
  43. data/lib/graphiti/base.rb +70 -0
  44. data/lib/graphiti/configuration.rb +21 -0
  45. data/lib/graphiti/context.rb +16 -0
  46. data/lib/graphiti/deserializer.rb +208 -0
  47. data/lib/graphiti/errors.rb +309 -0
  48. data/lib/graphiti/extensions/boolean_attribute.rb +33 -0
  49. data/lib/graphiti/extensions/extra_attribute.rb +70 -0
  50. data/lib/graphiti/extensions/temp_id.rb +26 -0
  51. data/lib/graphiti/filter_operators.rb +25 -0
  52. data/lib/graphiti/hash_renderer.rb +57 -0
  53. data/lib/graphiti/jsonapi_serializable_ext.rb +50 -0
  54. data/lib/graphiti/query.rb +251 -0
  55. data/lib/graphiti/rails.rb +28 -0
  56. data/lib/graphiti/railtie.rb +74 -0
  57. data/lib/graphiti/renderer.rb +60 -0
  58. data/lib/graphiti/resource.rb +110 -0
  59. data/lib/graphiti/resource/configuration.rb +239 -0
  60. data/lib/graphiti/resource/dsl.rb +138 -0
  61. data/lib/graphiti/resource/interface.rb +32 -0
  62. data/lib/graphiti/resource/polymorphism.rb +68 -0
  63. data/lib/graphiti/resource/sideloading.rb +102 -0
  64. data/lib/graphiti/resource_proxy.rb +127 -0
  65. data/lib/graphiti/responders.rb +19 -0
  66. data/lib/graphiti/runner.rb +25 -0
  67. data/lib/graphiti/scope.rb +98 -0
  68. data/lib/graphiti/scoping/base.rb +99 -0
  69. data/lib/graphiti/scoping/default_filter.rb +58 -0
  70. data/lib/graphiti/scoping/extra_attributes.rb +29 -0
  71. data/lib/graphiti/scoping/filter.rb +93 -0
  72. data/lib/graphiti/scoping/filterable.rb +36 -0
  73. data/lib/graphiti/scoping/paginate.rb +87 -0
  74. data/lib/graphiti/scoping/sort.rb +64 -0
  75. data/lib/graphiti/sideload.rb +281 -0
  76. data/lib/graphiti/sideload/belongs_to.rb +34 -0
  77. data/lib/graphiti/sideload/has_many.rb +16 -0
  78. data/lib/graphiti/sideload/has_one.rb +9 -0
  79. data/lib/graphiti/sideload/many_to_many.rb +24 -0
  80. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +108 -0
  81. data/lib/graphiti/stats/dsl.rb +89 -0
  82. data/lib/graphiti/stats/payload.rb +49 -0
  83. data/lib/graphiti/types.rb +172 -0
  84. data/lib/graphiti/util/attribute_check.rb +88 -0
  85. data/lib/graphiti/util/field_params.rb +16 -0
  86. data/lib/graphiti/util/hash.rb +51 -0
  87. data/lib/graphiti/util/hooks.rb +33 -0
  88. data/lib/graphiti/util/include_params.rb +39 -0
  89. data/lib/graphiti/util/persistence.rb +219 -0
  90. data/lib/graphiti/util/relationship_payload.rb +64 -0
  91. data/lib/graphiti/util/serializer_attributes.rb +97 -0
  92. data/lib/graphiti/util/sideload.rb +33 -0
  93. data/lib/graphiti/util/validation_response.rb +78 -0
  94. data/lib/graphiti/version.rb +3 -0
  95. 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