flexirest 1.7.0 → 1.7.1
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/CHANGELOG.md +6 -0
- data/lib/flexirest/json_api_proxy.rb +103 -76
- data/lib/flexirest/version.rb +1 -1
- data/spec/lib/json_api_spec.rb +29 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4cdc8ff86f82324c82e70c1fb8029e66a935e1ef
|
4
|
+
data.tar.gz: 893d3c0cb2198adbaeb81c77d7d0bd4d2549bf90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a76ac56123023ec67d94139ec6b39a1fa9b874a412556838d1ac226377f4e65fe9e7c613010bef8f16f38cc5b9cd8c14a101f863633a825476f081b7779398ad
|
7
|
+
data.tar.gz: 7f9adf54d566bd067cd72e7c6ad9792eb676b026dab6483944c48bd114bc3c90a41f3ca3306ba014413f6c124b6035fd1c1de2abf0da60e81fe61437dea051cc
|
data/CHANGELOG.md
CHANGED
@@ -186,6 +186,7 @@ module Flexirest
|
|
186
186
|
module Response
|
187
187
|
extend self
|
188
188
|
extend Flexirest::JsonAPIProxy::Helpers
|
189
|
+
ID_PFIX = '_id_'
|
189
190
|
|
190
191
|
def save_resource_class(object)
|
191
192
|
@resource_class = object.is_a?(Class) ? object : object.class
|
@@ -206,19 +207,20 @@ module Flexirest
|
|
206
207
|
records = [records] if is_singular_record
|
207
208
|
|
208
209
|
# Retrieve all names of linked relationships
|
209
|
-
|
210
|
-
|
211
|
-
end
|
210
|
+
relationships = records.first['relationships']
|
211
|
+
relationships = relationships ? relationships.keys : []
|
212
212
|
|
213
|
-
resource_type = records.first['type']
|
214
213
|
included = body['included']
|
215
214
|
|
216
215
|
# Parse the records, and retrieve all resources in a
|
217
216
|
# (nested) array of resources that is easy to work with in Flexirest
|
218
217
|
resources = records.map do |record|
|
219
|
-
fetch_attributes_and_relationships(
|
220
|
-
|
221
|
-
|
218
|
+
fetch_attributes_and_relationships(record, included, relationships)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Pluck all attributed and associations into hashes
|
222
|
+
resources = resources.map do |resource|
|
223
|
+
pluck_attributes_and_relationships(resource, relationships)
|
222
224
|
end
|
223
225
|
|
224
226
|
# Depending on whether we got a resource object (hash) or array
|
@@ -228,8 +230,9 @@ module Flexirest
|
|
228
230
|
|
229
231
|
private
|
230
232
|
|
231
|
-
def fetch_attributes_and_relationships(
|
232
|
-
|
233
|
+
def fetch_attributes_and_relationships(record, included, rels, base: nil)
|
234
|
+
base = Array(base) unless base.is_a?(Array)
|
235
|
+
rels = rels - [base.last]
|
233
236
|
rels_object = record['relationships']
|
234
237
|
|
235
238
|
rels.each do |rel_name|
|
@@ -241,16 +244,13 @@ module Flexirest
|
|
241
244
|
if is_singular_rel
|
242
245
|
# Fetch a linked resource from the relationships object
|
243
246
|
# and add it as an association attribute in the resource hash
|
244
|
-
record[rel_name], embedded =
|
245
|
-
rels_object, rel_name, included
|
246
|
-
)
|
247
|
-
|
247
|
+
record[rel_name], record[ID_PFIX + rel_name], embedded =
|
248
|
+
fetch_one_to_one(base, rels_object, rel_name, included)
|
248
249
|
else
|
249
250
|
# Fetch linked resources from the relationships object
|
250
251
|
# and add it as an array into the resource hash
|
251
|
-
record[rel_name], embedded =
|
252
|
-
rels_object, rel_name, included
|
253
|
-
)
|
252
|
+
record[rel_name], record[ID_PFIX + rel_name], embedded =
|
253
|
+
fetch_one_to_many(base, rels_object, rel_name, included)
|
254
254
|
end
|
255
255
|
|
256
256
|
# Do not try to fetch embedded results if the response is not
|
@@ -267,49 +267,27 @@ module Flexirest
|
|
267
267
|
if nested_rels_object && nested_rels_object.keys.present?
|
268
268
|
# Fetch the linked resources and its attributes recursively
|
269
269
|
fetch_attributes_and_relationships(
|
270
|
-
|
270
|
+
nested_record, included, nested_rels_object.keys,
|
271
|
+
base: base + [rel_name]
|
271
272
|
)
|
272
|
-
|
273
273
|
else
|
274
|
-
|
275
|
-
# resource attributes of the linked resource
|
276
|
-
fetch_and_delete_attributes(nested_record)
|
274
|
+
nested_record
|
277
275
|
end
|
278
276
|
end
|
279
277
|
|
280
|
-
|
281
|
-
# the original type (array or hash) into the record hash
|
282
|
-
record[rel_name] =
|
283
|
-
if is_singular_rel
|
284
|
-
linked_resources.first
|
285
|
-
else
|
286
|
-
linked_resources
|
287
|
-
end
|
278
|
+
record[rel_name] = linked_resources
|
288
279
|
end
|
289
280
|
|
290
|
-
# Add the record attributes to the record hash
|
291
|
-
fetch_and_delete_attributes(record)
|
292
281
|
record
|
293
282
|
end
|
294
283
|
|
295
|
-
def fetch_one_to_one(relationships, name, included)
|
284
|
+
def fetch_one_to_one(base, relationships, name, included)
|
296
285
|
# Parse the relationships object given the relationship name `name`,
|
297
286
|
# and look into the included object (in case of a compound document),
|
298
287
|
# to embed the linked resource into the response
|
299
288
|
|
300
289
|
if included.blank? || relationships[name]['data'].blank?
|
301
|
-
|
302
|
-
# When the response is not a compound document (i.e. there is no
|
303
|
-
# includes object), build a LazyAssociationLoader for lazy loading
|
304
|
-
return build_lazy_loader(
|
305
|
-
name, relationships[name]['links']['related']
|
306
|
-
), false
|
307
|
-
rescue NoMethodError
|
308
|
-
# If the url for retrieving the linked resource is missing,
|
309
|
-
# we assume there is no linked resource available to fetch
|
310
|
-
# Default nulled linked resource is `nil`
|
311
|
-
return nil, false
|
312
|
-
end
|
290
|
+
return build_lazy_loader(base, relationships, name), nil, false
|
313
291
|
end
|
314
292
|
|
315
293
|
# Retrieve the linked resource id and its pluralized type name
|
@@ -322,69 +300,118 @@ module Flexirest
|
|
322
300
|
i['id'] == rel_id && i['type'] == plural_name
|
323
301
|
end
|
324
302
|
|
325
|
-
return linked_resource, true
|
303
|
+
return linked_resource, rel_id, true
|
326
304
|
end
|
327
305
|
|
328
|
-
def fetch_one_to_many(relationships, name, included)
|
306
|
+
def fetch_one_to_many(base, relationships, name, included)
|
329
307
|
# Parse the relationships object given the relationship name `name`,
|
330
308
|
# and look into the included object (in case of a compound document),
|
331
309
|
# to embed the linked resources into the response
|
332
|
-
|
333
310
|
if included.blank? || relationships[name]['data'].blank?
|
334
|
-
|
335
|
-
# When the response is not a compound document (i.e. there is no
|
336
|
-
# includes object), build a LazyAssociationLoader for lazy loading
|
337
|
-
return build_lazy_loader(
|
338
|
-
name, relationships[name]['links']['related']
|
339
|
-
), false
|
340
|
-
rescue NoMethodError
|
341
|
-
# If the url for retrieving the linked resources is missing,
|
342
|
-
# we assume there are no linked resources available to fetch
|
343
|
-
# Default nulled linked resources is an empty array
|
344
|
-
return [], false
|
345
|
-
end
|
311
|
+
return build_lazy_loader(base, relationships, name), [], false
|
346
312
|
end
|
347
313
|
|
348
314
|
# Retrieve the linked resources ids
|
349
315
|
rel_ids = relationships[name]['data'].map { |r| r['id'] }
|
316
|
+
plural_name = name.pluralize
|
350
317
|
|
351
318
|
# Traverse through the included object, and find the included
|
352
319
|
# linked resources, based on the given ids and type name
|
353
320
|
linked_resources = included.select do |i|
|
354
|
-
rel_ids.include?(i['id']) && i['type'] ==
|
321
|
+
rel_ids.include?(i['id']) && i['type'] == plural_name
|
355
322
|
end
|
356
323
|
|
357
|
-
return linked_resources, true
|
324
|
+
return linked_resources, rel_ids, true
|
358
325
|
end
|
359
326
|
|
360
|
-
def
|
327
|
+
def pluck_attributes_and_relationships(record, rels)
|
328
|
+
cleaned = { id: record['id'] }
|
329
|
+
relationships = Hash[rels.map { |rel| [rel, singular?(rel)] }]
|
330
|
+
|
331
|
+
relationships.each do |rel_name, is_singular|
|
332
|
+
safe_name = rel_name.underscore
|
333
|
+
id_sfix = is_singular ? '_id' : '_ids'
|
334
|
+
cleaned[safe_name.singularize + id_sfix] = record[ID_PFIX + rel_name]
|
335
|
+
|
336
|
+
links = record[rel_name]
|
337
|
+
is_lazy_loader = links.is_a?(Flexirest::LazyAssociationLoader)
|
338
|
+
|
339
|
+
linked_resources =
|
340
|
+
if is_lazy_loader || links.blank?
|
341
|
+
# Skip this relationship if it hasn't been included
|
342
|
+
links
|
343
|
+
else
|
344
|
+
# Probe the linked resources
|
345
|
+
first_linked = links.first
|
346
|
+
|
347
|
+
# Retrieve all names of linked relationships
|
348
|
+
nested_rels =
|
349
|
+
if first_linked && first_linked['relationships']
|
350
|
+
first_linked['relationships'].keys
|
351
|
+
else
|
352
|
+
[]
|
353
|
+
end
|
354
|
+
|
355
|
+
# Recursively pluck attributes for all related resources
|
356
|
+
links.map do |linked_resource|
|
357
|
+
pluck_attributes_and_relationships(linked_resource, nested_rels)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
# Depending on if the resource is singular or plural, add it as
|
361
|
+
# the original type (array or hash) into the record hash
|
362
|
+
cleaned[safe_name] =
|
363
|
+
if is_lazy_loader || !is_singular
|
364
|
+
linked_resources
|
365
|
+
else
|
366
|
+
linked_resources ? linked_resources.first : nil
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
361
370
|
# Fetch attribute keys and values from the resource object
|
362
371
|
# and insert into result record hash
|
363
372
|
record['attributes'].each do |k, v|
|
364
|
-
|
373
|
+
cleaned[k.underscore] = v
|
365
374
|
end
|
366
375
|
|
367
|
-
|
368
|
-
record
|
376
|
+
cleaned
|
369
377
|
end
|
370
378
|
|
371
|
-
def
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
379
|
+
def find_association_class(base, name)
|
380
|
+
stack = base + [name]
|
381
|
+
klass = @resource_class
|
382
|
+
|
383
|
+
until stack.empty?
|
384
|
+
shift = stack.shift
|
385
|
+
last = klass
|
386
|
+
klass = klass._associations[shift.underscore.to_sym]
|
387
|
+
|
388
|
+
if klass.nil?
|
389
|
+
raise "#{last} has no defined relation to #{shift}. " \
|
390
|
+
"Have you defined :has_one or :has_many :#{shift} in #{last}?"
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
klass
|
377
395
|
end
|
378
396
|
|
379
|
-
def build_lazy_loader(
|
397
|
+
def build_lazy_loader(base, relationships, name)
|
398
|
+
is_singular = singular?(name)
|
380
399
|
# Create a new request, given the linked resource `name`,
|
381
400
|
# finding the association's class, and given the `url` to the linked
|
382
401
|
# resource
|
402
|
+
begin
|
403
|
+
# When the response is not a compound document (i.e. there is no
|
404
|
+
# includes object), build a LazyAssociationLoader for lazy loading
|
405
|
+
url = relationships[name]['links']['related']
|
406
|
+
rescue NoMethodError
|
407
|
+
# If the url for retrieving the linked resource is missing,
|
408
|
+
# we assume there is no linked resource available to fetch
|
409
|
+
# Default nulled linked resource is `nil` or `[]` for resources
|
410
|
+
return is_singular ? nil : []
|
411
|
+
end
|
383
412
|
|
384
|
-
|
385
|
-
|
386
|
-
@resource_class._associations[name.to_sym].new
|
387
|
-
)
|
413
|
+
klass = find_association_class(base, name)
|
414
|
+
request = Flexirest::Request.new({ url: url, method: :get }, klass.new)
|
388
415
|
|
389
416
|
# Also add the previous request's header, which may contain
|
390
417
|
# crucial authentication headers (or so), to connect with the service
|
data/lib/flexirest/version.rb
CHANGED
data/spec/lib/json_api_spec.rb
CHANGED
@@ -84,6 +84,24 @@ class JsonAPIExampleArticle < Flexirest::Base
|
|
84
84
|
relationships: { 'tags' => { data: [] }, 'author' => { data: nil } }
|
85
85
|
}
|
86
86
|
}
|
87
|
+
faker7 = {
|
88
|
+
data: {
|
89
|
+
id: 1, type: 'articles', attributes: { item: 'item one' },
|
90
|
+
relationships: {
|
91
|
+
'tags' => { data: [{ id: 1, type: 'tags' }, { id: 2, type: 'tags' }] },
|
92
|
+
'superman' => {
|
93
|
+
links: {
|
94
|
+
self: 'http://www.example.com/articles/1/relationships/superman',
|
95
|
+
related: 'http://www.example.com/articles/1/superman'
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
},
|
100
|
+
included: [
|
101
|
+
{ id: 1, type: 'tags', attributes: { item: 'item two' } },
|
102
|
+
{ id: 2, type: 'tags', attributes: { item: 'item three' } }
|
103
|
+
]
|
104
|
+
}
|
87
105
|
|
88
106
|
get(
|
89
107
|
:find,
|
@@ -126,6 +144,13 @@ class JsonAPIExampleArticle < Flexirest::Base
|
|
126
144
|
fake: faker6.to_json,
|
127
145
|
fake_content_type: 'application/vnd.api+json'
|
128
146
|
)
|
147
|
+
|
148
|
+
get(
|
149
|
+
:not_recognized_assoc,
|
150
|
+
'/articles/:id',
|
151
|
+
fake: faker7.to_json,
|
152
|
+
fake_content_type: 'application/vnd.api+json'
|
153
|
+
)
|
129
154
|
end
|
130
155
|
|
131
156
|
module JsonAPIExample
|
@@ -314,6 +339,10 @@ describe 'JSON API' do
|
|
314
339
|
expect(article.find_lazy(1).author).to be_an_instance_of(JsonAPIExample::Author)
|
315
340
|
expect(article.find_lazy(1).author.id).to_not be_nil
|
316
341
|
end
|
342
|
+
|
343
|
+
it 'should raise exception when an association in the response is not defined in base class' do
|
344
|
+
expect(-> { subject.includes(:tags).not_recognized_assoc(1) }).to raise_error(Exception)
|
345
|
+
end
|
317
346
|
end
|
318
347
|
|
319
348
|
context 'client' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flexirest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.
|
4
|
+
version: 1.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Jeffries
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-08-
|
11
|
+
date: 2018-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|