flexirest 1.7.0 → 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|