jsonapi_compliable 0.11.34 → 1.0.alpha.2

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