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 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