lhs 3.4.2 → 4.0.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/README.md +79 -25
- data/lib/lhs/collection.rb +5 -14
- data/lib/lhs/concerns/collection/internal_collection.rb +1 -1
- data/lib/lhs/concerns/record/all.rb +14 -9
- data/lib/lhs/concerns/record/configuration.rb +12 -6
- data/lib/lhs/concerns/record/find.rb +1 -1
- data/lib/lhs/concerns/record/pagination.rb +15 -24
- data/lib/lhs/concerns/record/request.rb +7 -7
- data/lib/lhs/data.rb +4 -3
- data/lib/lhs/pagination.rb +101 -0
- data/lib/lhs/record.rb +1 -0
- data/lib/lhs/version.rb +1 -1
- data/spec/collection/configurable_spec.rb +18 -21
- data/spec/record/find_in_batches_spec.rb +7 -7
- data/spec/record/paginatable_collection_spec.rb +278 -0
- data/spec/record/pagination_spec.rb +9 -0
- data/spec/support/cleanup_records.rb +1 -0
- metadata +5 -4
- data/spec/record/all_spec.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a00b04e289a4748446105fc8701ddabc87f58d77
|
4
|
+
data.tar.gz: 44f10bf8b376e4d849506074f39fd4373e7dc13b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15b4da5598314dc9ad1029e1c2b9562ef0610bb4f2f240e6c1be7d813a49e2e94dbebed77fd4f0a8c699998970069274a4b4a0939bc49d52769b416315259873
|
7
|
+
data.tar.gz: 83a9e1c944011beaea79e4a0b3c6cebaef12305234a91ebc0ed94741567a5dd919f9953af7d7d8e485bc944326d914f462ff91fa75507c4873ee42a2b752fa00
|
data/README.md
CHANGED
@@ -111,9 +111,11 @@ If no record is found, `nil` is returned.
|
|
111
111
|
```ruby
|
112
112
|
data = Feedback.all
|
113
113
|
data.count # 998
|
114
|
-
data.
|
114
|
+
data.length # 998
|
115
115
|
```
|
116
116
|
|
117
|
+
[Count vs. Length](#count-vs-length)
|
118
|
+
|
117
119
|
`find_each` is a more fine grained way to process single records that are fetched in batches.
|
118
120
|
|
119
121
|
```ruby
|
@@ -355,46 +357,90 @@ unless user.valid?
|
|
355
357
|
end
|
356
358
|
```
|
357
359
|
|
358
|
-
##
|
360
|
+
## How to work with paginated APIs
|
361
|
+
|
362
|
+
LHS supports paginated APIs and it also supports various pagination strategies and by providing configuration possibilities.
|
359
363
|
|
360
|
-
|
364
|
+
LHS diffentiates between the *pagination strategy* (how items/pages are navigated) itself and *pagination keys* (how stuff is named).
|
361
365
|
|
366
|
+
*Example 1 "offset"-strategy (default configuration)*
|
362
367
|
```ruby
|
363
|
-
|
364
|
-
|
365
|
-
|
368
|
+
# API response
|
369
|
+
{
|
370
|
+
items: [{...}, ...]
|
371
|
+
total: 300,
|
372
|
+
limit: 100,
|
373
|
+
offset: 0
|
374
|
+
}
|
375
|
+
# Next 'pages' are navigated with offset: 100, offset: 200, ...
|
376
|
+
|
377
|
+
# Nothing has to be configured in LHS because this is default pagination naming and strategy
|
378
|
+
class Results < LHS::Record
|
379
|
+
endpoint 'results'
|
380
|
+
end
|
366
381
|
```
|
367
382
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
383
|
+
*Example 2 "page"-strategy and some naming configuration*
|
384
|
+
```ruby
|
385
|
+
# API response
|
386
|
+
{
|
387
|
+
docs: [{...}, ...]
|
388
|
+
totalPages: 3,
|
389
|
+
limit: 100,
|
390
|
+
page: 1
|
391
|
+
}
|
392
|
+
# Next 'pages' are navigated with page: 1, offset: 2, ...
|
393
|
+
|
394
|
+
# How LHS has to be configured
|
395
|
+
class Results < LHS::Record
|
396
|
+
configuration items_key: 'docs', total_key: 'totalPages', pagination_key: 'page', pagination_strategy: 'page'
|
397
|
+
endpoint 'results'
|
398
|
+
end
|
399
|
+
```
|
377
400
|
|
401
|
+
*Example 3 "start"-strategy and naming configuration*
|
378
402
|
```ruby
|
379
|
-
|
380
|
-
|
381
|
-
|
403
|
+
# API response
|
404
|
+
{
|
405
|
+
results: [{...}, ...]
|
406
|
+
total: 300,
|
407
|
+
badgeSize: 100,
|
408
|
+
startAt: 1
|
409
|
+
}
|
410
|
+
# Next 'pages' are navigated with startWith: 101, startWith: 201, ...
|
411
|
+
|
412
|
+
# How LHS has to be configured
|
413
|
+
class Results < LHS::Record
|
414
|
+
configuration items_key: 'results', limit_key: 'badgeSize', pagination_key: 'startAt', pagination_strategy: 'start'
|
415
|
+
endpoint 'results'
|
382
416
|
end
|
383
417
|
```
|
384
|
-
|
385
|
-
`
|
386
|
-
|
387
|
-
`
|
418
|
+
|
419
|
+
`items_key` key used to determine items of the current page (e.g. `docs`, `items`, etc.).
|
420
|
+
|
421
|
+
`limit_key` key used to work with page limits (e.g. `size`, `limit`, etc.)
|
422
|
+
|
423
|
+
`pagination_key` key used to paginate multiple pages (e.g. `offset`, `page`, `startAt` etc.).
|
424
|
+
|
425
|
+
`pagination_strategy` used to configure the strategy used for navigating (e.g. `offset`, `page`, `start`, etc.).
|
426
|
+
|
427
|
+
`total_key` key used to determine the total amount of items (e.g. `total`, `totalResults`, etc.).
|
428
|
+
|
429
|
+
In case of paginated resources it's important to know the difference between [count vs. length](#count-vs-length)
|
388
430
|
|
389
431
|
### Partial Kaminari support
|
390
432
|
|
391
433
|
LHS implements an interface that makes it partially working with Kaminari.
|
392
434
|
|
393
|
-
For example, you can use kaminari to render paginations based on LHS Records:
|
435
|
+
The kaminari’s page parameter is in params[:page]. For example, you can use kaminari to render paginations based on LHS Records. Typically, your code will look like this:
|
394
436
|
|
395
437
|
```ruby
|
396
438
|
# controller
|
397
|
-
|
439
|
+
params[:page] = 0 if params[:page].nil?
|
440
|
+
page = params[:page].to_i
|
441
|
+
limit = 100
|
442
|
+
offset = (page - 1) * limit
|
443
|
+
@items = Record.where({ limit: limit, offset: offset }))
|
398
444
|
```
|
399
445
|
|
400
446
|
```ruby
|
@@ -402,7 +448,7 @@ For example, you can use kaminari to render paginations based on LHS Records:
|
|
402
448
|
= paginate @items
|
403
449
|
```
|
404
450
|
|
405
|
-
|
451
|
+
## form_for Helper
|
406
452
|
Rails `form_for` view-helper can be used in combination with instances of LHS::Record to autogenerate forms:
|
407
453
|
```
|
408
454
|
<%= form_for(@instance, url: '/create') do |f| %>
|
@@ -411,3 +457,11 @@ Rails `form_for` view-helper can be used in combination with instances of LHS::R
|
|
411
457
|
<%= f.submit "Create" %>
|
412
458
|
<% end %>
|
413
459
|
```
|
460
|
+
|
461
|
+
## Count vs. Length
|
462
|
+
|
463
|
+
The behaviour of `count` and `length` is based on ActiveRecord's behaviour.
|
464
|
+
|
465
|
+
`count` Determine the number of elements by taking the number of total elements that is provided by the endpoint/api.
|
466
|
+
|
467
|
+
`length` This returns the number of elements loaded from an endpoint/api. In case of paginated resources this can be different to count, as it depends on how many pages have been loaded.
|
data/lib/lhs/collection.rb
CHANGED
@@ -6,19 +6,12 @@ Dir[File.dirname(__FILE__) + '/concerns/collection/*.rb'].each { |file| require
|
|
6
6
|
class LHS::Collection < LHS::Proxy
|
7
7
|
include InternalCollection
|
8
8
|
|
9
|
-
delegate :select, to: :_collection
|
10
|
-
delegate :_record, to: :_data
|
9
|
+
delegate :select, :length, :size, to: :_collection
|
10
|
+
delegate :_record, :_raw, to: :_data
|
11
|
+
delegate :limit, :count, :total, :limit, :offset, :current_page, :start, to: :_pagination
|
11
12
|
|
12
|
-
def
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
def limit
|
17
|
-
_data._raw[_record.limit_key]
|
18
|
-
end
|
19
|
-
|
20
|
-
def offset
|
21
|
-
_data._raw[_record.offset_key]
|
13
|
+
def _pagination
|
14
|
+
_record.pagination(_data)
|
22
15
|
end
|
23
16
|
|
24
17
|
def href
|
@@ -31,8 +24,6 @@ class LHS::Collection < LHS::Proxy
|
|
31
24
|
Collection.new(raw, _data, _record)
|
32
25
|
end
|
33
26
|
|
34
|
-
delegate :_raw, to: :_data
|
35
|
-
|
36
27
|
protected
|
37
28
|
|
38
29
|
def method_missing(name, *args, &block)
|
@@ -12,7 +12,7 @@ class LHS::Collection < LHS::Proxy
|
|
12
12
|
include Enumerable
|
13
13
|
|
14
14
|
attr_accessor :raw
|
15
|
-
delegate :last, :sample, :[], :present?, :blank?, :empty?, to: :raw
|
15
|
+
delegate :length, :size, :last, :sample, :[], :present?, :blank?, :empty?, to: :raw
|
16
16
|
|
17
17
|
def initialize(raw, parent, record)
|
18
18
|
self.raw = raw
|
@@ -31,18 +31,23 @@ class LHS::Record
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def request_all_the_rest(data, params)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
pagination = data._record.pagination(data)
|
35
|
+
if pagination.pages_left
|
36
|
+
last_data = data
|
37
|
+
pagination.pages_left.times do |_index|
|
38
|
+
return data if last_data.length.zero?
|
39
|
+
pagination = data._record.pagination(last_data)
|
40
|
+
response_data = request(
|
41
41
|
params: params.merge(
|
42
|
-
data._record.limit_key => limit,
|
43
|
-
data._record.
|
42
|
+
data._record.limit_key => pagination.limit,
|
43
|
+
data._record.pagination_key => pagination.next_offset
|
44
44
|
)
|
45
45
|
)
|
46
|
+
data._raw[items_key].concat all_items_from response_data
|
47
|
+
data._raw[limit_key] = response_data._raw[limit_key]
|
48
|
+
data._raw[total_key] = response_data._raw[total_key]
|
49
|
+
data._raw[pagination_key] = response_data._raw[pagination_key]
|
50
|
+
last_data = response_data
|
46
51
|
end
|
47
52
|
end
|
48
53
|
end
|
@@ -11,23 +11,29 @@ class LHS::Record
|
|
11
11
|
|
12
12
|
module ClassMethods
|
13
13
|
def configuration(args)
|
14
|
-
@configuration
|
14
|
+
@configuration = args.freeze || {}
|
15
15
|
end
|
16
16
|
|
17
17
|
def items_key
|
18
|
-
@configuration.try(:[], :
|
18
|
+
@configuration.try(:[], :items_key) || :items
|
19
19
|
end
|
20
20
|
|
21
21
|
def limit_key
|
22
|
-
@configuration.try(:[], :
|
22
|
+
@configuration.try(:[], :limit_key) || :limit
|
23
23
|
end
|
24
24
|
|
25
25
|
def total_key
|
26
|
-
@configuration.try(:[], :
|
26
|
+
@configuration.try(:[], :total_key) || :total
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
29
|
+
# Key used for determine current page
|
30
|
+
def pagination_key
|
31
|
+
@configuration.try(:[], :pagination_key) || :offset
|
32
|
+
end
|
33
|
+
|
34
|
+
# Strategy used for calculationg next pages and navigate pages
|
35
|
+
def pagination_strategy
|
36
|
+
@configuration.try(:[], :pagination_strategy) || :offset
|
31
37
|
end
|
32
38
|
end
|
33
39
|
end
|
@@ -23,7 +23,7 @@ class LHS::Record
|
|
23
23
|
def find_with_parameters(params)
|
24
24
|
data = request(params: params)
|
25
25
|
if data._proxy.is_a?(LHS::Collection)
|
26
|
-
fail LHC::NotFound.new('Requested unique item. Multiple were found.', data._request.response) if data.
|
26
|
+
fail LHC::NotFound.new('Requested unique item. Multiple were found.', data._request.response) if data.length > 1
|
27
27
|
data.first || fail(LHC::NotFound.new('No item was found.', data._request.response))
|
28
28
|
else
|
29
29
|
data
|
@@ -4,33 +4,24 @@ class LHS::Record
|
|
4
4
|
|
5
5
|
module Pagination
|
6
6
|
extend ActiveSupport::Concern
|
7
|
+
# Kaminari-Interface
|
8
|
+
delegate :current_page, :first_page, :last_page, :prev_page, :next_page, :limit_value, :total_pages, to: :_pagination
|
7
9
|
|
8
|
-
def
|
9
|
-
|
10
|
+
def _pagination
|
11
|
+
self.class.pagination(_data)
|
10
12
|
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def next_page
|
25
|
-
current_page + 1
|
26
|
-
end
|
27
|
-
|
28
|
-
def limit_value
|
29
|
-
limit
|
30
|
-
end
|
31
|
-
|
32
|
-
def total_pages
|
33
|
-
total / limit
|
14
|
+
module ClassMethods
|
15
|
+
def pagination(data)
|
16
|
+
case data._record.pagination_strategy.to_sym
|
17
|
+
when :page
|
18
|
+
PagePagination.new(data)
|
19
|
+
when :start
|
20
|
+
StartPagination.new(data)
|
21
|
+
else
|
22
|
+
OffsetPagination.new(data)
|
23
|
+
end
|
24
|
+
end
|
34
25
|
end
|
35
26
|
end
|
36
27
|
end
|
@@ -92,7 +92,7 @@ class LHS::Record
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def multiple_requests(options)
|
95
|
-
options = options.map { |option| process_options(option) }
|
95
|
+
options = options.map { |option| process_options(option, find_endpoint(option[:params])) }
|
96
96
|
responses = LHC.request(options)
|
97
97
|
data = responses.map { |response| LHS::Data.new(response.body, nil, self, response.request) }
|
98
98
|
data = LHS::Data.new(data, nil, self)
|
@@ -107,11 +107,8 @@ class LHS::Record
|
|
107
107
|
end
|
108
108
|
|
109
109
|
# Merge explicit params and take configured endpoints options as base
|
110
|
-
def process_options(options)
|
111
|
-
options ||= {}
|
112
|
-
options = options.dup
|
110
|
+
def process_options(options, endpoint)
|
113
111
|
options[:params].deep_symbolize_keys! if options[:params]
|
114
|
-
endpoint = find_endpoint(options[:params])
|
115
112
|
options = (endpoint.options || {}).merge(options)
|
116
113
|
options[:url] = compute_url!(options[:params]) unless options.key?(:url)
|
117
114
|
merge_explicit_params!(options[:params])
|
@@ -135,8 +132,11 @@ class LHS::Record
|
|
135
132
|
end
|
136
133
|
|
137
134
|
def single_request(options)
|
138
|
-
|
139
|
-
|
135
|
+
options ||= {}
|
136
|
+
options = options.dup
|
137
|
+
endpoint = find_endpoint(options[:params])
|
138
|
+
response = LHC.request(process_options(options, endpoint))
|
139
|
+
data = LHS::Data.new(response.body, nil, self, response.request, endpoint)
|
140
140
|
handle_includes(including, data) if including
|
141
141
|
data
|
142
142
|
end
|
data/lib/lhs/data.rb
CHANGED
@@ -5,17 +5,18 @@ Dir[File.dirname(__FILE__) + '/concerns/data/*.rb'].each { |file| require file }
|
|
5
5
|
class LHS::Data
|
6
6
|
include Json
|
7
7
|
|
8
|
-
delegate :instance_methods, :items_key, :limit_key, :total_key, :
|
8
|
+
delegate :instance_methods, :items_key, :limit_key, :total_key, :pagination_key, to: :class
|
9
9
|
|
10
10
|
# prevent clashing with attributes of underlying data
|
11
|
-
attr_accessor :_proxy, :_raw, :_parent, :_record, :_request
|
11
|
+
attr_accessor :_proxy, :_raw, :_parent, :_record, :_request, :_endpoint
|
12
12
|
|
13
|
-
def initialize(input, parent = nil, record = nil, request = nil)
|
13
|
+
def initialize(input, parent = nil, record = nil, request = nil, endpoint = nil)
|
14
14
|
self._raw = raw_from_input(input)
|
15
15
|
self._parent = parent
|
16
16
|
self._record = record
|
17
17
|
self._proxy = proxy_from_input(input)
|
18
18
|
self._request = request
|
19
|
+
self._endpoint = endpoint
|
19
20
|
end
|
20
21
|
|
21
22
|
# merging data
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Pagination is used to navigate paginateable collections
|
2
|
+
class Pagination
|
3
|
+
|
4
|
+
delegate :_record, to: :data
|
5
|
+
attr_accessor :data
|
6
|
+
|
7
|
+
def initialize(data)
|
8
|
+
self.data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
# as standard in Rails' ActiveRecord count is not summing up, but using the number provided from data source
|
12
|
+
def count
|
13
|
+
total
|
14
|
+
end
|
15
|
+
|
16
|
+
def total
|
17
|
+
data._raw[_record.total_key.to_sym]
|
18
|
+
end
|
19
|
+
|
20
|
+
def limit
|
21
|
+
data._raw[_record.limit_key.to_sym] || LHS::Record::DEFAULT_LIMIT
|
22
|
+
end
|
23
|
+
|
24
|
+
def offset
|
25
|
+
data._raw[_record.pagination_key.to_sym]
|
26
|
+
end
|
27
|
+
alias current_page offset
|
28
|
+
alias start offset
|
29
|
+
|
30
|
+
def pages_left
|
31
|
+
total_pages - current_page
|
32
|
+
end
|
33
|
+
|
34
|
+
def next_offset
|
35
|
+
fail 'to be implemented in subclass'
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_page
|
39
|
+
fail 'to be implemented in subclass'
|
40
|
+
end
|
41
|
+
|
42
|
+
def first_page
|
43
|
+
1
|
44
|
+
end
|
45
|
+
|
46
|
+
def last_page
|
47
|
+
total_pages
|
48
|
+
end
|
49
|
+
|
50
|
+
def prev_page
|
51
|
+
current_page - 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def next_page
|
55
|
+
current_page + 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def limit_value
|
59
|
+
limit
|
60
|
+
end
|
61
|
+
|
62
|
+
def total_pages
|
63
|
+
(total.to_f / limit).ceil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class PagePagination < Pagination
|
68
|
+
|
69
|
+
def current_page
|
70
|
+
offset
|
71
|
+
end
|
72
|
+
|
73
|
+
def next_offset
|
74
|
+
current_page + 1
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
class StartPagination < Pagination
|
80
|
+
|
81
|
+
def current_page
|
82
|
+
(offset + limit - 1) / limit
|
83
|
+
end
|
84
|
+
|
85
|
+
def next_offset
|
86
|
+
offset + limit
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
class OffsetPagination < Pagination
|
92
|
+
|
93
|
+
def current_page
|
94
|
+
(offset + limit) / limit
|
95
|
+
end
|
96
|
+
|
97
|
+
def next_offset
|
98
|
+
offset + limit
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
data/lib/lhs/record.rb
CHANGED
data/lib/lhs/version.rb
CHANGED
@@ -4,32 +4,29 @@ describe LHS::Collection do
|
|
4
4
|
let(:search) { 'http://local.ch/search' }
|
5
5
|
let(:limit) { 10 }
|
6
6
|
let(:total) { 20 }
|
7
|
-
let(:offset) { 0 }
|
8
|
-
let(:first_response_data) do
|
9
|
-
{
|
10
|
-
docs: (1..10).to_a,
|
11
|
-
start: offset,
|
12
|
-
size: limit,
|
13
|
-
totalResults: total
|
14
|
-
}
|
15
|
-
end
|
16
|
-
let(:second_response_data) do
|
17
|
-
{
|
18
|
-
docs: (11..20).to_a,
|
19
|
-
start: offset,
|
20
|
-
size: limit,
|
21
|
-
totalResults: total
|
22
|
-
}
|
23
|
-
end
|
24
7
|
|
25
8
|
before(:each) do
|
26
9
|
LHC.config.placeholder('search', search)
|
27
10
|
class Search < LHS::Record
|
28
|
-
configuration
|
11
|
+
configuration items_key: :docs, limit_key: :size, pagination_key: :start, pagination_strategy: :start, total_key: :totalResults
|
29
12
|
endpoint ':search/:type'
|
30
13
|
end
|
31
|
-
stub_request(:get, "http://local.ch/search/phonebook?size=10").to_return(
|
32
|
-
|
14
|
+
stub_request(:get, "http://local.ch/search/phonebook?size=10").to_return(
|
15
|
+
body: {
|
16
|
+
docs: (1..10).to_a,
|
17
|
+
start: 1,
|
18
|
+
size: limit,
|
19
|
+
totalResults: total
|
20
|
+
}.to_json
|
21
|
+
)
|
22
|
+
stub_request(:get, "http://local.ch/search/phonebook?size=10&start=11").to_return(
|
23
|
+
body: {
|
24
|
+
docs: (11..20).to_a,
|
25
|
+
start: 11,
|
26
|
+
size: limit,
|
27
|
+
totalResults: total
|
28
|
+
}.to_json
|
29
|
+
)
|
33
30
|
end
|
34
31
|
|
35
32
|
context 'lets you configure how to deal with collections' do
|
@@ -38,7 +35,7 @@ describe LHS::Collection do
|
|
38
35
|
expect(results.count).to eq total
|
39
36
|
expect(results.total).to eq total
|
40
37
|
expect(results.limit).to eq limit
|
41
|
-
expect(results.offset).to eq
|
38
|
+
expect(results.offset).to eq 11
|
42
39
|
end
|
43
40
|
end
|
44
41
|
end
|
@@ -32,13 +32,13 @@ describe LHS::Collection do
|
|
32
32
|
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=201").to_return(status: 200, body: api_response((201..300).to_a, 201))
|
33
33
|
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=301").to_return(status: 200, body: api_response((301..400).to_a, 301))
|
34
34
|
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=401").to_return(status: 200, body: api_response((401..total).to_a, 401))
|
35
|
-
|
35
|
+
length = 0
|
36
36
|
Record.find_in_batches do |records|
|
37
|
-
|
37
|
+
length += records.length
|
38
38
|
expect(records).to be_kind_of Record
|
39
39
|
expect(records._proxy).to be_kind_of LHS::Collection
|
40
40
|
end
|
41
|
-
expect(
|
41
|
+
expect(length).to eq total
|
42
42
|
end
|
43
43
|
|
44
44
|
it 'adapts to backend max limit' do
|
@@ -47,19 +47,19 @@ describe LHS::Collection do
|
|
47
47
|
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=201").to_return(status: 200, body: api_response((201..300).to_a, 201))
|
48
48
|
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=301").to_return(status: 200, body: api_response((301..400).to_a, 301))
|
49
49
|
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=401").to_return(status: 200, body: api_response((401..total).to_a, 401))
|
50
|
-
|
50
|
+
length = 0
|
51
51
|
Record.find_in_batches(batch_size: 230) do |records|
|
52
|
-
|
52
|
+
length += records.length
|
53
53
|
expect(records).to be_kind_of Record
|
54
54
|
expect(records._proxy).to be_kind_of LHS::Collection
|
55
55
|
end
|
56
|
-
expect(
|
56
|
+
expect(length).to eq total
|
57
57
|
end
|
58
58
|
|
59
59
|
it 'forwards offset' do
|
60
60
|
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=401").to_return(status: 200, body: api_response((401..total).to_a, 401))
|
61
61
|
Record.find_in_batches(start: 401) do |records|
|
62
|
-
expect(records.
|
62
|
+
expect(records.length).to eq(total - 400)
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
|
2
|
+
require 'rails_helper'
|
3
|
+
|
4
|
+
describe LHS::Record do
|
5
|
+
before(:each) { LHC.config.placeholder('datastore', datastore) }
|
6
|
+
let(:datastore) { 'http://local.ch/v2' }
|
7
|
+
|
8
|
+
context 'default pagination behaviour' do
|
9
|
+
before(:each) do
|
10
|
+
class Record < LHS::Record
|
11
|
+
endpoint ':datastore/feedbacks'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'also works when there is no item in the first response' do
|
16
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
17
|
+
.to_return(
|
18
|
+
status: 200,
|
19
|
+
body: { items: [], total: 300, offset: 0 }.to_json
|
20
|
+
)
|
21
|
+
all = Record.all
|
22
|
+
expect(all).to be_kind_of Record
|
23
|
+
expect(all._proxy).to be_kind_of LHS::Collection
|
24
|
+
expect(all.length).to eq 0
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'also works when there is no total in the stubbing' do
|
28
|
+
stub_request(:get, %r{/feedbacks}).to_return(body: { items: (1..100).to_a }.to_json)
|
29
|
+
all = Record.all
|
30
|
+
expect(all).to be_kind_of Record
|
31
|
+
expect(all._proxy).to be_kind_of LHS::Collection
|
32
|
+
expect(all.length).to eq 100
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'also works when there is no key "items" in the stubbing' do
|
36
|
+
stub_request(:get, %r{/feedbacks}).to_return(body: (1..100).to_a.to_json)
|
37
|
+
all = Record.all
|
38
|
+
expect(all).to be_kind_of Record
|
39
|
+
expect(all._proxy).to be_kind_of LHS::Collection
|
40
|
+
expect(all.length).to eq 100
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'pagination using offset(0,100,200,...)' do
|
45
|
+
before(:each) do
|
46
|
+
class Record < LHS::Record
|
47
|
+
configuration pagination_strategy: 'offset', pagination_key: 'offset'
|
48
|
+
endpoint ':datastore/feedbacks'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'fetches all records from the backend' do
|
53
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
54
|
+
.to_return(
|
55
|
+
status: 200,
|
56
|
+
body: { items: (1..100).to_a, limit: 100, total: 300, offset: 0 }.to_json
|
57
|
+
)
|
58
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=100")
|
59
|
+
.to_return(
|
60
|
+
status: 200,
|
61
|
+
body: { items: (101..200).to_a, limit: 100, total: 300, offset: 100 }.to_json
|
62
|
+
)
|
63
|
+
last_request = stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=200")
|
64
|
+
.to_return(
|
65
|
+
status: 200,
|
66
|
+
body: { items: (201..300).to_a, limit: 100, total: 300, offset: 200 }.to_json
|
67
|
+
)
|
68
|
+
all = Record.all
|
69
|
+
assert_requested last_request
|
70
|
+
expect(all).to be_kind_of Record
|
71
|
+
expect(all._data._proxy).to be_kind_of LHS::Collection
|
72
|
+
expect(all.count).to eq 300
|
73
|
+
expect(all.last).to eq 300
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'fetches all, also if there is a rest and the total is not divideable trough the limit' do
|
77
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
78
|
+
.to_return(
|
79
|
+
status: 200,
|
80
|
+
body: { items: (1..100).to_a, limit: 100, total: 223, offset: 0 }.to_json
|
81
|
+
)
|
82
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=100")
|
83
|
+
.to_return(
|
84
|
+
status: 200,
|
85
|
+
body: { items: (101..200).to_a, limit: 100, total: 223, offset: 100 }.to_json
|
86
|
+
)
|
87
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=200")
|
88
|
+
.to_return(
|
89
|
+
status: 200,
|
90
|
+
body: { items: (201..223).to_a, limit: 100, total: 223, offset: 200 }.to_json
|
91
|
+
)
|
92
|
+
all = Record.all
|
93
|
+
expect(all).to be_kind_of Record
|
94
|
+
expect(all._data._proxy).to be_kind_of LHS::Collection
|
95
|
+
expect(all.count).to eq 223
|
96
|
+
expect(all.last).to eq 223
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'also fetches all when there is not meta information for limit' do
|
100
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
101
|
+
.to_return(
|
102
|
+
status: 200,
|
103
|
+
body: { items: (1..100).to_a, total: 300, offset: 0 }.to_json
|
104
|
+
)
|
105
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=100")
|
106
|
+
.to_return(
|
107
|
+
status: 200,
|
108
|
+
body: { items: (101..200).to_a, total: 300, offset: 100 }.to_json
|
109
|
+
)
|
110
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=200")
|
111
|
+
.to_return(
|
112
|
+
status: 200,
|
113
|
+
body: { items: (201..300).to_a, total: 300, offset: 200 }.to_json
|
114
|
+
)
|
115
|
+
all = Record.all
|
116
|
+
expect(all).to be_kind_of Record
|
117
|
+
expect(all._proxy).to be_kind_of LHS::Collection
|
118
|
+
expect(all.count).to eq 300
|
119
|
+
expect(all.last).to eq 300
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'pagination using page(1,2,3,...)' do
|
124
|
+
before(:each) do
|
125
|
+
class Record < LHS::Record
|
126
|
+
configuration pagination_strategy: 'page', pagination_key: 'page'
|
127
|
+
endpoint ':datastore/feedbacks'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'fetches all records from the backend' do
|
132
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
133
|
+
.to_return(
|
134
|
+
status: 200,
|
135
|
+
body: { items: (1..100).to_a, limit: 100, total: 300, page: 1 }.to_json
|
136
|
+
)
|
137
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&page=2")
|
138
|
+
.to_return(
|
139
|
+
status: 200,
|
140
|
+
body: { items: (101..200).to_a, limit: 100, total: 300, page: 2 }.to_json
|
141
|
+
)
|
142
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&page=3")
|
143
|
+
.to_return(
|
144
|
+
status: 200,
|
145
|
+
body: { items: (201..300).to_a, limit: 100, total: 300, page: 3 }.to_json
|
146
|
+
)
|
147
|
+
all = Record.all
|
148
|
+
expect(all).to be_kind_of Record
|
149
|
+
expect(all._data._proxy).to be_kind_of LHS::Collection
|
150
|
+
expect(all.count).to eq 300
|
151
|
+
expect(all.last).to eq 300
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'also fetches all when there is not meta information for limit' do
|
155
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
156
|
+
.to_return(
|
157
|
+
status: 200,
|
158
|
+
body: { items: (1..100).to_a, total: 300, page: 1 }.to_json
|
159
|
+
)
|
160
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&page=2")
|
161
|
+
.to_return(
|
162
|
+
status: 200,
|
163
|
+
body: { items: (101..200).to_a, total: 300, page: 2 }.to_json
|
164
|
+
)
|
165
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&page=3")
|
166
|
+
.to_return(
|
167
|
+
status: 200,
|
168
|
+
body: { items: (201..300).to_a, total: 300, page: 3 }.to_json
|
169
|
+
)
|
170
|
+
all = Record.all
|
171
|
+
expect(all).to be_kind_of Record
|
172
|
+
expect(all._proxy).to be_kind_of LHS::Collection
|
173
|
+
expect(all.count).to eq 300
|
174
|
+
expect(all.last).to eq 300
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'fetches all, also if there is a rest and the total is not divideable trough the limit' do
|
178
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
179
|
+
.to_return(
|
180
|
+
status: 200,
|
181
|
+
body: { items: (1..100).to_a, limit: 100, total: 223, page: 1 }.to_json
|
182
|
+
)
|
183
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&page=2")
|
184
|
+
.to_return(
|
185
|
+
status: 200,
|
186
|
+
body: { items: (101..200).to_a, limit: 100, total: 223, page: 2 }.to_json
|
187
|
+
)
|
188
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&page=3")
|
189
|
+
.to_return(
|
190
|
+
status: 200,
|
191
|
+
body: { items: (201..223).to_a, limit: 100, total: 223, page: 3 }.to_json
|
192
|
+
)
|
193
|
+
all = Record.all
|
194
|
+
expect(all).to be_kind_of Record
|
195
|
+
expect(all._data._proxy).to be_kind_of LHS::Collection
|
196
|
+
expect(all.count).to eq 223
|
197
|
+
expect(all.last).to eq 223
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'pagination using start(1,101,201,...)' do
|
202
|
+
before(:each) do
|
203
|
+
class Record < LHS::Record
|
204
|
+
configuration pagination_strategy: 'start', pagination_key: 'start'
|
205
|
+
endpoint ':datastore/feedbacks'
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'fetches all records from the backend' do
|
210
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
211
|
+
.to_return(
|
212
|
+
status: 200,
|
213
|
+
body: { items: (1..100).to_a, limit: 100, total: 300, start: 1 }.to_json
|
214
|
+
)
|
215
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&start=101")
|
216
|
+
.to_return(
|
217
|
+
status: 200,
|
218
|
+
body: { items: (101..200).to_a, limit: 100, total: 300, start: 101 }.to_json
|
219
|
+
)
|
220
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&start=201")
|
221
|
+
.to_return(
|
222
|
+
status: 200,
|
223
|
+
body: { items: (201..300).to_a, limit: 100, total: 300, start: 201 }.to_json
|
224
|
+
)
|
225
|
+
all = Record.all
|
226
|
+
expect(all).to be_kind_of Record
|
227
|
+
expect(all._data._proxy).to be_kind_of LHS::Collection
|
228
|
+
expect(all.count).to eq 300
|
229
|
+
expect(all.last).to eq 300
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'also fetches all when there is not meta information for limit' do
|
233
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
234
|
+
.to_return(
|
235
|
+
status: 200,
|
236
|
+
body: { items: (1..100).to_a, total: 300, start: 1 }.to_json
|
237
|
+
)
|
238
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&start=101")
|
239
|
+
.to_return(
|
240
|
+
status: 200,
|
241
|
+
body: { items: (101..200).to_a, total: 300, start: 101 }.to_json
|
242
|
+
)
|
243
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&start=201")
|
244
|
+
.to_return(
|
245
|
+
status: 200,
|
246
|
+
body: { items: (201..300).to_a, total: 300, start: 201 }.to_json
|
247
|
+
)
|
248
|
+
all = Record.all
|
249
|
+
expect(all).to be_kind_of Record
|
250
|
+
expect(all._proxy).to be_kind_of LHS::Collection
|
251
|
+
expect(all.count).to eq 300
|
252
|
+
expect(all.last).to eq 300
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'fetches all, also if there is a rest and the total is not divideable trough the limit' do
|
256
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
257
|
+
.to_return(
|
258
|
+
status: 200,
|
259
|
+
body: { items: (1..100).to_a, limit: 100, total: 223, start: 1 }.to_json
|
260
|
+
)
|
261
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&start=101")
|
262
|
+
.to_return(
|
263
|
+
status: 200,
|
264
|
+
body: { items: (101..200).to_a, limit: 100, total: 223, start: 101 }.to_json
|
265
|
+
)
|
266
|
+
stub_request(:get, "#{datastore}/feedbacks?limit=100&start=201")
|
267
|
+
.to_return(
|
268
|
+
status: 200,
|
269
|
+
body: { items: (201..223).to_a, limit: 100, total: 223, start: 201 }.to_json
|
270
|
+
)
|
271
|
+
all = Record.all
|
272
|
+
expect(all).to be_kind_of Record
|
273
|
+
expect(all._data._proxy).to be_kind_of LHS::Collection
|
274
|
+
expect(all.count).to eq 223
|
275
|
+
expect(all.last).to eq 223
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -56,5 +56,14 @@ describe LHS::Record do
|
|
56
56
|
it 'responds to next_page' do
|
57
57
|
expect(record.next_page).to eq(next_page)
|
58
58
|
end
|
59
|
+
|
60
|
+
context 'when amount of total pages is not diviable by the limit' do
|
61
|
+
let(:total) { 2738 }
|
62
|
+
let(:limit) { 100 }
|
63
|
+
|
64
|
+
it 'rounds up' do
|
65
|
+
expect(record.total_pages).to eq(28)
|
66
|
+
end
|
67
|
+
end
|
59
68
|
end
|
60
69
|
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:
|
4
|
+
version: 4.0.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-04-
|
11
|
+
date: 2016-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lhc
|
@@ -202,6 +202,7 @@ files:
|
|
202
202
|
- lib/lhs/endpoint.rb
|
203
203
|
- lib/lhs/errors.rb
|
204
204
|
- lib/lhs/item.rb
|
205
|
+
- lib/lhs/pagination.rb
|
205
206
|
- lib/lhs/proxy.rb
|
206
207
|
- lib/lhs/record.rb
|
207
208
|
- lib/lhs/version.rb
|
@@ -274,7 +275,6 @@ files:
|
|
274
275
|
- spec/item/validation_spec.rb
|
275
276
|
- spec/proxy/load_spec.rb
|
276
277
|
- spec/rails_helper.rb
|
277
|
-
- spec/record/all_spec.rb
|
278
278
|
- spec/record/build_spec.rb
|
279
279
|
- spec/record/create_spec.rb
|
280
280
|
- spec/record/creation_failed_spec.rb
|
@@ -291,6 +291,7 @@ files:
|
|
291
291
|
- spec/record/mapping_spec.rb
|
292
292
|
- spec/record/model_name_spec.rb
|
293
293
|
- spec/record/new_spec.rb
|
294
|
+
- spec/record/paginatable_collection_spec.rb
|
294
295
|
- spec/record/pagination_spec.rb
|
295
296
|
- spec/record/persisted_spec.rb
|
296
297
|
- spec/record/request_spec.rb
|
@@ -398,7 +399,6 @@ test_files:
|
|
398
399
|
- spec/item/validation_spec.rb
|
399
400
|
- spec/proxy/load_spec.rb
|
400
401
|
- spec/rails_helper.rb
|
401
|
-
- spec/record/all_spec.rb
|
402
402
|
- spec/record/build_spec.rb
|
403
403
|
- spec/record/create_spec.rb
|
404
404
|
- spec/record/creation_failed_spec.rb
|
@@ -415,6 +415,7 @@ test_files:
|
|
415
415
|
- spec/record/mapping_spec.rb
|
416
416
|
- spec/record/model_name_spec.rb
|
417
417
|
- spec/record/new_spec.rb
|
418
|
+
- spec/record/paginatable_collection_spec.rb
|
418
419
|
- spec/record/pagination_spec.rb
|
419
420
|
- spec/record/persisted_spec.rb
|
420
421
|
- spec/record/request_spec.rb
|
data/spec/record/all_spec.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
describe LHS::Collection do
|
4
|
-
let(:datastore) { 'http://local.ch/v2' }
|
5
|
-
|
6
|
-
before(:each) do
|
7
|
-
LHC.config.placeholder('datastore', datastore)
|
8
|
-
class Record < LHS::Record
|
9
|
-
endpoint ':datastore/:campaign_id/feedbacks'
|
10
|
-
endpoint ':datastore/feedbacks'
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
context 'all' do
|
15
|
-
it 'fetches all records from the backend' do
|
16
|
-
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
17
|
-
.to_return(status: 200, body: { items: (1..100).to_a, total: 300, limit: 100, offset: 0 }.to_json)
|
18
|
-
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=101")
|
19
|
-
.to_return(status: 200, body: { items: (101..200).to_a, total: 300, limit: 100, offset: 101 }.to_json)
|
20
|
-
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=201")
|
21
|
-
.to_return(status: 200, body: { items: (201..300).to_a, total: 300, limit: 100, offset: 201 }.to_json)
|
22
|
-
all = Record.all
|
23
|
-
expect(all).to be_kind_of Record
|
24
|
-
expect(all._data._proxy).to be_kind_of LHS::Collection
|
25
|
-
expect(all.count).to eq 300
|
26
|
-
expect(all.last).to eq 300
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'also fetches all when there is not meta information for limit' do
|
30
|
-
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
31
|
-
.to_return(status: 200, body: { items: (1..100).to_a, total: 300, offset: 0 }.to_json)
|
32
|
-
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=101")
|
33
|
-
.to_return(status: 200, body: { items: (101..200).to_a, total: 300, offset: 101 }.to_json)
|
34
|
-
stub_request(:get, "#{datastore}/feedbacks?limit=100&offset=201")
|
35
|
-
.to_return(status: 200, body: { items: (201..300).to_a, total: 300, offset: 201 }.to_json)
|
36
|
-
all = Record.all
|
37
|
-
expect(all).to be_kind_of Record
|
38
|
-
expect(all._proxy).to be_kind_of LHS::Collection
|
39
|
-
expect(all.count).to eq 300
|
40
|
-
expect(all.last).to eq 300
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'also works when there is no item in the first response' do
|
44
|
-
stub_request(:get, "#{datastore}/feedbacks?limit=100")
|
45
|
-
.to_return(status: 200, body: { items: [], total: 300, offset: 0 }.to_json)
|
46
|
-
all = Record.all
|
47
|
-
expect(all).to be_kind_of Record
|
48
|
-
expect(all._proxy).to be_kind_of LHS::Collection
|
49
|
-
expect(all.count).to eq 0
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'alsow works when there is no total in the stubbing' do
|
53
|
-
stub_request(:get, %r{/feedbacks}).to_return(body: { items: (1..100).to_a }.to_json)
|
54
|
-
all = Record.all
|
55
|
-
expect(all).to be_kind_of Record
|
56
|
-
expect(all._proxy).to be_kind_of LHS::Collection
|
57
|
-
expect(all.count).to eq 100
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'alsow works when there is no key "items" in the stubbing' do
|
61
|
-
stub_request(:get, %r{/feedbacks}).to_return(body: (1..100).to_a.to_json)
|
62
|
-
all = Record.all
|
63
|
-
expect(all).to be_kind_of Record
|
64
|
-
expect(all._proxy).to be_kind_of LHS::Collection
|
65
|
-
expect(all.count).to eq 100
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|