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