lhs 6.3.1 → 6.4.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/.rubocop.localch.yml +1 -4
- data/Gemfile +1 -1
- data/README.md +81 -64
- data/lhs.gemspec +1 -1
- data/lib/lhs/concerns/record/chainable.rb +9 -8
- data/lib/lhs/concerns/record/find.rb +40 -9
- data/lib/lhs/concerns/record/request.rb +28 -24
- data/lib/lhs/item.rb +4 -2
- data/lib/lhs/version.rb +1 -1
- data/spec/record/find_in_parallel_spec.rb +39 -0
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8657e6a2962b433061940b5fbed11d69538233b0
|
4
|
+
data.tar.gz: 588206ef328828b3ffa4ad5a33019f1c8e11f3fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f18c4f116090fbf214ca85552455b79e282f47f72c60f9484f35b927616c036a8ae732850e98cd7484db0d889444082afa94b69d653dd4e0a1b23b747d5012ee
|
7
|
+
data.tar.gz: f241d8737f772951a5549ad847ff2905e190071254c8af57022c4e5d05d0375254d46b27496d4f1964c47e0cb4a0948557861b440120a71b1efeb86e7decfbe2
|
data/.rubocop.localch.yml
CHANGED
data/Gemfile
CHANGED
@@ -8,4 +8,4 @@ gemspec
|
|
8
8
|
# Declare any dependencies that are still in development here instead of in
|
9
9
|
# your gemspec. These might include edge Rails or gems from your path or
|
10
10
|
# Git. Remember to move these dependencies to your gemspec before releasing
|
11
|
-
# your gem to rubygems.org.
|
11
|
+
# your gem to rubygems.org.
|
data/README.md
CHANGED
@@ -8,15 +8,15 @@ LHS uses [LHC](//github.com/local-ch/LHC) for http requests.
|
|
8
8
|
Access data that is provided by an http json service with ease using a LHS::Record.
|
9
9
|
|
10
10
|
```ruby
|
11
|
-
class
|
11
|
+
class Record < LHS::Record
|
12
12
|
|
13
|
-
endpoint ':
|
14
|
-
endpoint ':
|
13
|
+
endpoint ':service/v2/records'
|
14
|
+
endpoint ':service/v2/association/:association_id/records'
|
15
15
|
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
record = Record.find_by(email: 'somebody@mail.com') #<Record>
|
19
|
+
record.review # "Lunch was great"
|
20
20
|
```
|
21
21
|
|
22
22
|
## Where to store LHS::Records
|
@@ -28,12 +28,12 @@ Please store all defined LHS::Records in `app/models` as they are not autoloaded
|
|
28
28
|
You setup a LHS::Record by configuring one or multiple endpoints. You can also add request options for an endpoint (see following example).
|
29
29
|
|
30
30
|
```ruby
|
31
|
-
class
|
31
|
+
class Record < LHS::Record
|
32
32
|
|
33
|
-
endpoint ':
|
34
|
-
endpoint ':
|
35
|
-
endpoint ':
|
36
|
-
endpoint ':
|
33
|
+
endpoint ':service/v2/association/:association_id/records'
|
34
|
+
endpoint ':service/v2/association/:association_id/records/:id'
|
35
|
+
endpoint ':service/v2/records', cache: true, cache_expires_in: 1.day
|
36
|
+
endpoint ':service/v2/records/:id', cache: true, cache_expires_in: 1.day
|
37
37
|
|
38
38
|
end
|
39
39
|
```
|
@@ -44,10 +44,10 @@ Please use placeholders when configuring endpoints also for hosts. Otherwise LHS
|
|
44
44
|
If you try to setup a LHS::Record with clashing endpoints it will immediately raise an exception.
|
45
45
|
|
46
46
|
```ruby
|
47
|
-
class
|
47
|
+
class Record < LHS::Record
|
48
48
|
|
49
|
-
endpoint ':
|
50
|
-
endpoint ':
|
49
|
+
endpoint ':service/v2/records'
|
50
|
+
endpoint ':service/v2/something_else'
|
51
51
|
|
52
52
|
end
|
53
53
|
# raises: Clashing endpoints.
|
@@ -58,16 +58,16 @@ end
|
|
58
58
|
You can query a service for records by using `where`.
|
59
59
|
|
60
60
|
```ruby
|
61
|
-
|
61
|
+
Record.where(color: 'blue')
|
62
62
|
```
|
63
63
|
|
64
|
-
This uses the `:
|
64
|
+
This uses the `:service/v2/records` endpoint, cause `:association_id` was not provided. In addition it would add `?color=blue` to the get parameters.
|
65
65
|
|
66
66
|
```ruby
|
67
|
-
|
67
|
+
Record.where(association_id: 'fq-a81ngsl1d')
|
68
68
|
```
|
69
69
|
|
70
|
-
Uses the `:
|
70
|
+
Uses the `:service/v2/association/:association_id/records` endpoint.
|
71
71
|
|
72
72
|
## Chaining where statements
|
73
73
|
|
@@ -149,7 +149,7 @@ If no record is found an error is raised.
|
|
149
149
|
`find` can also be used to find a single uniqe record with parameters:
|
150
150
|
|
151
151
|
```ruby
|
152
|
-
|
152
|
+
Record.find(association_id: 123, id: 456)
|
153
153
|
```
|
154
154
|
|
155
155
|
`find_by` finds the first record matching the specified conditions.
|
@@ -159,20 +159,37 @@ If no record is found, `nil` is returned.
|
|
159
159
|
`find_by!` raises LHC::NotFound if nothing was found.
|
160
160
|
|
161
161
|
```ruby
|
162
|
-
|
163
|
-
|
162
|
+
Record.find_by(id: 'z12f-3asm3ngals')
|
163
|
+
Record.find_by(id: 'doesntexist') # nil
|
164
164
|
```
|
165
165
|
|
166
166
|
`first` is an alias for finding the first record without parameters.
|
167
167
|
|
168
168
|
```ruby
|
169
|
-
|
169
|
+
Record.first
|
170
170
|
```
|
171
171
|
|
172
172
|
If no record is found, `nil` is returned.
|
173
173
|
|
174
174
|
`first!` raises LHC::NotFound if nothing was found.
|
175
175
|
|
176
|
+
# Find multiple single records in parallel
|
177
|
+
|
178
|
+
In case you want to fetch multiple records by id in parallel, you can also do this with `find`:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
Record.find(1, 2, 3)
|
182
|
+
```
|
183
|
+
|
184
|
+
If you want to inject values for the failing records, that might not have been found, you can inject values for them with error handlers:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
data = Record
|
188
|
+
.handle(LHC::Unauthorized, ->(response) { Record.new(name: 'unknown') })
|
189
|
+
.find(1, 2, 3)
|
190
|
+
data[1].name # 'unknown'
|
191
|
+
```
|
192
|
+
|
176
193
|
## Navigate data
|
177
194
|
|
178
195
|
After fetching [single](#find-single-records) or [multiple](#find-multiple-records) records you can navigate the received data with ease.
|
@@ -219,7 +236,7 @@ You can apply options to the request chain. Those options will be forwarded to t
|
|
219
236
|
`all` fetches all records from the service by doing multiple requests if necessary.
|
220
237
|
|
221
238
|
```ruby
|
222
|
-
data =
|
239
|
+
data = Record.all
|
223
240
|
data.count # 998
|
224
241
|
data.length # 998
|
225
242
|
```
|
@@ -229,26 +246,26 @@ data.length # 998
|
|
229
246
|
`find_each` is a more fine grained way to process single records that are fetched in batches.
|
230
247
|
|
231
248
|
```ruby
|
232
|
-
|
249
|
+
Record.find_each(start: 50, batch_size: 20, params: { has_reviews: true }) do |record|
|
233
250
|
# Iterates over each record. Starts with record nr. 50 and fetches 20 records each batch.
|
234
|
-
|
235
|
-
break if
|
251
|
+
record
|
252
|
+
break if record.some_attribute == some_value
|
236
253
|
end
|
237
254
|
```
|
238
255
|
|
239
256
|
`find_in_batches` is used by `find_each` and processes batches.
|
240
257
|
```ruby
|
241
|
-
|
258
|
+
Record.find_in_batches(start: 50, batch_size: 20, params: { has_reviews: true }) do |records|
|
242
259
|
# Iterates over multiple records (batch size is 20). Starts with record nr. 50 and fetches 20 records each batch.
|
243
|
-
|
244
|
-
break if
|
260
|
+
records
|
261
|
+
break if records.first.name == some_value
|
245
262
|
end
|
246
263
|
```
|
247
264
|
|
248
265
|
## Create records
|
249
266
|
|
250
267
|
```ruby
|
251
|
-
|
268
|
+
record = Record.create(
|
252
269
|
recommended: true,
|
253
270
|
source_id: 'aaa',
|
254
271
|
content_ad_id: '1z-5r1fkaj'
|
@@ -258,9 +275,9 @@ end
|
|
258
275
|
When creation fails, the object contains errors. It provides them through the `errors` attribute:
|
259
276
|
|
260
277
|
```ruby
|
261
|
-
|
262
|
-
|
263
|
-
|
278
|
+
record.errors #<LHS::Errors>
|
279
|
+
record.errors.include?(:ratings) # true
|
280
|
+
record.errors[:ratings] # ['REQUIRED_PROPERTY_VALUE']
|
264
281
|
record.errors.messages # {:ratings=>["REQUIRED_PROPERTY_VALUE"], :recommended=>["REQUIRED_PROPERTY_VALUE"]}
|
265
282
|
record.errors.message # ratings must be set when review or name or review_title is set | The property value is required; it cannot be null, empty, or blank."
|
266
283
|
```
|
@@ -270,8 +287,8 @@ When creation fails, the object contains errors. It provides them through the `e
|
|
270
287
|
Build and persist new items from scratch are done either with `new` or it's alias `build`.
|
271
288
|
|
272
289
|
```ruby
|
273
|
-
|
274
|
-
|
290
|
+
record = Record.new(recommended: true)
|
291
|
+
record.save
|
275
292
|
```
|
276
293
|
|
277
294
|
## Custom setters and getters
|
@@ -280,21 +297,21 @@ Sometimes it is the case that you want to have your custom getters and setters a
|
|
280
297
|
The initializer will now use custom setter if one is defined:
|
281
298
|
|
282
299
|
```ruby
|
283
|
-
class
|
300
|
+
class Record < LHS::Record
|
284
301
|
def ratings=(ratings)
|
285
302
|
_raw[:ratings] = ratings.map { |k, v| { name: k, value: v } }
|
286
303
|
end
|
287
304
|
end
|
288
305
|
|
289
|
-
|
290
|
-
|
306
|
+
record = Record.new(ratings: { quality: 3 }) # <Record{:ratings=>[{:name=>:quality, :value=>3}]}>
|
307
|
+
record.ratings # #<LHS::Data:0x007fc8fa6d4050 ... @_raw=[{:name=>:quality, :value=>3}]>
|
291
308
|
|
292
309
|
```
|
293
310
|
|
294
311
|
If you have an accompanying getter the whole data manipulation would be internal only.
|
295
312
|
|
296
313
|
```ruby
|
297
|
-
class
|
314
|
+
class Record < LHS::Record
|
298
315
|
def ratings=(ratings)
|
299
316
|
_raw[:ratings] = ratings.map { |k, v| { name: k, value: v } }
|
300
317
|
end
|
@@ -304,8 +321,8 @@ class Feedback < LHS::Record
|
|
304
321
|
end
|
305
322
|
end
|
306
323
|
|
307
|
-
|
308
|
-
|
324
|
+
record = Record.new(ratings: { quality: 3 }) # <Record{:ratings=>[{:name=>:quality, :value=>3}]}>
|
325
|
+
record.ratings # {:quality=>3}
|
309
326
|
|
310
327
|
```
|
311
328
|
|
@@ -360,9 +377,9 @@ After include:
|
|
360
377
|
### Two-Level `includes`
|
361
378
|
|
362
379
|
```ruby
|
363
|
-
# a
|
364
|
-
|
365
|
-
|
380
|
+
# a record has a association, which has an entry
|
381
|
+
records = Record.includes(association: :entry).where(has_reviews: true)
|
382
|
+
records.first.association.entry.name # 'Casa Ferlin'
|
366
383
|
```
|
367
384
|
|
368
385
|
### Multiple `includes`
|
@@ -375,7 +392,7 @@ After include:
|
|
375
392
|
claims = Claims.includes([:localch_account, :entry]).where(place_id: 'huU90mB_6vAfUdVz_uDoyA')
|
376
393
|
|
377
394
|
# Two-level with array of includes
|
378
|
-
|
395
|
+
records = Record.includes(campaign: [:entry, :user]).where(has_reviews: true)
|
379
396
|
```
|
380
397
|
|
381
398
|
### Known LHS::Records are used to request linked resources
|
@@ -389,15 +406,15 @@ The [Auth Inteceptor](https://github.com/local-ch/lhc-core-interceptors#auth-int
|
|
389
406
|
```ruby
|
390
407
|
class Favorite < LHS::Record
|
391
408
|
|
392
|
-
endpoint ':
|
393
|
-
endpoint ':
|
409
|
+
endpoint ':service/:user_id/favorites', auth: { basic: { username: 'steve', password: 'can' } }
|
410
|
+
endpoint ':service/:user_id/favorites/:id', auth: { basic: { username: 'steve', password: 'can' } }
|
394
411
|
|
395
412
|
end
|
396
413
|
|
397
414
|
class Place < LHS::Record
|
398
415
|
|
399
|
-
endpoint ':
|
400
|
-
endpoint ':
|
416
|
+
endpoint ':service/v2/places', auth: { basic: { username: 'steve', password: 'can' } }
|
417
|
+
endpoint ':service/v2/places/:id', auth: { basic: { username: 'steve', password: 'can' } }
|
401
418
|
|
402
419
|
end
|
403
420
|
|
@@ -421,7 +438,7 @@ To influence how data is accessed/provied, you can use mappings to either map de
|
|
421
438
|
|
422
439
|
```ruby
|
423
440
|
class LocalEntry < LHS::Record
|
424
|
-
endpoint ':
|
441
|
+
endpoint ':service/v2/local-entries'
|
425
442
|
|
426
443
|
def name
|
427
444
|
addresses.first.business.identities.first.name
|
@@ -436,7 +453,7 @@ Nested records (in nested data) are automaticaly casted when the href matches an
|
|
436
453
|
|
437
454
|
```ruby
|
438
455
|
class Place < LHS::Record
|
439
|
-
endpoint ':
|
456
|
+
endpoint ':service/v2/places'
|
440
457
|
|
441
458
|
def name
|
442
459
|
addresses.first.business.identities.first.name
|
@@ -444,7 +461,7 @@ class Place < LHS::Record
|
|
444
461
|
end
|
445
462
|
|
446
463
|
class Favorite < LHS::Record
|
447
|
-
endpoint ':
|
464
|
+
endpoint ':service/v2/favorites'
|
448
465
|
end
|
449
466
|
|
450
467
|
favorite = Favorite.includes(:place).find(1)
|
@@ -458,7 +475,7 @@ If automatic-detection of nested records does not work, make sure your LHS::Reco
|
|
458
475
|
You can change attributes of LHS::Records:
|
459
476
|
|
460
477
|
```ruby
|
461
|
-
record =
|
478
|
+
record = Record.find(id: 'z12f-3asm3ngals')
|
462
479
|
rcord.recommended = false
|
463
480
|
```
|
464
481
|
|
@@ -467,9 +484,9 @@ You can change attributes of LHS::Records:
|
|
467
484
|
You can persist changes with `save`. `save` will return `false` if persisting fails. `save!` instead will raise an exception.
|
468
485
|
|
469
486
|
```ruby
|
470
|
-
|
471
|
-
|
472
|
-
|
487
|
+
record = Record.find('1z-5r1fkaj')
|
488
|
+
record.recommended = false
|
489
|
+
record.save
|
473
490
|
```
|
474
491
|
|
475
492
|
## Update
|
@@ -479,8 +496,8 @@ You can persist changes with `save`. `save` will return `false` if persisting fa
|
|
479
496
|
`update` always updates the data of the local object first, before it tries to sync with an endpoint. So even if persisting fails, the local object is updated.
|
480
497
|
|
481
498
|
```ruby
|
482
|
-
|
483
|
-
|
499
|
+
record = Record.find('1z-5r1fkaj')
|
500
|
+
record.update(recommended: false)
|
484
501
|
```
|
485
502
|
|
486
503
|
## Destroy
|
@@ -488,8 +505,8 @@ feedback.update(recommended: false)
|
|
488
505
|
You can delete records remotely by calling `destroy` on an LHS::Record.
|
489
506
|
|
490
507
|
```ruby
|
491
|
-
|
492
|
-
|
508
|
+
record = Record.find('1z-5r1fkaj')
|
509
|
+
record.destroy
|
493
510
|
```
|
494
511
|
|
495
512
|
## Validation
|
@@ -500,7 +517,7 @@ The specific endpoint has to support validations with the `persist=false` parame
|
|
500
517
|
|
501
518
|
```ruby
|
502
519
|
class User < LHS::Record
|
503
|
-
endpoint ':
|
520
|
+
endpoint ':service/v2/users', validates: true
|
504
521
|
end
|
505
522
|
|
506
523
|
user = User.build(email: 'im not an email address')
|
@@ -513,7 +530,7 @@ In case endpoints define other parameter names for validation like `publish` you
|
|
513
530
|
|
514
531
|
```ruby
|
515
532
|
class User < LHS::Record
|
516
|
-
endpoint ':
|
533
|
+
endpoint ':service/v2/users', validates: 'publish'
|
517
534
|
end
|
518
535
|
```
|
519
536
|
|
@@ -595,7 +612,7 @@ You can use chainable pagination in combination with query chains:
|
|
595
612
|
|
596
613
|
```ruby
|
597
614
|
class Record < LHS::Record
|
598
|
-
endpoint ':
|
615
|
+
endpoint ':service/records'
|
599
616
|
end
|
600
617
|
Record.page(3).per(20).where(color: 'blue')
|
601
618
|
# /records?offset=40&limit=20&color=blue
|
@@ -605,7 +622,7 @@ The applied pagination strategy depends on the actual configured pagination, so
|
|
605
622
|
|
606
623
|
```ruby
|
607
624
|
class Record < LHS::Record
|
608
|
-
endpoint ':
|
625
|
+
endpoint ':service/records'
|
609
626
|
configuration pagination_strategy: 'page'
|
610
627
|
end
|
611
628
|
Record.page(3).per(20).where(color: 'blue')
|
@@ -614,7 +631,7 @@ The applied pagination strategy depends on the actual configured pagination, so
|
|
614
631
|
|
615
632
|
```ruby
|
616
633
|
class Record < LHS::Record
|
617
|
-
endpoint ':
|
634
|
+
endpoint ':service/records'
|
618
635
|
configuration pagination_strategy: 'start'
|
619
636
|
end
|
620
637
|
Record.page(3).per(20).where(color: 'blue')
|
data/lhs.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.requirements << 'Ruby >= 2.0.0'
|
21
21
|
s.required_ruby_version = '>= 2.0.0'
|
22
22
|
|
23
|
-
s.add_dependency 'lhc', '>= 3.
|
23
|
+
s.add_dependency 'lhc', '>= 3.6.0'
|
24
24
|
s.add_dependency 'lhc-core-interceptors', '>= 2.0.1'
|
25
25
|
|
26
26
|
s.add_development_dependency 'rspec-rails', '>= 3.0.0'
|
@@ -161,8 +161,10 @@ class LHS::Record
|
|
161
161
|
push ErrorHandling.new(error_class => handler)
|
162
162
|
end
|
163
163
|
|
164
|
-
def find(args)
|
165
|
-
|
164
|
+
def find(*args)
|
165
|
+
options = chain_options
|
166
|
+
options = options.merge(error_handler: chain_error_handler) if chain_error_handler.any?
|
167
|
+
@record_class.find(*args.push(options))
|
166
168
|
end
|
167
169
|
|
168
170
|
def find_by(params = {})
|
@@ -198,12 +200,11 @@ class LHS::Record
|
|
198
200
|
end
|
199
201
|
|
200
202
|
def resolve
|
203
|
+
options = chain_options
|
204
|
+
options = options.merge(params: chain_parameters.merge(chain_pagination))
|
205
|
+
options = options.merge(error_handler: chain_error_handler) if chain_error_handler.any?
|
201
206
|
@resolved ||= @record_class.new(
|
202
|
-
@record_class.request(
|
203
|
-
chain_options
|
204
|
-
.merge(params: chain_parameters.merge(chain_pagination))
|
205
|
-
.merge(error_handling: chain_error_handling)
|
206
|
-
)
|
207
|
+
@record_class.request(options)
|
207
208
|
)
|
208
209
|
end
|
209
210
|
|
@@ -223,7 +224,7 @@ class LHS::Record
|
|
223
224
|
merge_links _links.select { |link| link.is_a? Option }
|
224
225
|
end
|
225
226
|
|
226
|
-
def
|
227
|
+
def chain_error_handler
|
227
228
|
_links.select { |link| link.is_a? ErrorHandling }
|
228
229
|
end
|
229
230
|
|
@@ -7,22 +7,36 @@ class LHS::Record
|
|
7
7
|
|
8
8
|
module ClassMethods
|
9
9
|
# Find a single uniqe record
|
10
|
-
def find(args
|
10
|
+
def find(*args)
|
11
|
+
args, options = process_args(args)
|
11
12
|
data =
|
12
|
-
if args.is_a?
|
13
|
+
if args.is_a? Array
|
14
|
+
find_in_parallel(args, options)
|
15
|
+
elsif args.is_a? Hash
|
13
16
|
find_with_parameters(args, options)
|
14
17
|
else
|
15
18
|
find_by_id(args, options)
|
16
19
|
end
|
17
|
-
return data
|
20
|
+
return data if data.is_a?(Array) || !data._record
|
18
21
|
data._record.new(data)
|
19
22
|
end
|
20
23
|
|
21
24
|
private
|
22
25
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
+
def process_args(args)
|
27
|
+
if args.length == 1
|
28
|
+
args = args.first
|
29
|
+
elsif args.length == 2 && args.last.is_a?(Hash)
|
30
|
+
options = args.pop if args.last.is_a?(Hash)
|
31
|
+
args = args.first
|
32
|
+
elsif args.last.is_a?(Hash)
|
33
|
+
options = args.pop
|
34
|
+
end
|
35
|
+
options ||= nil
|
36
|
+
[args, options]
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_unique_item!(data)
|
26
40
|
if data._proxy.is_a?(LHS::Collection)
|
27
41
|
fail LHC::NotFound.new('Requested unique item. Multiple were found.', data._request.response) if data.length > 1
|
28
42
|
data.first || fail(LHC::NotFound.new('No item was found.', data._request.response))
|
@@ -31,9 +45,26 @@ class LHS::Record
|
|
31
45
|
end
|
32
46
|
end
|
33
47
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
48
|
+
def find_with_parameters(args, options = {})
|
49
|
+
data = request(request_options(args, options))
|
50
|
+
get_unique_item!(data)
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_by_id(args, options = {})
|
54
|
+
request(request_options(args, options))
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_in_parallel(args, options)
|
58
|
+
options = args.map { |argument| request_options(argument, options) }
|
59
|
+
request(options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def request_options(args, options)
|
63
|
+
if args.is_a? Hash
|
64
|
+
(options || {}).merge(params: args)
|
65
|
+
else
|
66
|
+
(options || {}).merge(params: { id: args })
|
67
|
+
end
|
37
68
|
end
|
38
69
|
end
|
39
70
|
end
|
@@ -7,6 +7,7 @@ class LHS::Record
|
|
7
7
|
|
8
8
|
module ClassMethods
|
9
9
|
def request(options)
|
10
|
+
options ||= {}
|
10
11
|
if options.is_a? Array
|
11
12
|
multiple_requests(options)
|
12
13
|
else
|
@@ -181,12 +182,12 @@ class LHS::Record
|
|
181
182
|
next unless option.present?
|
182
183
|
process_options(option, find_endpoint(option[:params]))
|
183
184
|
end
|
184
|
-
data = LHC.request(options.compact).map
|
185
|
-
|
186
|
-
unless data.empty?
|
187
|
-
data = LHS::Data.new(data, nil, self)
|
188
|
-
handle_includes(including, data, referencing) if including
|
185
|
+
data = LHC.request(options.compact).map do |response|
|
186
|
+
LHS::Data.new(response.body, nil, self, response.request)
|
189
187
|
end
|
188
|
+
data = restore_with_nils(data, locate_nils(options)) # nil objects in data provide location information for mapping
|
189
|
+
data = LHS::Data.new(data, nil, self)
|
190
|
+
handle_includes(including, data, referencing) if including && !data.empty?
|
190
191
|
data
|
191
192
|
end
|
192
193
|
|
@@ -217,6 +218,7 @@ class LHS::Record
|
|
217
218
|
# Merge explicit params and take configured endpoints options as base
|
218
219
|
def process_options(options, endpoint)
|
219
220
|
options[:params].deep_symbolize_keys! if options[:params]
|
221
|
+
options[:error_handler] = merge_error_handlers(options[:error_handler]) if options[:error_handler]
|
220
222
|
options = (endpoint.options || {}).merge(options)
|
221
223
|
options[:url] = compute_url!(options[:params]) unless options.key?(:url)
|
222
224
|
merge_explicit_params!(options[:params])
|
@@ -224,6 +226,23 @@ class LHS::Record
|
|
224
226
|
options
|
225
227
|
end
|
226
228
|
|
229
|
+
# LHC supports only one error handler, merge all error handlers to one
|
230
|
+
# and reraise
|
231
|
+
def merge_error_handlers(handlers)
|
232
|
+
lambda do |response|
|
233
|
+
return_data = nil
|
234
|
+
error_class = LHC::Error.find(response)
|
235
|
+
error = error_class.new(error_class, response)
|
236
|
+
handlers = handlers.to_a.select { |error_handler| error.is_a? error_handler.class }
|
237
|
+
fail(error) unless handlers.any?
|
238
|
+
handlers.each do |handler|
|
239
|
+
handlers_return = handler.call(response)
|
240
|
+
return_data = handlers_return if handlers_return.present?
|
241
|
+
end
|
242
|
+
return return_data
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
227
246
|
def record_for_options(options)
|
228
247
|
records = []
|
229
248
|
if options.is_a?(Array)
|
@@ -243,25 +262,10 @@ class LHS::Record
|
|
243
262
|
options ||= {}
|
244
263
|
options = options.dup
|
245
264
|
endpoint = find_endpoint(options[:params])
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
handle_includes(including, data, referencing) if including
|
251
|
-
data
|
252
|
-
rescue => error
|
253
|
-
handle_error(error, error_handling)
|
254
|
-
nil
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
def handle_error(error, error_handling)
|
259
|
-
error_handlers = (error_handling || []).select { |error_handler| error.is_a? error_handler.class }
|
260
|
-
if error_handlers.any?
|
261
|
-
error_handlers.each { |handler| handler.call(error) }
|
262
|
-
else
|
263
|
-
fail error
|
264
|
-
end
|
265
|
+
response = LHC.request(process_options(options, endpoint))
|
266
|
+
data = LHS::Data.new(response.body, nil, self, response.request, endpoint)
|
267
|
+
handle_includes(including, data, referencing) if including
|
268
|
+
data
|
265
269
|
end
|
266
270
|
|
267
271
|
def url_option_for(item, key = nil)
|
data/lib/lhs/item.rb
CHANGED
@@ -26,8 +26,10 @@ class LHS::Item < LHS::Proxy
|
|
26
26
|
return set(name, args.try(&:first)) if name.to_s[/=$/]
|
27
27
|
name = args.first if name == :[]
|
28
28
|
value = _data._raw[name.to_s]
|
29
|
-
value
|
30
|
-
|
29
|
+
if value.nil? && _data._raw.present?
|
30
|
+
value = _data._raw[name.to_sym]
|
31
|
+
value ||= _data._raw[name.to_s.classify.to_sym]
|
32
|
+
end
|
31
33
|
if value.is_a?(Hash)
|
32
34
|
handle_hash(value)
|
33
35
|
elsif value.is_a?(Array)
|
data/lib/lhs/version.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe LHS::Record do
|
4
|
+
before(:each) do
|
5
|
+
class Record < LHS::Record
|
6
|
+
endpoint 'http://datastore/records/:id'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'find in parallel' do
|
11
|
+
before(:each) do
|
12
|
+
stub_request(:get, "http://datastore/records/1").to_return(status: 200, body: { id: 1 }.to_json)
|
13
|
+
stub_request(:get, "http://datastore/records/3").to_return(status: 200, body: { id: 3 }.to_json)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'finds records in parallel' do
|
17
|
+
stub_request(:get, "http://datastore/records/2").to_return(status: 200, body: { id: 2 }.to_json)
|
18
|
+
allow(Record).to receive(:request).and_call_original
|
19
|
+
data = Record.find([1, 2, 3])
|
20
|
+
expect(Record).to have_received(:request).once
|
21
|
+
expect(data[0].id).to eq 1
|
22
|
+
expect(data[1].id).to eq 2
|
23
|
+
expect(data[2].id).to eq 3
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'raises an exeption if one of the parallel request fails' do
|
27
|
+
stub_request(:get, "http://datastore/records/2").to_return(status: 401)
|
28
|
+
expect(-> { Record.find([1, 2, 3]) }).to raise_error(LHC::Unauthorized)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'applies error handlers from the chain and returns whatever the error handler returns' do
|
32
|
+
stub_request(:get, "http://datastore/records/2").to_return(status: 401)
|
33
|
+
data = Record
|
34
|
+
.handle(LHC::Unauthorized, ->(_response) { Record.new(name: 'unknown') })
|
35
|
+
.find(1, 2, 3)
|
36
|
+
expect(data[1].name).to eq 'unknown'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lhs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- https://github.com/local-ch/lhs/graphs/contributors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lhc
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 3.
|
19
|
+
version: 3.6.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 3.
|
26
|
+
version: 3.6.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: lhc-core-interceptors
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -298,6 +298,7 @@ files:
|
|
298
298
|
- spec/record/find_by_spec.rb
|
299
299
|
- spec/record/find_each_spec.rb
|
300
300
|
- spec/record/find_in_batches_spec.rb
|
301
|
+
- spec/record/find_in_parallel_spec.rb
|
301
302
|
- spec/record/find_spec.rb
|
302
303
|
- spec/record/first_spec.rb
|
303
304
|
- spec/record/immutable_chains_spec.rb
|
@@ -351,7 +352,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
351
352
|
requirements:
|
352
353
|
- Ruby >= 2.0.0
|
353
354
|
rubyforge_project:
|
354
|
-
rubygems_version: 2.6
|
355
|
+
rubygems_version: 2.4.6
|
355
356
|
signing_key:
|
356
357
|
specification_version: 4
|
357
358
|
summary: Rails gem providing an easy, active-record-like interface for http json services
|
@@ -443,6 +444,7 @@ test_files:
|
|
443
444
|
- spec/record/find_by_spec.rb
|
444
445
|
- spec/record/find_each_spec.rb
|
445
446
|
- spec/record/find_in_batches_spec.rb
|
447
|
+
- spec/record/find_in_parallel_spec.rb
|
446
448
|
- spec/record/find_spec.rb
|
447
449
|
- spec/record/first_spec.rb
|
448
450
|
- spec/record/immutable_chains_spec.rb
|