graphiti-rb 1.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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