jsonapi-realizer 6.0.0.rc2 → 6.0.0
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 +4 -4
- data/LICENSE +440 -0
- data/README.md +2 -12
- data/Rakefile +1 -0
- data/lib/jsonapi/realizer/action.rb +80 -74
- data/lib/jsonapi/realizer/adapter/active_record.rb +11 -9
- data/lib/jsonapi/realizer/adapter.rb +7 -7
- data/lib/jsonapi/realizer/adapter_spec.rb +2 -4
- data/lib/jsonapi/realizer/configuration.rb +2 -0
- data/lib/jsonapi/realizer/context.rb +2 -0
- data/lib/jsonapi/realizer/controller.rb +4 -2
- data/lib/jsonapi/realizer/error/include_without_data_property.rb +2 -1
- data/lib/jsonapi/realizer/error/invalid_content_type_header.rb +2 -0
- data/lib/jsonapi/realizer/error/invalid_data_type_property.rb +2 -0
- data/lib/jsonapi/realizer/error/invalid_root_property.rb +2 -0
- data/lib/jsonapi/realizer/error/missing_content_type_header.rb +2 -1
- data/lib/jsonapi/realizer/error/missing_data_type_property.rb +2 -1
- data/lib/jsonapi/realizer/error/missing_root_property.rb +2 -1
- data/lib/jsonapi/realizer/error/resource_attribute_not_found.rb +2 -0
- data/lib/jsonapi/realizer/error/resource_relationship_not_found.rb +2 -0
- data/lib/jsonapi/realizer/error.rb +2 -0
- data/lib/jsonapi/realizer/resource/attribute.rb +2 -0
- data/lib/jsonapi/realizer/resource/configuration.rb +2 -0
- data/lib/jsonapi/realizer/resource/relation.rb +2 -0
- data/lib/jsonapi/realizer/resource.rb +182 -172
- data/lib/jsonapi/realizer/resource_spec.rb +50 -9
- data/lib/jsonapi/realizer/version.rb +3 -1
- data/lib/jsonapi/realizer.rb +7 -5
- data/lib/jsonapi/realizer_spec.rb +2 -28
- data/lib/jsonapi-realizer.rb +2 -0
- metadata +24 -204
- data/lib/jsonapi/realizer/version_spec.rb +0 -7
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module JSONAPI
|
|
2
4
|
module Realizer
|
|
3
5
|
module Resource
|
|
@@ -8,11 +10,11 @@ module JSONAPI
|
|
|
8
10
|
extend(ActiveSupport::Concern)
|
|
9
11
|
include(ActiveModel::Model)
|
|
10
12
|
|
|
11
|
-
MIXIN_HOOK =
|
|
13
|
+
MIXIN_HOOK = lambda do |*|
|
|
12
14
|
@attributes = {}
|
|
13
15
|
@relations = {}
|
|
14
16
|
|
|
15
|
-
unless const_defined?(
|
|
17
|
+
unless const_defined?(:Context)
|
|
16
18
|
self::Context = Class.new do
|
|
17
19
|
include(JSONAPI::Realizer::Context)
|
|
18
20
|
|
|
@@ -25,8 +27,8 @@ module JSONAPI
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
validates_presence_of(:intent)
|
|
28
|
-
validates_presence_of(:parameters, :
|
|
29
|
-
validates_presence_of(:headers, :
|
|
30
|
+
validates_presence_of(:parameters, allow_empty: true)
|
|
31
|
+
validates_presence_of(:headers, allow_empty: true)
|
|
30
32
|
|
|
31
33
|
identifier(JSONAPI::Realizer.configuration.default_identifier)
|
|
32
34
|
|
|
@@ -38,7 +40,7 @@ module JSONAPI
|
|
|
38
40
|
attr_accessor(:parameters)
|
|
39
41
|
attr_accessor(:headers)
|
|
40
42
|
attr_writer(:context)
|
|
41
|
-
|
|
43
|
+
attr_writer(:scope)
|
|
42
44
|
|
|
43
45
|
def initialize(**keyword_arguments)
|
|
44
46
|
super(**keyword_arguments)
|
|
@@ -46,41 +48,30 @@ module JSONAPI
|
|
|
46
48
|
context.validate!
|
|
47
49
|
validate!
|
|
48
50
|
|
|
49
|
-
if filtering?
|
|
50
|
-
@scope = adapter.filtering(scope, filters)
|
|
51
|
-
end
|
|
51
|
+
@scope = adapter.filtering(scope, filters) if filtering?
|
|
52
52
|
|
|
53
|
-
if include?
|
|
54
|
-
@scope = adapter.include_relationships(scope, includes)
|
|
55
|
-
end
|
|
53
|
+
@scope = adapter.include_relationships(scope, includes) if include?
|
|
56
54
|
|
|
57
|
-
if sorting?
|
|
58
|
-
@scope = adapter.sorting(scope, sorts)
|
|
59
|
-
end
|
|
55
|
+
@scope = adapter.sorting(scope, sorts) if sorting?
|
|
60
56
|
|
|
61
|
-
if paginate?
|
|
62
|
-
@scope = adapter.paginate(scope, *pagination)
|
|
63
|
-
end
|
|
57
|
+
@scope = adapter.paginate(scope, *pagination) if paginate?
|
|
64
58
|
|
|
65
|
-
if writing? && data?
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
adapter.write_attributes(object, attributes) if writing? && data? && attributes?
|
|
60
|
+
|
|
61
|
+
return unless writing? && data? && relationships?
|
|
62
|
+
|
|
63
|
+
adapter.write_relationships(object, relationships)
|
|
69
64
|
end
|
|
70
65
|
|
|
71
66
|
def to_hash
|
|
72
|
-
@
|
|
73
|
-
:pagination
|
|
74
|
-
:selects
|
|
75
|
-
:includes
|
|
76
|
-
:
|
|
67
|
+
@to_hash ||= {
|
|
68
|
+
pagination: (pagination if paginate?),
|
|
69
|
+
selects: (selects if selects?),
|
|
70
|
+
includes: (includes if include?),
|
|
71
|
+
object:
|
|
77
72
|
}.compact
|
|
78
73
|
end
|
|
79
74
|
|
|
80
|
-
private def writing?
|
|
81
|
-
[:create, :update].include?(intent)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
75
|
def paginate?
|
|
85
76
|
parameters.key?("page") && (parameters.fetch("page").key?("limit") || parameters.fetch("page").key?("offset"))
|
|
86
77
|
end
|
|
@@ -98,19 +89,19 @@ module JSONAPI
|
|
|
98
89
|
|
|
99
90
|
def sorts
|
|
100
91
|
@sorts ||= parameters.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
92
|
+
# {sort: "name,-age,accounts.created_at,-accounts.updated_at"}
|
|
93
|
+
fetch("sort").
|
|
94
|
+
# "name,-age,accounts.created_at,-accounts.updated_at"
|
|
95
|
+
split(",").
|
|
96
|
+
# ["name", "-age", "accounts.created_at", "-accounts.updated_at"]
|
|
97
|
+
map do |token|
|
|
98
|
+
token.start_with?("-") ? [token.sub(/^-/, "").underscore, "-"] : [token.underscore, "+"]
|
|
99
|
+
end.
|
|
100
|
+
# [["name", "+"], ["age", "-"], ["accounts.created_at", "+"], ["accounts.updated_at", "-"]]
|
|
101
|
+
map do |(path, direction)|
|
|
102
|
+
[path.include?(".") ? path.split(".") : [self.class.configuration.type, path], direction]
|
|
103
|
+
end
|
|
104
|
+
# [[["accounts", "name"], "+"], [["accounts", "age"], "-"], [["accounts", "created_at"], "+"], [["accounts", "updated_at"], "-"]]
|
|
114
105
|
end
|
|
115
106
|
|
|
116
107
|
def filtering?
|
|
@@ -119,11 +110,11 @@ module JSONAPI
|
|
|
119
110
|
|
|
120
111
|
def filters
|
|
121
112
|
@filters ||= parameters.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
113
|
+
# {"filter" => {"full-name" => "Abby Marquardt", "email" => "amado@goldner.com"}}
|
|
114
|
+
fetch("filter").
|
|
115
|
+
# {"full-name" => "Abby Marquardt", "email" => "amado@goldner.com"}
|
|
116
|
+
transform_keys(&:underscore)
|
|
117
|
+
# {"full_name" => "Abby Marquardt", "email" => "amado@goldner.com"}
|
|
127
118
|
end
|
|
128
119
|
|
|
129
120
|
def include?
|
|
@@ -132,30 +123,30 @@ module JSONAPI
|
|
|
132
123
|
|
|
133
124
|
def includes
|
|
134
125
|
@includes ||= parameters.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
126
|
+
# {"include" => "active-photographer.photographs,comments,comments.author"}
|
|
127
|
+
fetch("include").
|
|
128
|
+
# "active-photographer.photographs,comments,comments.author"
|
|
129
|
+
split(/\s*,\s*/).
|
|
130
|
+
# ["active-photographer.photographs", "comments", "comments.author"]
|
|
131
|
+
map { |chain| chain.split(".") }.
|
|
132
|
+
# [["active-photographer", "photographs"], ["comments"], ["comments", "author"]]
|
|
133
|
+
map { |list| list.map(&:underscore) }.
|
|
134
|
+
# [["active_photographer", "photographs"], ["comments"], ["comments", "author"]]
|
|
135
|
+
map do |relationship_chain|
|
|
136
|
+
# This walks down the path of relationships and normalizes thenm to
|
|
137
|
+
# their defined "as", which lets us expose AccountRealizer#name, but that actually
|
|
138
|
+
# references Account#full_name.
|
|
139
|
+
relationship_chain.reduce([[], self.class]) do |(normalized_relationship_chain, realizer_class), relationship_link|
|
|
140
|
+
[
|
|
149
141
|
[
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# [["account", "photographs"], ["comments"], ["comments", "account"]]
|
|
142
|
+
*normalized_relationship_chain,
|
|
143
|
+
realizer_class.relation(relationship_link).as
|
|
144
|
+
],
|
|
145
|
+
realizer_class.relation(relationship_link).realizer_class
|
|
146
|
+
]
|
|
147
|
+
end.first
|
|
148
|
+
end
|
|
149
|
+
# [["account", "photographs"], ["comments"], ["comments", "account"]]
|
|
159
150
|
end
|
|
160
151
|
|
|
161
152
|
def selects?
|
|
@@ -164,99 +155,53 @@ module JSONAPI
|
|
|
164
155
|
|
|
165
156
|
def selects
|
|
166
157
|
@selects ||= parameters.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
private def data?
|
|
179
|
-
parameters.key?("data")
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
private def data
|
|
183
|
-
@data ||= parameters.fetch("data")
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
private def type
|
|
187
|
-
return unless data.key?("type")
|
|
188
|
-
|
|
189
|
-
@type ||= data.fetch("type")
|
|
158
|
+
# {"fields" => {"articles" => "title,body,sub-text", "people" => "name"}}
|
|
159
|
+
fetch("fields").
|
|
160
|
+
# {"articles" => "title,body,sub-text", "people" => "name"}
|
|
161
|
+
transform_keys(&:underscore).
|
|
162
|
+
# {"articles" => "title,body,sub-text", "people" => "name"}
|
|
163
|
+
transform_values { |value| value.split(/\s*,\s*/) }.
|
|
164
|
+
# {"articles" => ["title", "body", "sub-text"], "people" => ["name"]}
|
|
165
|
+
transform_values { |value| value.map(&:underscore) }
|
|
166
|
+
# {"articles" => ["title", "body", "sub_text"], "people" => ["name"]}
|
|
190
167
|
end
|
|
191
168
|
|
|
192
169
|
def attributes
|
|
193
170
|
return unless data.key?("attributes")
|
|
194
171
|
|
|
195
|
-
@attributes ||= data
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
172
|
+
@attributes ||= data
|
|
173
|
+
.fetch("attributes")
|
|
174
|
+
.transform_keys(&:underscore)
|
|
175
|
+
.transform_keys { |key| attribute(key).as }
|
|
199
176
|
end
|
|
200
177
|
|
|
201
178
|
def relationships
|
|
202
179
|
return unless data.key?("relationships")
|
|
203
180
|
|
|
204
|
-
@relationships ||= data
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
private def scope
|
|
212
|
-
@scope ||= adapter.find_many(@scope || model_class)
|
|
181
|
+
@relationships ||= data
|
|
182
|
+
.fetch("relationships")
|
|
183
|
+
.transform_keys(&:underscore)
|
|
184
|
+
.map(&method(:as_relationship)).to_h
|
|
185
|
+
.transform_keys { |key| relation(key).as }
|
|
213
186
|
end
|
|
214
187
|
|
|
215
188
|
def object
|
|
216
189
|
@object ||= case intent
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
190
|
+
when :create
|
|
191
|
+
scope.new
|
|
192
|
+
when :show, :update, :destroy
|
|
193
|
+
adapter.find_one(scope, parameters.fetch("id"))
|
|
194
|
+
else
|
|
195
|
+
scope
|
|
196
|
+
end
|
|
224
197
|
end
|
|
225
198
|
|
|
226
199
|
def intent
|
|
227
200
|
@intent.to_sym
|
|
228
201
|
end
|
|
229
202
|
|
|
230
|
-
private def as_relationship(name, value)
|
|
231
|
-
data = value.fetch("data")
|
|
232
|
-
|
|
233
|
-
relation_configuration = relation(name).realizer_class.configuration
|
|
234
|
-
|
|
235
|
-
if data.is_a?(Array)
|
|
236
|
-
[name, relation_configuration.adapter.find_many(relation_configuration.model_class, {id: data.map {|value| value.fetch("id")}})]
|
|
237
|
-
else
|
|
238
|
-
[name, relation_configuration.adapter.find_one(relation_configuration.model_class, data.fetch("id"))]
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
private def attribute(name)
|
|
243
|
-
self.class.attribute(name)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
private def relation(name)
|
|
247
|
-
self.class.relation(name)
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
private def adapter
|
|
251
|
-
self.class.configuration.adapter
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
private def model_class
|
|
255
|
-
self.class.configuration.model_class
|
|
256
|
-
end
|
|
257
|
-
|
|
258
203
|
def context
|
|
259
|
-
self.class.const_get(
|
|
204
|
+
self.class.const_get(:Context).new(**@context || {})
|
|
260
205
|
end
|
|
261
206
|
|
|
262
207
|
included do
|
|
@@ -265,6 +210,7 @@ module JSONAPI
|
|
|
265
210
|
|
|
266
211
|
class_methods do
|
|
267
212
|
def inherited(object)
|
|
213
|
+
super
|
|
268
214
|
object.class_eval(&MIXIN_HOOK) unless object.instance_variable_defined?(:@abstract_class)
|
|
269
215
|
end
|
|
270
216
|
|
|
@@ -273,62 +219,126 @@ module JSONAPI
|
|
|
273
219
|
end
|
|
274
220
|
|
|
275
221
|
def type(value, class_name:, adapter:)
|
|
276
|
-
@type
|
|
277
|
-
@model_class
|
|
278
|
-
@adapter
|
|
222
|
+
@type = value.to_s
|
|
223
|
+
@model_class = class_name.constantize
|
|
224
|
+
@adapter = JSONAPI::Realizer::Adapter.new(interface: adapter)
|
|
279
225
|
end
|
|
280
226
|
|
|
281
227
|
def has(name, as: name)
|
|
282
228
|
@attributes[name] ||= Attribute.new(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
:
|
|
229
|
+
name:,
|
|
230
|
+
as:,
|
|
231
|
+
owner: self
|
|
286
232
|
)
|
|
287
233
|
end
|
|
288
234
|
|
|
289
|
-
|
|
235
|
+
# rubocop:disable Naming/PredicateName
|
|
236
|
+
def has_one(name, class_name:, as: name)
|
|
290
237
|
@relations[name] ||= Relation.new(
|
|
291
|
-
:
|
|
292
|
-
:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
:
|
|
238
|
+
owner: self,
|
|
239
|
+
type: :one,
|
|
240
|
+
name:,
|
|
241
|
+
as:,
|
|
242
|
+
realizer_class_name: class_name
|
|
296
243
|
)
|
|
297
244
|
end
|
|
245
|
+
# rubocop:enable Naming/PredicateName
|
|
298
246
|
|
|
299
|
-
|
|
247
|
+
# rubocop:disable Naming/PredicateName
|
|
248
|
+
def has_many(name, class_name:, as: name)
|
|
300
249
|
@relations[name] ||= Relation.new(
|
|
301
|
-
:
|
|
302
|
-
:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
:
|
|
250
|
+
owner: self,
|
|
251
|
+
type: :many,
|
|
252
|
+
name:,
|
|
253
|
+
as:,
|
|
254
|
+
realizer_class_name: class_name
|
|
306
255
|
)
|
|
307
256
|
end
|
|
257
|
+
# rubocop:enable Naming/PredicateName
|
|
308
258
|
|
|
309
259
|
def context
|
|
310
|
-
const_get(
|
|
260
|
+
const_get(:Context)
|
|
311
261
|
end
|
|
312
262
|
|
|
313
263
|
def configuration
|
|
314
|
-
@configuration ||= Configuration.new(
|
|
315
|
-
:
|
|
316
|
-
:
|
|
317
|
-
:
|
|
318
|
-
:
|
|
319
|
-
:
|
|
320
|
-
:
|
|
321
|
-
|
|
264
|
+
@configuration ||= Configuration.new(
|
|
265
|
+
owner: self,
|
|
266
|
+
type: @type,
|
|
267
|
+
model_class: @model_class,
|
|
268
|
+
adapter: @adapter,
|
|
269
|
+
attributes: @attributes,
|
|
270
|
+
relations: @relations
|
|
271
|
+
)
|
|
322
272
|
end
|
|
323
273
|
|
|
324
274
|
def attribute(name)
|
|
325
|
-
configuration.attributes.fetch(name.to_sym){raise(Error::ResourceAttributeNotFound, name
|
|
275
|
+
configuration.attributes.fetch(name.to_sym) { raise(Error::ResourceAttributeNotFound, name:, realizer: self) }
|
|
326
276
|
end
|
|
327
277
|
|
|
328
278
|
def relation(name)
|
|
329
|
-
configuration.relations.fetch(name.to_sym){raise(Error::ResourceRelationshipNotFound, name
|
|
279
|
+
configuration.relations.fetch(name.to_sym) { raise(Error::ResourceRelationshipNotFound, name:, realizer: self) }
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private def writing?
|
|
284
|
+
%i[create update].include?(intent)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
private def data?
|
|
288
|
+
parameters.key?("data")
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
private def data
|
|
292
|
+
@data ||= parameters.fetch("data")
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
private def type
|
|
296
|
+
return unless data.key?("type")
|
|
297
|
+
|
|
298
|
+
@type ||= data.fetch("type")
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
private def attributes?
|
|
302
|
+
data.key?("attributes")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
private def relationships?
|
|
306
|
+
data.key?("relationships")
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
private def scope
|
|
310
|
+
@scope ||= adapter.find_many(@scope || model_class)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
private def as_relationship(name, value)
|
|
314
|
+
return [name, nil] if value.nil?
|
|
315
|
+
|
|
316
|
+
data = value.fetch("data")
|
|
317
|
+
|
|
318
|
+
relation_configuration = relation(name).realizer_class.configuration
|
|
319
|
+
|
|
320
|
+
if data.is_a?(Array)
|
|
321
|
+
[name, relation_configuration.adapter.find_many(relation_configuration.model_class, { id: data.map { |value| value.fetch("id") } })]
|
|
322
|
+
else
|
|
323
|
+
[name, relation_configuration.adapter.find_one(relation_configuration.model_class, data.fetch("id"))]
|
|
330
324
|
end
|
|
331
325
|
end
|
|
326
|
+
|
|
327
|
+
private def attribute(name)
|
|
328
|
+
self.class.attribute(name)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
private def relation(name)
|
|
332
|
+
self.class.relation(name)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
private def adapter
|
|
336
|
+
self.class.configuration.adapter
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
private def model_class
|
|
340
|
+
self.class.configuration.model_class
|
|
341
|
+
end
|
|
332
342
|
end
|
|
333
343
|
end
|
|
334
344
|
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require("spec_helper")
|
|
2
4
|
|
|
3
5
|
RSpec.describe(JSONAPI::Realizer::Resource) do
|
|
4
|
-
let(:resource_class) {PhotoRealizer}
|
|
5
|
-
let(:resource) {resource_class.new(intent
|
|
6
|
+
let(:resource_class) { PhotoRealizer }
|
|
7
|
+
let(:resource) { resource_class.new(intent:, parameters:, headers:) }
|
|
6
8
|
|
|
7
9
|
describe "#as_native" do
|
|
8
|
-
|
|
10
|
+
subject { resource }
|
|
9
11
|
|
|
10
12
|
context "when accepting the right type, when creating with data, with spares fields, and includes" do
|
|
11
|
-
let(:intent) {:create}
|
|
13
|
+
let(:intent) { :create }
|
|
12
14
|
let(:parameters) do
|
|
13
15
|
{
|
|
14
16
|
"include" => "photographer",
|
|
@@ -41,23 +43,62 @@ RSpec.describe(JSONAPI::Realizer::Resource) do
|
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
before do
|
|
44
|
-
Account.create!(:
|
|
46
|
+
Account.create!(id: 9, name: "Dan Gebhardt", twitter: "dgeb")
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
it "object is a Photo" do
|
|
48
|
-
expect(subject.object).to
|
|
50
|
+
expect(subject.object).to be_a(Photo)
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
it "object isn't saved" do
|
|
52
|
-
expect(subject.object).
|
|
54
|
+
expect(subject.object).not_to be_persisted
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
it "object has the right attributes" do
|
|
56
58
|
expect(subject.object).to have_attributes(
|
|
57
|
-
:
|
|
58
|
-
:
|
|
59
|
+
title: "Ember Hamster",
|
|
60
|
+
src: "http://example.com/images/productivity.png"
|
|
59
61
|
)
|
|
60
62
|
end
|
|
63
|
+
|
|
64
|
+
it "has a photographer" do
|
|
65
|
+
expect(subject.object.photographer).not_to be_nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "when specifying nil relationship" do
|
|
70
|
+
let(:intent) { :update }
|
|
71
|
+
let(:parameters) do
|
|
72
|
+
{
|
|
73
|
+
"id" => "11",
|
|
74
|
+
"data" => {
|
|
75
|
+
"id" => "11",
|
|
76
|
+
"type" => "photos",
|
|
77
|
+
"relationships" => {
|
|
78
|
+
"photographer" => nil
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
let(:headers) do
|
|
84
|
+
{
|
|
85
|
+
"Accept" => "application/vnd.api+json",
|
|
86
|
+
"Content-Type" => "application/vnd.api+json"
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
before do
|
|
91
|
+
account = Account.create!(id: 9, name: "Dan Gebhardt", twitter: "dgeb")
|
|
92
|
+
Photo.create!(id: 11, photographer: account, title: "Ember Hamster", src: "http://example.com/images/productivity.png")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "object is a Photo" do
|
|
96
|
+
expect(subject.object).to be_a(Photo)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "clears relationship on realizing nil" do
|
|
100
|
+
expect(subject.object.photographer).to be_nil
|
|
101
|
+
end
|
|
61
102
|
end
|
|
62
103
|
end
|
|
63
104
|
end
|
data/lib/jsonapi/realizer.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require("ostruct")
|
|
2
4
|
require("addressable")
|
|
3
5
|
require("active_model")
|
|
@@ -7,7 +9,7 @@ require("active_support/core_ext/string")
|
|
|
7
9
|
require("active_support/core_ext/module")
|
|
8
10
|
|
|
9
11
|
module JSONAPI
|
|
10
|
-
MEDIA_TYPE = "application/vnd.api+json" unless const_defined?(
|
|
12
|
+
MEDIA_TYPE = "application/vnd.api+json" unless const_defined?(:MEDIA_TYPE)
|
|
11
13
|
|
|
12
14
|
module Realizer
|
|
13
15
|
require_relative("realizer/version")
|
|
@@ -16,10 +18,10 @@ module JSONAPI
|
|
|
16
18
|
require_relative("realizer/controller")
|
|
17
19
|
|
|
18
20
|
@configuration ||= Configuration.new(
|
|
19
|
-
:
|
|
20
|
-
:
|
|
21
|
-
:
|
|
22
|
-
:
|
|
21
|
+
default_invalid_content_type_exception: JSONAPI::Realizer::Error::InvalidContentTypeHeader,
|
|
22
|
+
default_missing_content_type_exception: JSONAPI::Realizer::Error::MissingContentTypeHeader,
|
|
23
|
+
default_identifier: :id,
|
|
24
|
+
adapter_mappings: {}
|
|
23
25
|
)
|
|
24
26
|
|
|
25
27
|
require_relative("realizer/adapter")
|
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
let(:parameters) {
|
|
5
|
-
{
|
|
6
|
-
"data" => {
|
|
7
|
-
"type" => "photos",
|
|
8
|
-
"attributes" => {
|
|
9
|
-
"title" => "Ember Hamster",
|
|
10
|
-
"src" => "http://example.com/images/productivity.png"
|
|
11
|
-
},
|
|
12
|
-
"relationships" => {
|
|
13
|
-
"photographer" => {
|
|
14
|
-
"data" => {
|
|
15
|
-
"type" => "people",
|
|
16
|
-
"id" => "9"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
let(:headers) {
|
|
24
|
-
{
|
|
25
|
-
"Accept" => "application/vnd.api+json",
|
|
26
|
-
"Content-Type" => "application/vnd.api+json"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
end
|
|
3
|
+
require "spec_helper"
|