jsonapi_compliable 0.6.4 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +11 -3
- data/.yardopts +1 -0
- data/README.md +10 -1
- data/Rakefile +1 -0
- data/docs/JsonapiCompliable.html +202 -0
- data/docs/JsonapiCompliable/Adapters.html +119 -0
- data/docs/JsonapiCompliable/Adapters/Abstract.html +2285 -0
- data/docs/JsonapiCompliable/Adapters/ActiveRecord.html +2151 -0
- data/docs/JsonapiCompliable/Adapters/ActiveRecordSideloading.html +582 -0
- data/docs/JsonapiCompliable/Adapters/Null.html +1682 -0
- data/docs/JsonapiCompliable/Base.html +1395 -0
- data/docs/JsonapiCompliable/Deserializer.html +835 -0
- data/docs/JsonapiCompliable/Errors.html +115 -0
- data/docs/JsonapiCompliable/Errors/BadFilter.html +124 -0
- data/docs/JsonapiCompliable/Errors/StatNotFound.html +266 -0
- data/docs/JsonapiCompliable/Errors/UnsupportedPageSize.html +264 -0
- data/docs/JsonapiCompliable/Errors/ValidationError.html +124 -0
- data/docs/JsonapiCompliable/Extensions.html +117 -0
- data/docs/JsonapiCompliable/Extensions/BooleanAttribute.html +212 -0
- data/docs/JsonapiCompliable/Extensions/BooleanAttribute/ClassMethods.html +229 -0
- data/docs/JsonapiCompliable/Extensions/ExtraAttribute.html +242 -0
- data/docs/JsonapiCompliable/Extensions/ExtraAttribute/ClassMethods.html +237 -0
- data/docs/JsonapiCompliable/Query.html +1099 -0
- data/docs/JsonapiCompliable/Rails.html +211 -0
- data/docs/JsonapiCompliable/Resource.html +5241 -0
- data/docs/JsonapiCompliable/Scope.html +703 -0
- data/docs/JsonapiCompliable/Scoping.html +117 -0
- data/docs/JsonapiCompliable/Scoping/Base.html +843 -0
- data/docs/JsonapiCompliable/Scoping/DefaultFilter.html +318 -0
- data/docs/JsonapiCompliable/Scoping/ExtraFields.html +301 -0
- data/docs/JsonapiCompliable/Scoping/Filter.html +313 -0
- data/docs/JsonapiCompliable/Scoping/Filterable.html +364 -0
- data/docs/JsonapiCompliable/Scoping/Paginate.html +613 -0
- data/docs/JsonapiCompliable/Scoping/Sort.html +454 -0
- data/docs/JsonapiCompliable/SerializableTempId.html +216 -0
- data/docs/JsonapiCompliable/Sideload.html +2484 -0
- data/docs/JsonapiCompliable/Stats.html +117 -0
- data/docs/JsonapiCompliable/Stats/DSL.html +999 -0
- data/docs/JsonapiCompliable/Stats/Payload.html +391 -0
- data/docs/JsonapiCompliable/Util.html +117 -0
- data/docs/JsonapiCompliable/Util/FieldParams.html +228 -0
- data/docs/JsonapiCompliable/Util/Hash.html +471 -0
- data/docs/JsonapiCompliable/Util/IncludeParams.html +299 -0
- data/docs/JsonapiCompliable/Util/Persistence.html +435 -0
- data/docs/JsonapiCompliable/Util/RelationshipPayload.html +563 -0
- data/docs/JsonapiCompliable/Util/RenderOptions.html +250 -0
- data/docs/JsonapiCompliable/Util/ValidationResponse.html +532 -0
- data/docs/_index.html +527 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +492 -0
- data/docs/file.README.html +99 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +99 -0
- data/docs/js/app.js +248 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +1731 -0
- data/docs/top-level-namespace.html +110 -0
- data/gemfiles/rails_5.gemfile +1 -1
- data/lib/jsonapi_compliable/adapters/abstract.rb +267 -4
- data/lib/jsonapi_compliable/adapters/active_record.rb +23 -1
- data/lib/jsonapi_compliable/adapters/null.rb +43 -3
- data/lib/jsonapi_compliable/base.rb +182 -33
- data/lib/jsonapi_compliable/deserializer.rb +90 -21
- data/lib/jsonapi_compliable/extensions/boolean_attribute.rb +12 -0
- data/lib/jsonapi_compliable/extensions/extra_attribute.rb +32 -0
- data/lib/jsonapi_compliable/extensions/temp_id.rb +11 -0
- data/lib/jsonapi_compliable/query.rb +94 -2
- data/lib/jsonapi_compliable/rails.rb +8 -0
- data/lib/jsonapi_compliable/resource.rb +548 -11
- data/lib/jsonapi_compliable/scope.rb +43 -1
- data/lib/jsonapi_compliable/scoping/base.rb +59 -8
- data/lib/jsonapi_compliable/scoping/default_filter.rb +33 -0
- data/lib/jsonapi_compliable/scoping/extra_fields.rb +33 -0
- data/lib/jsonapi_compliable/scoping/filter.rb +29 -2
- data/lib/jsonapi_compliable/scoping/filterable.rb +4 -0
- data/lib/jsonapi_compliable/scoping/paginate.rb +33 -0
- data/lib/jsonapi_compliable/scoping/sort.rb +18 -0
- data/lib/jsonapi_compliable/sideload.rb +229 -1
- data/lib/jsonapi_compliable/stats/dsl.rb +44 -0
- data/lib/jsonapi_compliable/stats/payload.rb +20 -0
- data/lib/jsonapi_compliable/util/field_params.rb +1 -0
- data/lib/jsonapi_compliable/util/hash.rb +18 -0
- data/lib/jsonapi_compliable/util/include_params.rb +22 -0
- data/lib/jsonapi_compliable/util/persistence.rb +13 -3
- data/lib/jsonapi_compliable/util/relationship_payload.rb +2 -0
- data/lib/jsonapi_compliable/util/render_options.rb +2 -0
- data/lib/jsonapi_compliable/util/validation_response.rb +16 -0
- data/lib/jsonapi_compliable/version.rb +1 -1
- metadata +60 -5
- data/gemfiles/rails_4.gemfile.lock +0 -208
- data/gemfiles/rails_5.gemfile.lock +0 -212
- data/lib/jsonapi_compliable/write.rb +0 -93
@@ -1,25 +1,65 @@
|
|
1
1
|
module JsonapiCompliable
|
2
2
|
module Adapters
|
3
|
+
# The Null adapter is a 'pass-through' adapter. It won't modify the scope.
|
4
|
+
# Useful when your customization does not support all possible
|
5
|
+
# configuration (e.g. the service you hit does not support sorting)
|
3
6
|
class Null < Abstract
|
7
|
+
# (see Adapters::Abstract#filter)
|
4
8
|
def filter(scope, attribute, value)
|
5
9
|
scope
|
6
10
|
end
|
7
11
|
|
12
|
+
# (see Adapters::Abstract#order)
|
8
13
|
def order(scope, attribute, direction)
|
9
14
|
scope
|
10
15
|
end
|
11
16
|
|
12
|
-
|
17
|
+
# (see Adapters::Abstract#paginate)
|
18
|
+
def paginate(scope, current_page, per_page)
|
13
19
|
scope
|
14
20
|
end
|
15
21
|
|
16
|
-
|
22
|
+
# (see Adapters::Abstract#count)
|
23
|
+
def count(scope, attr)
|
17
24
|
scope
|
18
25
|
end
|
19
26
|
|
20
|
-
|
27
|
+
# (see Adapters::Abstract#average)
|
28
|
+
def average(scope, attr)
|
29
|
+
scope
|
30
|
+
end
|
31
|
+
|
32
|
+
# (see Adapters::Abstract#sum)
|
33
|
+
def sum(scope, attr)
|
34
|
+
scope
|
35
|
+
end
|
36
|
+
|
37
|
+
# (see Adapters::Abstract#sum)
|
38
|
+
def maximum(scope, attr)
|
39
|
+
scope
|
40
|
+
end
|
41
|
+
|
42
|
+
# (see Adapters::Abstract#minimum)
|
43
|
+
def minimum(scope, attr)
|
44
|
+
scope
|
45
|
+
end
|
46
|
+
|
47
|
+
# Since this is a null adapter, just yield
|
48
|
+
# @see Adapters::ActiveRecord#transaction
|
49
|
+
# @return Result of yield
|
50
|
+
# @param [Class] model_class The class we're operating on
|
51
|
+
def transaction(model_class)
|
21
52
|
yield
|
22
53
|
end
|
54
|
+
|
55
|
+
# (see Adapters::Abstract#resolve)
|
56
|
+
def resolve(scope)
|
57
|
+
scope
|
58
|
+
end
|
59
|
+
|
60
|
+
# (see Adapters::Abstract#associate)
|
61
|
+
def associate(parent, child, association_name, association_type)
|
62
|
+
end
|
23
63
|
end
|
24
64
|
end
|
25
65
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module JsonapiCompliable
|
2
|
+
# Provides main interface to jsonapi_compliable
|
3
|
+
#
|
4
|
+
# This gets mixed in to a "context" class, such as a Rails controller.
|
2
5
|
module Base
|
3
6
|
extend ActiveSupport::Concern
|
4
7
|
|
5
|
-
MAX_PAGE_SIZE = 1_000
|
6
|
-
|
7
8
|
included do
|
8
9
|
class << self
|
9
10
|
attr_accessor :_jsonapi_compliable
|
@@ -15,22 +16,77 @@ module JsonapiCompliable
|
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
# @!classmethods
|
20
|
+
module ClassMethods
|
21
|
+
# Define your JSONAPI configuration
|
22
|
+
#
|
23
|
+
# @example Inline Resource
|
24
|
+
# # 'Quick and Dirty' solution that does not require a separate
|
25
|
+
# # Resource object
|
26
|
+
# class PostsController < ApplicationController
|
27
|
+
# jsonapi do
|
28
|
+
# type :posts
|
29
|
+
# use_adapter JsonapiCompliable::Adapters::ActiveRecord
|
30
|
+
#
|
31
|
+
# allow_filter :title
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @example Resource Class (preferred)
|
36
|
+
# # Make code reusable by encapsulating it in a Resource class
|
37
|
+
# class PostsController < ApplicationController
|
38
|
+
# jsonapi resource: PostResource
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# @see Resource
|
42
|
+
# @param resource [Resource] the Resource class associated to this endpoint
|
43
|
+
# @return [void]
|
44
|
+
def jsonapi(foo = 'bar', resource: nil, &blk)
|
45
|
+
if resource
|
46
|
+
self._jsonapi_compliable = resource
|
47
|
+
else
|
48
|
+
if !self._jsonapi_compliable
|
49
|
+
self._jsonapi_compliable = Class.new(JsonapiCompliable::Resource)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
self._jsonapi_compliable.class_eval(&blk) if blk
|
54
|
+
end
|
20
55
|
end
|
21
56
|
|
22
|
-
|
23
|
-
|
57
|
+
# Returns an instance of the associated Resource
|
58
|
+
#
|
59
|
+
# In other words, if you configured your controller as:
|
60
|
+
#
|
61
|
+
# jsonapi resource: MyResource
|
62
|
+
#
|
63
|
+
# This returns MyResource.new
|
64
|
+
#
|
65
|
+
# @return [Resource] the configured Resource for this controller
|
66
|
+
def resource
|
67
|
+
@resource ||= self.class._jsonapi_compliable.new
|
24
68
|
end
|
25
69
|
|
70
|
+
# Instantiates the relevant Query object
|
71
|
+
#
|
72
|
+
# @see Query
|
73
|
+
# @return [Query] the Query object for this resource/params
|
26
74
|
def query
|
27
75
|
@query ||= Query.new(resource, params)
|
28
76
|
end
|
29
77
|
|
78
|
+
# @see Query#to_hash
|
79
|
+
# @return [Hash] the normalized query hash for only the *current* resource
|
30
80
|
def query_hash
|
31
81
|
@query_hash ||= query.to_hash[resource.type]
|
32
82
|
end
|
33
83
|
|
84
|
+
# Tracks the current context so we can refer to it within any
|
85
|
+
# random object. Helpful for easy-access to things like the current
|
86
|
+
# user.
|
87
|
+
#
|
88
|
+
# @api private
|
89
|
+
# @yieldreturn Code to run within the current context
|
34
90
|
def wrap_context
|
35
91
|
if self.class._jsonapi_compliable
|
36
92
|
resource.with_context(self, action_name.to_sym) do
|
@@ -39,14 +95,57 @@ module JsonapiCompliable
|
|
39
95
|
end
|
40
96
|
end
|
41
97
|
|
98
|
+
# Use when direct, low-level access to the scope is required.
|
99
|
+
#
|
100
|
+
# @example Show Action
|
101
|
+
# # Scope#resolve returns an array, but we only want to render
|
102
|
+
# # one object, not an array
|
103
|
+
# scope = jsonapi_scope(Employee.where(id: params[:id]))
|
104
|
+
# render_jsonapi(scope.resolve.first, scope: false)
|
105
|
+
#
|
106
|
+
# @example Scope Chaining
|
107
|
+
# # Chain onto scope after running through typical DSL
|
108
|
+
# # Here, we'll add active: true to our hash if the user
|
109
|
+
# # is filtering on something
|
110
|
+
# scope = jsonapi_scope({})
|
111
|
+
# scope.object.merge!(active: true) if scope.object[:filter]
|
112
|
+
#
|
113
|
+
# @see Resource#build_scope
|
114
|
+
# @return [Scope] the configured scope
|
42
115
|
def jsonapi_scope(scope, opts = {})
|
43
116
|
resource.build_scope(scope, query, opts)
|
44
117
|
end
|
45
118
|
|
119
|
+
# @see Deserializer#initialize
|
120
|
+
# @return [Deserializer]
|
46
121
|
def deserialized_params
|
47
122
|
@deserialized_params ||= JsonapiCompliable::Deserializer.new(params, request.env)
|
48
123
|
end
|
49
124
|
|
125
|
+
# Create the resource model and process all nested relationships via the
|
126
|
+
# serialized parameters. Any error, including validation errors, will roll
|
127
|
+
# back the transaction.
|
128
|
+
#
|
129
|
+
# @example Basic Rails
|
130
|
+
# # Example Resource must have 'model'
|
131
|
+
# #
|
132
|
+
# # class PostResource < ApplicationResource
|
133
|
+
# # model Post
|
134
|
+
# # end
|
135
|
+
# def create
|
136
|
+
# post, success = jsonapi_create.to_a
|
137
|
+
#
|
138
|
+
# if success
|
139
|
+
# render_jsonapi(post, scope: false)
|
140
|
+
# else
|
141
|
+
# render_errors_for(post)
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# @see Resource.model
|
146
|
+
# @see #resource
|
147
|
+
# @see #deserialized_params
|
148
|
+
# @return [Util::ValidationResponse]
|
50
149
|
def jsonapi_create
|
51
150
|
_persist do
|
52
151
|
resource.persist_with_relationships \
|
@@ -56,6 +155,28 @@ module JsonapiCompliable
|
|
56
155
|
end
|
57
156
|
end
|
58
157
|
|
158
|
+
# Update the resource model and process all nested relationships via the
|
159
|
+
# serialized parameters. Any error, including validation errors, will roll
|
160
|
+
# back the transaction.
|
161
|
+
#
|
162
|
+
# @example Basic Rails
|
163
|
+
# # Example Resource must have 'model'
|
164
|
+
# #
|
165
|
+
# # class PostResource < ApplicationResource
|
166
|
+
# # model Post
|
167
|
+
# # end
|
168
|
+
# def update
|
169
|
+
# post, success = jsonapi_update.to_a
|
170
|
+
#
|
171
|
+
# if success
|
172
|
+
# render_jsonapi(post, scope: false)
|
173
|
+
# else
|
174
|
+
# render_errors_for(post)
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# @see #jsonapi_create
|
179
|
+
# @return [Util::ValidationResponse]
|
59
180
|
def jsonapi_update
|
60
181
|
_persist do
|
61
182
|
resource.persist_with_relationships \
|
@@ -65,21 +186,37 @@ module JsonapiCompliable
|
|
65
186
|
end
|
66
187
|
end
|
67
188
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
189
|
+
# Similar to +render :json+ or +render :jsonapi+
|
190
|
+
#
|
191
|
+
# By default, this will "build" the scope via +#jsonapi_scope+. To avoid
|
192
|
+
# this, pass +scope: false+
|
193
|
+
#
|
194
|
+
# This builds relevant options and sends them to
|
195
|
+
# +JSONAPI::Serializable::Renderer.render+from
|
196
|
+
# {http://jsonapi-rb.org jsonapi-rb}
|
197
|
+
#
|
198
|
+
# @example Build Scope by Default
|
199
|
+
# # Employee.all returns an ActiveRecord::Relation. No SQL is fired at this point.
|
200
|
+
# # We further 'chain' onto this scope, applying pagination, sorting,
|
201
|
+
# # filters, etc that the user has requested.
|
202
|
+
# def index
|
203
|
+
# employees = Employee.all
|
204
|
+
# render_jsonapi(employees)
|
205
|
+
# end
|
206
|
+
#
|
207
|
+
# @example Avoid Building Scope by Default
|
208
|
+
# # Maybe we already manually scoped, and don't want to fire the logic twice
|
209
|
+
# # This code is equivalent to the above example
|
210
|
+
# def index
|
211
|
+
# scope = jsonapi_scope(Employee.all)
|
212
|
+
# # ... do other things with the scope ...
|
213
|
+
# render_jsonapi(scope.resolve, scope: false)
|
214
|
+
# end
|
215
|
+
#
|
216
|
+
# @param scope [Scope, Object] the scope to build or render.
|
217
|
+
# @param [Hash] opts the render options passed to {http://jsonapi-rb.org jsonapi-rb}
|
218
|
+
# @option opts [Boolean] :scope Default: true. Should we call #jsonapi_scope on this object?
|
219
|
+
# @see #jsonapi_scope
|
83
220
|
def render_jsonapi(scope, opts = {})
|
84
221
|
scope = jsonapi_scope(scope) unless opts[:scope] == false || scope.is_a?(JsonapiCompliable::Scope)
|
85
222
|
opts = default_jsonapi_render_options.merge(opts)
|
@@ -89,29 +226,41 @@ module JsonapiCompliable
|
|
89
226
|
perform_render_jsonapi(opts)
|
90
227
|
end
|
91
228
|
|
92
|
-
#
|
93
|
-
#
|
229
|
+
# Define a hash that will be automatically merged into your
|
230
|
+
# render_jsonapi call
|
231
|
+
#
|
232
|
+
# @example
|
233
|
+
# # this
|
234
|
+
# render_jsonapi(foo)
|
235
|
+
# # is equivalent to this
|
236
|
+
# render jsonapi: foo, default_jsonapi_render_options
|
237
|
+
#
|
238
|
+
# @see #render_jsonapi
|
239
|
+
# @return [Hash] the options hash you define
|
94
240
|
def default_jsonapi_render_options
|
95
241
|
{}.tap do |options|
|
96
242
|
end
|
97
243
|
end
|
98
244
|
|
245
|
+
private
|
246
|
+
|
99
247
|
def force_includes?
|
100
248
|
not params[:data].nil?
|
101
249
|
end
|
102
250
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
self._jsonapi_compliable = resource
|
107
|
-
else
|
108
|
-
if !self._jsonapi_compliable
|
109
|
-
self._jsonapi_compliable = Class.new(JsonapiCompliable::Resource)
|
110
|
-
end
|
111
|
-
end
|
251
|
+
def perform_render_jsonapi(opts)
|
252
|
+
JSONAPI::Serializable::Renderer.render(opts.delete(:jsonapi), opts)
|
253
|
+
end
|
112
254
|
|
113
|
-
|
255
|
+
def _persist
|
256
|
+
validation_response = nil
|
257
|
+
resource.transaction do
|
258
|
+
object = yield
|
259
|
+
validation_response = Util::ValidationResponse.new \
|
260
|
+
object, deserialized_params
|
261
|
+
raise Errors::ValidationError unless validation_response.to_a[1]
|
114
262
|
end
|
263
|
+
validation_response
|
115
264
|
end
|
116
265
|
end
|
117
266
|
end
|
@@ -1,35 +1,81 @@
|
|
1
|
+
# Responsible for parsing incoming write payloads
|
2
|
+
#
|
3
|
+
# Given a PUT payload like:
|
4
|
+
#
|
5
|
+
# {
|
6
|
+
# data: {
|
7
|
+
# id: '1',
|
8
|
+
# type: 'posts',
|
9
|
+
# attributes: { title: 'My Title' },
|
10
|
+
# relationships: {
|
11
|
+
# author: {
|
12
|
+
# data: {
|
13
|
+
# id: '1',
|
14
|
+
# type: 'authors'
|
15
|
+
# }
|
16
|
+
# }
|
17
|
+
# }
|
18
|
+
# },
|
19
|
+
# included: [
|
20
|
+
# {
|
21
|
+
# id: '1'
|
22
|
+
# type: 'authors',
|
23
|
+
# attributes: { name: 'Joe Author' }
|
24
|
+
# }
|
25
|
+
# ]
|
26
|
+
# }
|
27
|
+
#
|
28
|
+
# You can now easily deal with this payload:
|
29
|
+
#
|
30
|
+
# deserializer.attributes
|
31
|
+
# # => { id: '1', title: 'My Title' }
|
32
|
+
# deserializer.meta
|
33
|
+
# # => { type: 'posts', method: :update }
|
34
|
+
# deserializer.relationships
|
35
|
+
# # {
|
36
|
+
# # author: {
|
37
|
+
# # meta: { ... },
|
38
|
+
# # attributes: { ... },
|
39
|
+
# # relationships: { ... }
|
40
|
+
# # }
|
41
|
+
# # }
|
42
|
+
#
|
43
|
+
# When creating objects, we accept a +temp-id+ so that the client can track
|
44
|
+
# the object it just created. Expect this in +meta+:
|
45
|
+
#
|
46
|
+
# { type: 'authors', method: :create, temp_id: 'abc123' }
|
1
47
|
class JsonapiCompliable::Deserializer
|
48
|
+
# @param payload [Hash] The incoming payload with symbolized keys
|
49
|
+
# @param env [Hash] the Rack env (e.g. +request.env+).
|
2
50
|
def initialize(payload, env)
|
3
51
|
@payload = payload
|
4
52
|
@env = env
|
5
53
|
end
|
6
54
|
|
55
|
+
# @return [Hash] the raw :data value of the payload
|
7
56
|
def data
|
8
57
|
@payload[:data]
|
9
58
|
end
|
10
59
|
|
60
|
+
# @return [String] the raw :id value of the payload
|
11
61
|
def id
|
12
62
|
data[:id]
|
13
63
|
end
|
14
64
|
|
65
|
+
# @return [Hash] the raw :attributes hash + +id+
|
15
66
|
def attributes
|
16
67
|
@attributes ||= raw_attributes.tap do |hash|
|
17
68
|
hash.merge!(id: id) if id
|
18
69
|
end
|
19
70
|
end
|
20
71
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
when 'PUT', 'PATCH' then :update
|
29
|
-
when 'DELETE' then :destroy
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
72
|
+
# 'meta' information about this resource. Includes:
|
73
|
+
#
|
74
|
+
# +type+: the jsonapi type
|
75
|
+
# +method+: create/update/destroy/disassociate. Based on the request env or the +method+ within the +relationships+ hash
|
76
|
+
# +temp_id+: the +temp-id+, if specified
|
77
|
+
#
|
78
|
+
# @return [Hash]
|
33
79
|
def meta
|
34
80
|
{
|
35
81
|
type: data[:type],
|
@@ -38,23 +84,25 @@ class JsonapiCompliable::Deserializer
|
|
38
84
|
}
|
39
85
|
end
|
40
86
|
|
87
|
+
# @return [Hash] the relationships hash
|
41
88
|
def relationships
|
42
89
|
@relationships ||= process_relationships(raw_relationships)
|
43
90
|
end
|
44
91
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
92
|
+
# Parses the +relationships+ recursively and builds an all-hash
|
93
|
+
# include directive like
|
94
|
+
#
|
95
|
+
# { posts: { comments: {} } }
|
96
|
+
#
|
97
|
+
# Relationships that have been marked for destruction will NOT
|
98
|
+
# be part of the include directive.
|
99
|
+
#
|
100
|
+
# @return [Hash] the include directive
|
49
101
|
def include_directive(memo = {}, relationship_node = nil)
|
50
102
|
relationship_node ||= relationships
|
51
103
|
|
52
104
|
relationship_node.each_pair do |name, relationship_payload|
|
53
|
-
|
54
|
-
next if arrayified.all? { |rp| removed?(rp) }
|
55
|
-
|
56
|
-
memo[name] ||= {}
|
57
|
-
deep_merge!(memo[name], sub_directives(memo[name], arrayified))
|
105
|
+
merge_include_directive(memo, name, relationship_payload)
|
58
106
|
end
|
59
107
|
|
60
108
|
memo
|
@@ -62,6 +110,27 @@ class JsonapiCompliable::Deserializer
|
|
62
110
|
|
63
111
|
private
|
64
112
|
|
113
|
+
def merge_include_directive(memo, name, relationship_payload)
|
114
|
+
arrayified = [relationship_payload].flatten
|
115
|
+
return if arrayified.all? { |rp| removed?(rp) }
|
116
|
+
|
117
|
+
memo[name] ||= {}
|
118
|
+
deep_merge!(memo[name], sub_directives(memo[name], arrayified))
|
119
|
+
memo
|
120
|
+
end
|
121
|
+
|
122
|
+
def included
|
123
|
+
@payload[:included] || []
|
124
|
+
end
|
125
|
+
|
126
|
+
def method
|
127
|
+
case @env['REQUEST_METHOD']
|
128
|
+
when 'POST' then :create
|
129
|
+
when 'PUT', 'PATCH' then :update
|
130
|
+
when 'DELETE' then :destroy
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
65
134
|
def removed?(relationship_payload)
|
66
135
|
method = relationship_payload[:meta][:method]
|
67
136
|
[:disassociate, :destroy].include?(method)
|