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.
- checksums.yaml +5 -5
- data/.ruby-version +1 -1
- data/.travis.yml +1 -2
- data/Rakefile +7 -3
- data/jsonapi_compliable.gemspec +7 -3
- data/lib/generators/jsonapi/resource_generator.rb +8 -79
- data/lib/generators/jsonapi/templates/application_resource.rb.erb +2 -1
- data/lib/generators/jsonapi/templates/controller.rb.erb +19 -64
- data/lib/generators/jsonapi/templates/resource.rb.erb +5 -47
- data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
- data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
- data/lib/jsonapi_compliable.rb +87 -18
- data/lib/jsonapi_compliable/adapters/abstract.rb +202 -45
- data/lib/jsonapi_compliable/adapters/active_record.rb +6 -130
- data/lib/jsonapi_compliable/adapters/active_record/base.rb +247 -0
- data/lib/jsonapi_compliable/adapters/active_record/belongs_to_sideload.rb +17 -0
- data/lib/jsonapi_compliable/adapters/active_record/has_many_sideload.rb +17 -0
- data/lib/jsonapi_compliable/adapters/active_record/has_one_sideload.rb +17 -0
- data/lib/jsonapi_compliable/adapters/active_record/inferrence.rb +12 -0
- data/lib/jsonapi_compliable/adapters/active_record/many_to_many_sideload.rb +30 -0
- data/lib/jsonapi_compliable/adapters/null.rb +177 -6
- data/lib/jsonapi_compliable/base.rb +33 -320
- data/lib/jsonapi_compliable/context.rb +16 -0
- data/lib/jsonapi_compliable/deserializer.rb +14 -39
- data/lib/jsonapi_compliable/errors.rb +227 -24
- data/lib/jsonapi_compliable/extensions/extra_attribute.rb +3 -1
- data/lib/jsonapi_compliable/filter_operators.rb +25 -0
- data/lib/jsonapi_compliable/hash_renderer.rb +57 -0
- data/lib/jsonapi_compliable/query.rb +190 -202
- data/lib/jsonapi_compliable/rails.rb +12 -6
- data/lib/jsonapi_compliable/railtie.rb +64 -0
- data/lib/jsonapi_compliable/renderer.rb +60 -0
- data/lib/jsonapi_compliable/resource.rb +35 -663
- data/lib/jsonapi_compliable/resource/configuration.rb +239 -0
- data/lib/jsonapi_compliable/resource/dsl.rb +138 -0
- data/lib/jsonapi_compliable/resource/interface.rb +32 -0
- data/lib/jsonapi_compliable/resource/polymorphism.rb +68 -0
- data/lib/jsonapi_compliable/resource/sideloading.rb +102 -0
- data/lib/jsonapi_compliable/resource_proxy.rb +127 -0
- data/lib/jsonapi_compliable/responders.rb +19 -0
- data/lib/jsonapi_compliable/runner.rb +25 -0
- data/lib/jsonapi_compliable/scope.rb +37 -79
- data/lib/jsonapi_compliable/scoping/extra_attributes.rb +29 -0
- data/lib/jsonapi_compliable/scoping/filter.rb +39 -58
- data/lib/jsonapi_compliable/scoping/filterable.rb +9 -14
- data/lib/jsonapi_compliable/scoping/paginate.rb +9 -3
- data/lib/jsonapi_compliable/scoping/sort.rb +16 -4
- data/lib/jsonapi_compliable/sideload.rb +221 -347
- data/lib/jsonapi_compliable/sideload/belongs_to.rb +34 -0
- data/lib/jsonapi_compliable/sideload/has_many.rb +16 -0
- data/lib/jsonapi_compliable/sideload/has_one.rb +9 -0
- data/lib/jsonapi_compliable/sideload/many_to_many.rb +24 -0
- data/lib/jsonapi_compliable/sideload/polymorphic_belongs_to.rb +108 -0
- data/lib/jsonapi_compliable/stats/payload.rb +4 -8
- data/lib/jsonapi_compliable/types.rb +172 -0
- data/lib/jsonapi_compliable/util/attribute_check.rb +88 -0
- data/lib/jsonapi_compliable/util/persistence.rb +29 -7
- data/lib/jsonapi_compliable/util/relationship_payload.rb +4 -4
- data/lib/jsonapi_compliable/util/render_options.rb +4 -32
- data/lib/jsonapi_compliable/util/serializer_attributes.rb +98 -0
- data/lib/jsonapi_compliable/util/validation_response.rb +15 -9
- data/lib/jsonapi_compliable/version.rb +1 -1
- metadata +105 -24
- data/lib/generators/jsonapi/field_generator.rb +0 -0
- data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +0 -29
- data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +0 -20
- data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +0 -22
- data/lib/generators/jsonapi/templates/payload.rb.erb +0 -39
- data/lib/generators/jsonapi/templates/serializer.rb.erb +0 -25
- data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +0 -19
- data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +0 -33
- data/lib/jsonapi_compliable/adapters/active_record_sideloading.rb +0 -152
- 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
|