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
@@ -0,0 +1,239 @@
1
+ module JsonapiCompliable
2
+ class Resource
3
+ module Configuration
4
+ extend ActiveSupport::Concern
5
+
6
+ module Overrides
7
+ def serializer=(val)
8
+ if val
9
+ if super(Class.new(val))
10
+ apply_attributes_to_serializer
11
+ apply_sideloads_to_serializer
12
+ end
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def polymorphic=(klasses)
19
+ super
20
+ send(:prepend, Polymorphism)
21
+ end
22
+
23
+ def type=(val)
24
+ if val = super
25
+ self.serializer.type(val)
26
+ end
27
+ end
28
+
29
+ def model
30
+ klass = super
31
+ unless klass || abstract_class?
32
+ if klass = infer_model
33
+ self.model = klass
34
+ else
35
+ raise Errors::ModelNotFound.new(self)
36
+ end
37
+ end
38
+ klass
39
+ end
40
+ end
41
+
42
+ included do
43
+ class << self
44
+ attr_writer :config
45
+ end
46
+
47
+ class_attribute :adapter,
48
+ :model,
49
+ :type,
50
+ :polymorphic,
51
+ :polymorphic_child,
52
+ :serializer,
53
+ :default_page_size,
54
+ :default_sort,
55
+ :attributes_readable_by_default,
56
+ :attributes_writable_by_default,
57
+ :attributes_sortable_by_default,
58
+ :attributes_filterable_by_default,
59
+ :relationships_readable_by_default,
60
+ :relationships_writable_by_default
61
+
62
+ class << self
63
+ prepend Overrides
64
+ end
65
+
66
+ def self.inherited(klass)
67
+ super
68
+ klass.config = Util::Hash.deep_dup(config)
69
+ klass.adapter ||= Adapters::Abstract.new
70
+ klass.default_sort ||= []
71
+ klass.default_page_size ||= 20
72
+ # re-assigning causes a new Class.new
73
+ if klass.serializer
74
+ klass.serializer = klass.serializer
75
+ else
76
+ klass.serializer = JSONAPI::Serializable::Resource
77
+ end
78
+ klass.type ||= klass.infer_type
79
+ default(klass, :attributes_readable_by_default, true)
80
+ default(klass, :attributes_writable_by_default, true)
81
+ default(klass, :attributes_sortable_by_default, true)
82
+ default(klass, :attributes_filterable_by_default, true)
83
+ default(klass, :relationships_readable_by_default, true)
84
+ default(klass, :relationships_writable_by_default, true)
85
+
86
+ unless klass.config[:attributes][:id]
87
+ klass.attribute :id, :integer_id
88
+ end
89
+ klass.stat total: [:count]
90
+ end
91
+ end
92
+
93
+ class_methods do
94
+ def get_attr!(name, flag, opts = {})
95
+ opts[:raise_error] = true
96
+ get_attr(name, flag, opts)
97
+ end
98
+
99
+ def get_attr(name, flag, opts = {})
100
+ defaults = { request: false }
101
+ opts = defaults.merge(opts)
102
+ new.get_attr(name, flag, opts)
103
+ end
104
+
105
+ def abstract_class?
106
+ !!abstract_class
107
+ end
108
+
109
+ def abstract_class
110
+ @abstract_class
111
+ end
112
+
113
+ def abstract_class=(val)
114
+ if @abstract_class = val
115
+ self.serializer = nil
116
+ self.type = nil
117
+ end
118
+ end
119
+
120
+ def infer_type
121
+ if name.present?
122
+ name.demodulize.gsub('Resource','').underscore.pluralize.to_sym
123
+ else
124
+ :undefined_jsonapi_type
125
+ end
126
+ end
127
+
128
+ def infer_model
129
+ name.gsub('Resource', '').safe_constantize if name
130
+ end
131
+
132
+ def default(object, attr, value)
133
+ prior = object.send(attr)
134
+ unless prior || prior == false
135
+ object.send(:"#{attr}=", value)
136
+ end
137
+ end
138
+ private :default
139
+
140
+ def config
141
+ @config ||=
142
+ {
143
+ filters: {},
144
+ default_filters: {},
145
+ stats: {},
146
+ sort_all: nil,
147
+ sorts: {},
148
+ pagination: nil,
149
+ before_commit: {},
150
+ attributes: {},
151
+ extra_attributes: {},
152
+ sideloads: {}
153
+ }
154
+ end
155
+
156
+ def attributes
157
+ config[:attributes]
158
+ end
159
+
160
+ def extra_attributes
161
+ config[:extra_attributes]
162
+ end
163
+
164
+ def all_attributes
165
+ attributes.merge(extra_attributes)
166
+ end
167
+
168
+ def sideloads
169
+ config[:sideloads]
170
+ end
171
+
172
+ def filters
173
+ config[:filters]
174
+ end
175
+
176
+ def sorts
177
+ config[:sorts]
178
+ end
179
+
180
+ def stats
181
+ config[:stats]
182
+ end
183
+
184
+ def pagination
185
+ config[:pagination]
186
+ end
187
+
188
+ def default_filters
189
+ config[:default_filters]
190
+ end
191
+ end
192
+
193
+ def get_attr!(name, flag, options = {})
194
+ options[:raise_error] = true
195
+ get_attr(name, flag, options)
196
+ end
197
+
198
+ def get_attr(name, flag, request: false, raise_error: false)
199
+ Util::AttributeCheck.run(self, name, flag, request, raise_error)
200
+ end
201
+
202
+ def filters
203
+ self.class.filters
204
+ end
205
+
206
+ def sort_all
207
+ self.class.sort_all
208
+ end
209
+
210
+ def sorts
211
+ self.class.sorts
212
+ end
213
+
214
+ def stats
215
+ self.class.stats
216
+ end
217
+
218
+ def pagination
219
+ self.class.pagination
220
+ end
221
+
222
+ def attributes
223
+ self.class.attributes
224
+ end
225
+
226
+ def extra_attributes
227
+ self.class.extra_attributes
228
+ end
229
+
230
+ def all_attributes
231
+ self.class.all_attributes
232
+ end
233
+
234
+ def default_filters
235
+ self.class.default_filters
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,138 @@
1
+ module JsonapiCompliable
2
+ class Resource
3
+ module DSL
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def filter(name, *args, &blk)
8
+ opts = args.extract_options!
9
+
10
+ if att = get_attr(name, :filterable, raise_error: :only_unsupported)
11
+ aliases = [name, opts[:aliases]].flatten.compact
12
+ operators = FilterOperators.build(&blk)
13
+ config[:filters][name.to_sym] = {
14
+ aliases: aliases,
15
+ type: att[:type]
16
+ }.merge(operators.to_hash)
17
+ else
18
+ if type = args[0]
19
+ attribute name, type, only: [:filterable]
20
+ filter(name, opts, &blk)
21
+ else
22
+ raise Errors::ImplicitFilterTypeMissing.new(self, name)
23
+ end
24
+ end
25
+ end
26
+
27
+ def sort_all(&blk)
28
+ if block_given?
29
+ config[:_sort_all] = blk
30
+ else
31
+ config[:_sort_all]
32
+ end
33
+ end
34
+
35
+ def sort(name, *args, &blk)
36
+ opts = args.extract_options!
37
+
38
+ if get_attr(name, :sortable, raise_error: :only_unsupported)
39
+ config[:sorts][name] = blk
40
+ else
41
+ if type = args[0]
42
+ attribute name, type, only: [:sortable]
43
+ sort(name, opts, &blk)
44
+ else
45
+ raise Errors::ImplicitSortTypeMissing.new(self, name)
46
+ end
47
+ end
48
+ end
49
+
50
+ def paginate(&blk)
51
+ config[:pagination] = blk
52
+ end
53
+
54
+ def stat(symbol_or_hash, &blk)
55
+ dsl = Stats::DSL.new(adapter, symbol_or_hash)
56
+ dsl.instance_eval(&blk) if blk
57
+ config[:stats][dsl.name] = dsl
58
+ end
59
+
60
+ def default_filter(name = nil, &blk)
61
+ name ||= :__default
62
+ config[:default_filters][name.to_sym] = {
63
+ filter: blk
64
+ }
65
+ end
66
+
67
+ def before_commit(only: [:create, :update, :destroy], &blk)
68
+ Array(only).each do |verb|
69
+ config[:before_commit][verb] = blk
70
+ end
71
+ end
72
+
73
+ def attribute(name, type, options = {}, &blk)
74
+ raise Errors::TypeNotFound.new(self, name, type) unless Types[type]
75
+ attribute_option(options, :readable)
76
+ attribute_option(options, :writable)
77
+ attribute_option(options, :sortable)
78
+ attribute_option(options, :filterable)
79
+ options[:type] = type
80
+ options[:proc] = blk
81
+ config[:attributes][name] = options
82
+ apply_attributes_to_serializer
83
+ filter(name) if options[:filterable]
84
+ end
85
+
86
+ def extra_attribute(name, type, options = {}, &blk)
87
+ raise Errors::TypeNotFound.new(self, name, type) unless Types[type]
88
+ defaults = {
89
+ type: type,
90
+ proc: blk,
91
+ readable: true,
92
+ writable: false,
93
+ sortable: false,
94
+ filterable: false
95
+ }
96
+ options = defaults.merge(options)
97
+ config[:extra_attributes][name] = options
98
+ apply_extra_attributes_to_serializer
99
+ end
100
+
101
+ def all_attributes
102
+ attributes.merge(extra_attributes)
103
+ end
104
+
105
+ def apply_attributes_to_serializer
106
+ serializer.type(type)
107
+ Util::SerializerAttributes.new(self, attributes).apply
108
+ end
109
+ private :apply_attributes_to_serializer
110
+
111
+ def apply_extra_attributes_to_serializer
112
+ Util::SerializerAttributes.new(self, extra_attributes, true).apply
113
+ end
114
+
115
+ def attribute_option(options, name)
116
+ if options[name] != false
117
+ default = if only = options[:only]
118
+ Array(only).include?(name) ? true : false
119
+ elsif except = options[:except]
120
+ Array(except).include?(name) ? false : true
121
+ else
122
+ send(:"attributes_#{name}_by_default")
123
+ end
124
+ options[name] ||= default
125
+ end
126
+ end
127
+ private :attribute_option
128
+
129
+ def relationship_option(options, name)
130
+ if options[name] != false
131
+ options[name] ||= send(:"relationships_#{name}_by_default")
132
+ end
133
+ end
134
+ private :attribute_option
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,32 @@
1
+ module JsonapiCompliable
2
+ class Resource
3
+ module Interface
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def all(params = {}, base_scope = nil)
8
+ _all(params, {}, base_scope)
9
+ end
10
+
11
+ def _all(params, opts, base_scope)
12
+ runner = Runner.new(self, params)
13
+ runner.proxy(base_scope, opts)
14
+ end
15
+
16
+ def find(params, base_scope = nil)
17
+ id = params[:data].try(:[], :id) || params.delete(:id)
18
+ params[:filter] ||= {}
19
+ params[:filter].merge!(id: id)
20
+
21
+ runner = Runner.new(self, params)
22
+ runner.proxy(base_scope, single: true, raise_on_missing: true)
23
+ end
24
+
25
+ def build(params, base_scope = nil)
26
+ runner = Runner.new(self, params)
27
+ runner.proxy(base_scope, single: true, raise_on_missing: true)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,68 @@
1
+ # For "Rails STI" behavior
2
+ # CreditCard.all # => [<Visa>, <Mastercard>, etc]
3
+ module JsonapiCompliable
4
+ class Resource
5
+ module Polymorphism
6
+ def self.prepended(klass)
7
+ klass.extend ClassMethods
8
+ end
9
+
10
+ def serializer_for(model)
11
+ if polymorphic_child?
12
+ serializer
13
+ else
14
+ child = self.class.resource_for_model(model)
15
+ child.serializer
16
+ end
17
+ end
18
+
19
+ def associate_all(parent, children, association_name, type)
20
+ children.each do |c|
21
+ associate(parent, c, association_name, type)
22
+ end
23
+ end
24
+
25
+ def associate(parent, child, association_name, type)
26
+ child_resource = self.class.resource_for_model(parent)
27
+ if child_resource.sideloads[association_name]
28
+ child_resource.adapter
29
+ .associate(parent, child, association_name, type)
30
+ end
31
+ end
32
+
33
+ module ClassMethods
34
+ def inherited(klass)
35
+ klass.type = nil
36
+ klass.model = klass.infer_model
37
+ klass.polymorphic_child = true
38
+ super
39
+ end
40
+
41
+ def sideload(name)
42
+ sl = super
43
+ if !polymorphic_child? && sl.nil?
44
+ children.each do |c|
45
+ break if sl = c.sideloads[name]
46
+ end
47
+ end
48
+ sl
49
+ end
50
+
51
+ def children
52
+ @children ||= polymorphic.map do |klass|
53
+ klass.is_a?(String) ? klass.safe_constantize : klass
54
+ end
55
+ end
56
+
57
+ def resource_for_model(model)
58
+ resource = children.find { |c| model.is_a?(c.model) }
59
+ if resource.nil?
60
+ raise Errors::PolymorphicChildNotFound.new(self, model)
61
+ else
62
+ resource
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end