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
@@ -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