jsonapi_compliable 0.11.34 → 1.0.alpha.2

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 (73) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -2
  4. data/Rakefile +7 -3
  5. data/jsonapi_compliable.gemspec +7 -3
  6. data/lib/generators/jsonapi/resource_generator.rb +8 -79
  7. data/lib/generators/jsonapi/templates/application_resource.rb.erb +2 -1
  8. data/lib/generators/jsonapi/templates/controller.rb.erb +19 -64
  9. data/lib/generators/jsonapi/templates/resource.rb.erb +5 -47
  10. data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
  11. data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
  12. data/lib/jsonapi_compliable.rb +87 -18
  13. data/lib/jsonapi_compliable/adapters/abstract.rb +202 -45
  14. data/lib/jsonapi_compliable/adapters/active_record.rb +6 -130
  15. data/lib/jsonapi_compliable/adapters/active_record/base.rb +247 -0
  16. data/lib/jsonapi_compliable/adapters/active_record/belongs_to_sideload.rb +17 -0
  17. data/lib/jsonapi_compliable/adapters/active_record/has_many_sideload.rb +17 -0
  18. data/lib/jsonapi_compliable/adapters/active_record/has_one_sideload.rb +17 -0
  19. data/lib/jsonapi_compliable/adapters/active_record/inferrence.rb +12 -0
  20. data/lib/jsonapi_compliable/adapters/active_record/many_to_many_sideload.rb +30 -0
  21. data/lib/jsonapi_compliable/adapters/null.rb +177 -6
  22. data/lib/jsonapi_compliable/base.rb +33 -320
  23. data/lib/jsonapi_compliable/context.rb +16 -0
  24. data/lib/jsonapi_compliable/deserializer.rb +14 -39
  25. data/lib/jsonapi_compliable/errors.rb +227 -24
  26. data/lib/jsonapi_compliable/extensions/extra_attribute.rb +3 -1
  27. data/lib/jsonapi_compliable/filter_operators.rb +25 -0
  28. data/lib/jsonapi_compliable/hash_renderer.rb +57 -0
  29. data/lib/jsonapi_compliable/query.rb +190 -202
  30. data/lib/jsonapi_compliable/rails.rb +12 -6
  31. data/lib/jsonapi_compliable/railtie.rb +64 -0
  32. data/lib/jsonapi_compliable/renderer.rb +60 -0
  33. data/lib/jsonapi_compliable/resource.rb +35 -663
  34. data/lib/jsonapi_compliable/resource/configuration.rb +239 -0
  35. data/lib/jsonapi_compliable/resource/dsl.rb +138 -0
  36. data/lib/jsonapi_compliable/resource/interface.rb +32 -0
  37. data/lib/jsonapi_compliable/resource/polymorphism.rb +68 -0
  38. data/lib/jsonapi_compliable/resource/sideloading.rb +102 -0
  39. data/lib/jsonapi_compliable/resource_proxy.rb +127 -0
  40. data/lib/jsonapi_compliable/responders.rb +19 -0
  41. data/lib/jsonapi_compliable/runner.rb +25 -0
  42. data/lib/jsonapi_compliable/scope.rb +37 -79
  43. data/lib/jsonapi_compliable/scoping/extra_attributes.rb +29 -0
  44. data/lib/jsonapi_compliable/scoping/filter.rb +39 -58
  45. data/lib/jsonapi_compliable/scoping/filterable.rb +9 -14
  46. data/lib/jsonapi_compliable/scoping/paginate.rb +9 -3
  47. data/lib/jsonapi_compliable/scoping/sort.rb +16 -4
  48. data/lib/jsonapi_compliable/sideload.rb +221 -347
  49. data/lib/jsonapi_compliable/sideload/belongs_to.rb +34 -0
  50. data/lib/jsonapi_compliable/sideload/has_many.rb +16 -0
  51. data/lib/jsonapi_compliable/sideload/has_one.rb +9 -0
  52. data/lib/jsonapi_compliable/sideload/many_to_many.rb +24 -0
  53. data/lib/jsonapi_compliable/sideload/polymorphic_belongs_to.rb +108 -0
  54. data/lib/jsonapi_compliable/stats/payload.rb +4 -8
  55. data/lib/jsonapi_compliable/types.rb +172 -0
  56. data/lib/jsonapi_compliable/util/attribute_check.rb +88 -0
  57. data/lib/jsonapi_compliable/util/persistence.rb +29 -7
  58. data/lib/jsonapi_compliable/util/relationship_payload.rb +4 -4
  59. data/lib/jsonapi_compliable/util/render_options.rb +4 -32
  60. data/lib/jsonapi_compliable/util/serializer_attributes.rb +98 -0
  61. data/lib/jsonapi_compliable/util/validation_response.rb +15 -9
  62. data/lib/jsonapi_compliable/version.rb +1 -1
  63. metadata +105 -24
  64. data/lib/generators/jsonapi/field_generator.rb +0 -0
  65. data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +0 -29
  66. data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +0 -20
  67. data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +0 -22
  68. data/lib/generators/jsonapi/templates/payload.rb.erb +0 -39
  69. data/lib/generators/jsonapi/templates/serializer.rb.erb +0 -25
  70. data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +0 -19
  71. data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +0 -33
  72. data/lib/jsonapi_compliable/adapters/active_record_sideloading.rb +0 -152
  73. data/lib/jsonapi_compliable/scoping/extra_fields.rb +0 -58
@@ -1,130 +1,6 @@
1
- require 'jsonapi_compliable/adapters/active_record_sideloading'
2
-
3
- module JsonapiCompliable
4
- module Adapters
5
- # @see Adapters::Abstract
6
- class ActiveRecord < Abstract
7
- # (see Adapters::Abstract#filter)
8
- def filter(scope, attribute, value)
9
- scope.where(attribute => value)
10
- end
11
-
12
- # (see Adapters::Abstract#order)
13
- def order(scope, attribute, direction)
14
- scope.order(attribute => direction)
15
- end
16
-
17
- # (see Adapters::Abstract#paginate)
18
- def paginate(scope, current_page, per_page)
19
- scope.page(current_page).per(per_page)
20
- end
21
-
22
- # (see Adapters::Abstract#count)
23
- def count(scope, attr)
24
- if attr.to_sym == :total
25
- scope.distinct.count
26
- else
27
- scope.distinct.count(attr)
28
- end
29
- end
30
-
31
- # (see Adapters::Abstract#average)
32
- def average(scope, attr)
33
- scope.average(attr).to_f
34
- end
35
-
36
- # (see Adapters::Abstract#sum)
37
- def sum(scope, attr)
38
- scope.sum(attr)
39
- end
40
-
41
- # (see Adapters::Abstract#maximum)
42
- def maximum(scope, attr)
43
- scope.maximum(attr)
44
- end
45
-
46
- # (see Adapters::Abstract#minimum)
47
- def minimum(scope, attr)
48
- scope.minimum(attr)
49
- end
50
-
51
- # (see Adapters::Abstract#resolve)
52
- def resolve(scope)
53
- scope.to_a
54
- end
55
-
56
- # Run this write request within an ActiveRecord transaction
57
- # @param [Class] model_class The ActiveRecord class we are saving
58
- # @return Result of yield
59
- # @see Adapters::Abstract#transaction
60
- def transaction(model_class)
61
- model_class.transaction do
62
- yield
63
- end
64
- end
65
-
66
- # (see Adapters::Abstract#sideloading_module)
67
- def sideloading_module
68
- JsonapiCompliable::Adapters::ActiveRecordSideloading
69
- end
70
-
71
- # When a has_many relationship, we need to avoid Activerecord implicitly
72
- # firing a query. Otherwise, simple assignment will do
73
- # @see Adapters::Abstract#associate
74
- def associate(parent, child, association_name, association_type)
75
- if association_type == :has_many
76
- associate_many(parent, child, association_name)
77
- elsif association_type == :habtm
78
- if parent.send(association_name).exists?(child.id)
79
- associate_many(parent, child, association_name)
80
- else
81
- parent.send(association_name) << child
82
- end
83
- elsif association_type == :has_one
84
- parent.send("#{association_name}=", child)
85
- elsif
86
- child.send("#{association_name}=", parent)
87
- end
88
- end
89
-
90
- # When a has_and_belongs_to_many relationship, we don't have a foreign
91
- # key that can be null'd. Instead, go through the ActiveRecord API.
92
- # @see Adapters::Abstract#disassociate
93
- def disassociate(parent, child, association_name, association_type)
94
- if association_type == :habtm
95
- parent.send(association_name).delete(child)
96
- else
97
- # Nothing to do here, happened when we merged foreign key
98
- end
99
- end
100
-
101
- # (see Adapters::Abstract#create)
102
- def create(model_class, create_params)
103
- instance = model_class.new(create_params)
104
- instance.save
105
- instance
106
- end
107
-
108
- # (see Adapters::Abstract#update)
109
- def update(model_class, update_params)
110
- instance = model_class.find(update_params.delete(:id))
111
- instance.update_attributes(update_params)
112
- instance
113
- end
114
-
115
- # (see Adapters::Abstract#destroy)
116
- def destroy(model_class, id)
117
- instance = model_class.find(id)
118
- instance.destroy
119
- instance
120
- end
121
-
122
- private
123
-
124
- def associate_many(parent, child, association_name)
125
- parent.association(association_name).loaded!
126
- parent.association(association_name).add_to_target(child, :skip_callbacks)
127
- end
128
- end
129
- end
130
- end
1
+ require 'jsonapi_compliable/adapters/active_record/base'
2
+ require 'jsonapi_compliable/adapters/active_record/inferrence'
3
+ require 'jsonapi_compliable/adapters/active_record/has_many_sideload'
4
+ require 'jsonapi_compliable/adapters/active_record/belongs_to_sideload'
5
+ require 'jsonapi_compliable/adapters/active_record/has_one_sideload'
6
+ require 'jsonapi_compliable/adapters/active_record/many_to_many_sideload'
@@ -0,0 +1,247 @@
1
+ module JsonapiCompliable
2
+ module Adapters
3
+ module ActiveRecord
4
+ class Base < ::JsonapiCompliable::Adapters::Abstract
5
+ def filter_string_eq(scope, attribute, value, is_not: false)
6
+ column = scope.klass.arel_table[attribute]
7
+ clause = column.lower.eq_any(value.map(&:downcase))
8
+ is_not ? scope.where.not(clause) : scope.where(clause)
9
+ end
10
+
11
+ def filter_string_eql(scope, attribute, value, is_not: false)
12
+ clause = { attribute => value }
13
+ is_not ? scope.where.not(clause) : scope.where(clause)
14
+ end
15
+
16
+ def filter_string_not_eq(scope, attribute, value)
17
+ filter_string_eq(scope, attribute, value, is_not: true)
18
+ end
19
+
20
+ def filter_string_not_eql(scope, attribute, value)
21
+ filter_string_eql(scope, attribute, value, is_not: true)
22
+ end
23
+
24
+ def filter_string_prefix(scope, attribute, value, is_not: false)
25
+ column = scope.klass.arel_table[attribute]
26
+ map = value.map { |v| "#{v}%" }
27
+ clause = column.lower.matches_any(map)
28
+ is_not ? scope.where.not(clause) : scope.where(clause)
29
+ end
30
+
31
+ def filter_string_not_prefix(scope, attribute, value)
32
+ filter_string_prefix(scope, attribute, value, is_not: true)
33
+ end
34
+
35
+ def filter_string_suffix(scope, attribute, value, is_not: false)
36
+ column = scope.klass.arel_table[attribute]
37
+ map = value.map { |v| "%#{v}" }
38
+ clause = column.lower.matches_any(map)
39
+ is_not ? scope.where.not(clause) : scope.where(clause)
40
+ end
41
+
42
+ def filter_string_not_suffix(scope, attribute, value)
43
+ filter_string_suffix(scope, attribute, value, is_not: true)
44
+ end
45
+
46
+ def filter_string_like(scope, attribute, value, is_not: false)
47
+ column = scope.klass.arel_table[attribute]
48
+ map = value.map { |v| "%#{v.downcase}%" }
49
+ clause = column.lower.matches_any(map)
50
+ is_not ? scope.where.not(clause) : scope.where(clause)
51
+ end
52
+
53
+ def filter_string_not_like(scope, attribute, value)
54
+ filter_string_like(scope, attribute, value, is_not: true)
55
+ end
56
+
57
+ def filter_integer_eq(scope, attribute, value, is_not: false)
58
+ clause = { attribute => value }
59
+ is_not ? scope.where.not(clause) : scope.where(clause)
60
+ end
61
+ alias :filter_float_eq :filter_integer_eq
62
+ alias :filter_decimal_eq :filter_integer_eq
63
+ alias :filter_date_eq :filter_integer_eq
64
+ alias :filter_boolean_eq :filter_integer_eq
65
+
66
+ def filter_integer_not_eq(scope, attribute, value)
67
+ filter_integer_eq(scope, attribute, value, is_not: true)
68
+ end
69
+ alias :filter_float_not_eq :filter_integer_not_eq
70
+ alias :filter_decimal_not_eq :filter_integer_not_eq
71
+ alias :filter_date_not_eq :filter_integer_not_eq
72
+
73
+ def filter_integer_gt(scope, attribute, value)
74
+ column = scope.klass.arel_table[attribute]
75
+ scope.where(column.gt_any(value))
76
+ end
77
+ alias :filter_float_gt :filter_integer_gt
78
+ alias :filter_decimal_gt :filter_integer_gt
79
+ alias :filter_datetime_gt :filter_integer_gt
80
+ alias :filter_date_gt :filter_integer_gt
81
+
82
+ def filter_integer_gte(scope, attribute, value)
83
+ column = scope.klass.arel_table[attribute]
84
+ scope.where(column.gteq_any(value))
85
+ end
86
+ alias :filter_float_gte :filter_integer_gte
87
+ alias :filter_decimal_gte :filter_integer_gte
88
+ alias :filter_datetime_gte :filter_integer_gte
89
+ alias :filter_date_gte :filter_integer_gte
90
+
91
+ def filter_integer_lt(scope, attribute, value)
92
+ column = scope.klass.arel_table[attribute]
93
+ scope.where(column.lt_any(value))
94
+ end
95
+ alias :filter_float_lt :filter_integer_lt
96
+ alias :filter_decimal_lt :filter_integer_lt
97
+ alias :filter_datetime_lt :filter_integer_lt
98
+ alias :filter_date_lt :filter_integer_lt
99
+
100
+ def filter_integer_lte(scope, attribute, value)
101
+ column = scope.klass.arel_table[attribute]
102
+ scope.where(column.lteq_any(value))
103
+ end
104
+ alias :filter_float_lte :filter_integer_lte
105
+ alias :filter_decimal_lte :filter_integer_lte
106
+ alias :filter_date_lte :filter_integer_lte
107
+
108
+ # Ensure fractional seconds don't matter
109
+ def filter_datetime_eq(scope, attribute, value, is_not: false)
110
+ ranges = value.map { |v| (v..v+1.second-0.00000001) }
111
+ clause = { attribute => ranges }
112
+ is_not ? scope.where.not(clause) : scope.where(clause)
113
+ end
114
+
115
+ def filter_datetime_not_eq(scope, attribute, value)
116
+ filter_datetime_eq(scope, attribute, value, is_not: true)
117
+ end
118
+
119
+ def filter_datetime_lte(scope, attribute, value)
120
+ value = value.map { |v| v + 1.second-0.00000001 }
121
+ column = scope.klass.arel_table[attribute]
122
+ scope.where(column.lteq_any(value))
123
+ end
124
+
125
+ def base_scope(model)
126
+ model.all
127
+ end
128
+
129
+ # (see Adapters::Abstract#order)
130
+ def order(scope, attribute, direction)
131
+ scope.order(attribute => direction)
132
+ end
133
+
134
+ # (see Adapters::Abstract#paginate)
135
+ def paginate(scope, current_page, per_page)
136
+ scope.page(current_page).per(per_page)
137
+ end
138
+
139
+ # (see Adapters::Abstract#count)
140
+ def count(scope, attr)
141
+ if attr.to_sym == :total
142
+ scope.distinct.count
143
+ else
144
+ scope.distinct.count(attr)
145
+ end
146
+ end
147
+
148
+ # (see Adapters::Abstract#average)
149
+ def average(scope, attr)
150
+ scope.average(attr).to_f
151
+ end
152
+
153
+ # (see Adapters::Abstract#sum)
154
+ def sum(scope, attr)
155
+ scope.sum(attr)
156
+ end
157
+
158
+ # (see Adapters::Abstract#maximum)
159
+ def maximum(scope, attr)
160
+ scope.maximum(attr)
161
+ end
162
+
163
+ # (see Adapters::Abstract#minimum)
164
+ def minimum(scope, attr)
165
+ scope.minimum(attr)
166
+ end
167
+
168
+ # (see Adapters::Abstract#resolve)
169
+ def resolve(scope)
170
+ scope.to_a
171
+ end
172
+
173
+ # Run this write request within an ActiveRecord transaction
174
+ # @param [Class] model_class The ActiveRecord class we are saving
175
+ # @return Result of yield
176
+ # @see Adapters::Abstract#transaction
177
+ def transaction(model_class)
178
+ model_class.transaction do
179
+ yield
180
+ end
181
+ end
182
+
183
+ def sideloading_classes
184
+ {
185
+ has_many: HasManySideload,
186
+ has_one: HasOneSideload,
187
+ belongs_to: BelongsToSideload,
188
+ many_to_many: ManyToManySideload
189
+ }
190
+ end
191
+
192
+ def associate_all(parent, children, association_name, association_type)
193
+ association = parent.association(association_name)
194
+ association.loaded!
195
+
196
+ children.each do |child|
197
+ if association_type == :many_to_many &&
198
+ !parent.send(association_name).exists?(child.id) &&
199
+ [:create, :update].include?(JsonapiCompliable.context[:namespace])
200
+ parent.send(association_name) << child
201
+ else
202
+ association.target |= [child]
203
+ end
204
+ end
205
+ end
206
+
207
+ def associate(parent, child, association_name, association_type)
208
+ association = parent.association(association_name)
209
+ association.loaded!
210
+ association.target = child
211
+ end
212
+
213
+ # When a has_and_belongs_to_many relationship, we don't have a foreign
214
+ # key that can be null'd. Instead, go through the ActiveRecord API.
215
+ # @see Adapters::Abstract#disassociate
216
+ def disassociate(parent, child, association_name, association_type)
217
+ if association_type == :many_to_many
218
+ parent.send(association_name).delete(child)
219
+ else
220
+ # Nothing to do here, happened when we merged foreign key
221
+ end
222
+ end
223
+
224
+ # (see Adapters::Abstract#create)
225
+ def create(model_class, create_params)
226
+ instance = model_class.new(create_params)
227
+ instance.save
228
+ instance
229
+ end
230
+
231
+ # (see Adapters::Abstract#update)
232
+ def update(model_class, update_params)
233
+ instance = model_class.find(update_params.delete(:id))
234
+ instance.update_attributes(update_params)
235
+ instance
236
+ end
237
+
238
+ # (see Adapters::Abstract#destroy)
239
+ def destroy(model_class, id)
240
+ instance = model_class.find(id)
241
+ instance.destroy
242
+ instance
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,17 @@
1
+ module JsonapiCompliable
2
+ module Adapters
3
+ module ActiveRecord
4
+ class BelongsToSideload < Sideload::BelongsTo
5
+ include Inferrence
6
+
7
+ def default_base_scope
8
+ resource_class.model.all
9
+ end
10
+
11
+ def scope(parent_ids)
12
+ base_scope.where(primary_key => parent_ids)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module JsonapiCompliable
2
+ module Adapters
3
+ module ActiveRecord # todo change
4
+ class HasManySideload < Sideload::HasMany
5
+ include Inferrence
6
+
7
+ def default_base_scope
8
+ resource_class.model.all
9
+ end
10
+
11
+ def scope(parent_ids)
12
+ base_scope.where(foreign_key => parent_ids)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module JsonapiCompliable
2
+ module Adapters
3
+ module ActiveRecord
4
+ class HasOneSideload < Sideload::HasOne
5
+ include Inferrence
6
+
7
+ def default_base_scope
8
+ resource_class.model.all
9
+ end
10
+
11
+ def scope(parent_ids)
12
+ base_scope.where(foreign_key => parent_ids)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module JsonapiCompliable
2
+ module Adapters
3
+ module ActiveRecord
4
+ module Inferrence
5
+ def infer_foreign_key
6
+ parent_model = parent_resource_class.model
7
+ parent_model.reflections[association_name.to_s].foreign_key.to_sym
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end