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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f6e4ecaa1f891e1bb1a98368b8dbdf39024d31f
4
- data.tar.gz: deb3c6a4cbf8af73c9cb2c513866162f7e440071
3
+ metadata.gz: 4cdc8ff86f82324c82e70c1fb8029e66a935e1ef
4
+ data.tar.gz: 893d3c0cb2198adbaeb81c77d7d0bd4d2549bf90
5
5
  SHA512:
6
- metadata.gz: beff9dd048c839bcdadda6c49aadd44e2bcc833ff436edfdb917dc6c9efff64afbc249502909aee8bc200ca06aac9587be41b33f595b316c2dbbab2e675b5b55
7
- data.tar.gz: 90054f73fb70d72ba4cef6d87a6dc87683c55e731f47d38839a70b2ad5c98c6fae6486be4ff56c19829bd1234f82094d5eb4d9ebac2ecbe5a1ac282132b0457b
6
+ metadata.gz: a76ac56123023ec67d94139ec6b39a1fa9b874a412556838d1ac226377f4e65fe9e7c613010bef8f16f38cc5b9cd8c14a101f863633a825476f081b7779398ad
7
+ data.tar.gz: 7f9adf54d566bd067cd72e7c6ad9792eb676b026dab6483944c48bd114bc3c90a41f3ca3306ba014413f6c124b6035fd1c1de2abf0da60e81fe61437dea051cc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.1
4
+
5
+ Bugfix:
6
+
7
+ - Fixed fetching nested associations from the compound document when using JsonAPI standard requests (thanks to Mike Voets for the PR)
8
+
3
9
  ## 1.7.0
4
10
 
5
11
  Feature:
@@ -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
- if records.first['relationships']
210
- relationships = records.first['relationships'].keys
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
- resource_type, record, included, relationships
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(base, record, included, rels)
232
- rels = (rels || []) - [base]
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 = fetch_one_to_one(
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 = fetch_one_to_many(
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
- record['type'], nested_record, included, nested_rels_object.keys
270
+ nested_record, included, nested_rels_object.keys,
271
+ base: base + [rel_name]
271
272
  )
272
-
273
273
  else
274
- # If there are no nested linked resources, just fetch the
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
- # Depending on if the resource is singular or plural, add it as
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
- begin
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
- begin
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'] == name
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 fetch_and_delete_attributes(record)
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
- record[k] = v
373
+ cleaned[k.underscore] = v
365
374
  end
366
375
 
367
- delete_keys(record)
368
- record
376
+ cleaned
369
377
  end
370
378
 
371
- def delete_keys(record)
372
- # Delete the attribute keys and values from the original response hash
373
- record.delete('type')
374
- record.delete('links')
375
- record.delete('attributes')
376
- record.delete('relationships')
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(name, url)
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
- request = Flexirest::Request.new(
385
- { url: url, method: :get },
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
@@ -1,3 +1,3 @@
1
1
  module Flexirest
2
- VERSION = "1.7.0"
2
+ VERSION = "1.7.1"
3
3
  end
@@ -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.0
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-05 00:00:00.000000000 Z
11
+ date: 2018-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler