lhs 13.2.3 → 14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2bda2ff4ae84ce367ee21ad8922b46e600b288c5
4
- data.tar.gz: c8fc0746ffca39f2b7e5ad370ba71cc884535ec9
3
+ metadata.gz: 2844d363e539a18b08de95cd58f510dcaa101651
4
+ data.tar.gz: 0e4fc872e5730d30b8ead542f1b135cb28c96d81
5
5
  SHA512:
6
- metadata.gz: e298a06e35a61cdbaddbceffc23e7e982e4387b9d21c7753165202e309bc0e463339b511039abf20c3038c389460e4676ef2ce933a285b83cb53b6543ffb180b
7
- data.tar.gz: d2020691e22febd3f22256c2f689d093e65fd92fb0bb5519ff5413905f54f6a946fe5ce8cbb83ba9b2681322033f7f93ec26f50c4eba56b19e92cb580b267c1c
6
+ metadata.gz: 4ecde150a77ea8c199abc608331f35b854e546714f20e155003fa52b8cca63e6088128cec7d6ae0466bc9432124650d26d86fe16bc4bf840d8824bf1a44482ac
7
+ data.tar.gz: dd4f3cee3b961267307ca3a3f40cfa1bebffa53baa5b958255ac3594a6437095f00c1944e3f5dc4f68a97993ed0e90ea3c3e6c30a36e27d03063eb268b03aaab
data/README.md CHANGED
@@ -336,6 +336,8 @@ Record.all(color: 'blue')
336
336
  # All three are doing the same thing: fetching all records with the color 'blue' from the endpoint while resolving pagingation if endpoint is paginated
337
337
  ```
338
338
 
339
+ In case an API does not provide pagination information (limit, offset and total), LHS keeps on loading pages when requesting `all` until the first empty page responds.
340
+
339
341
  [Count vs. Length](#count-vs-length)
340
342
 
341
343
  `find_each` is a more fine grained way to process single records that are fetched in batches.
@@ -21,6 +21,15 @@ class LHS::Record
21
21
 
22
22
  private
23
23
 
24
+ def single_request_load_and_merge_remaining_objects!(data, options, endpoint)
25
+ return unless options[:all]
26
+ load_and_merge_remaining_objects!(
27
+ data: data,
28
+ options: process_options(options, endpoint),
29
+ load_not_paginated_collection: true
30
+ )
31
+ end
32
+
24
33
  def filter_empty_request_options(options)
25
34
  options.map do |option|
26
35
  option if !option || !option.key?(:url) || !option[:url].nil?
@@ -208,15 +217,36 @@ class LHS::Record
208
217
  # we can evaluate if there are further remote objects remaining
209
218
  # and after preparing all the requests that have to be made in order to fetch all
210
219
  # remote items during this batch, they are fetched in parallel
211
- def load_and_merge_remaining_objects!(data, options)
220
+ def load_and_merge_remaining_objects!(data:, options:, load_not_paginated_collection: false)
212
221
  if paginated?(data._raw)
213
222
  load_and_merge_paginated_collection!(data, options)
214
223
  elsif data.collection? && paginated?(data.first.try(:_raw))
215
224
  load_and_merge_set_of_paginated_collections!(data, options)
225
+ elsif load_not_paginated_collection
226
+ load_and_merge_not_paginated_collection!(data, options)
227
+ end
228
+ end
229
+
230
+ def load_and_merge_not_paginated_collection!(data, options)
231
+ return if data.length.zero?
232
+ options = options.is_a?(Hash) ? options : {}
233
+ limit = options.dig(:params, limit_key) || pagination_class::DEFAULT_LIMIT
234
+ offset = options.dig(:params, pagination_key) || pagination_class::DEFAULT_OFFSET
235
+ options[:params] = options.fetch(:params, {}).merge(
236
+ limit_key => limit,
237
+ pagination_key => pagination_class.next_offset(
238
+ offset,
239
+ limit
240
+ )
241
+ )
242
+ additional_data = data._record.request(options)
243
+ additional_data.each do |item_data|
244
+ data.concat(input: data._raw, items: [item_data], record: self)
216
245
  end
217
246
  end
218
247
 
219
248
  def load_and_merge_paginated_collection!(data, options)
249
+ data._raw[limit_key] = data.length if data._raw[limit_key].blank? && !data.length.zero?
220
250
  pagination = data._record.pagination(data)
221
251
  return data if pagination.pages_left.zero?
222
252
  record = data._record
@@ -279,13 +309,13 @@ class LHS::Record
279
309
  # paginates itself to ensure all records are fetched
280
310
  def load_all_included!(record, options)
281
311
  data = record.request(options)
282
- load_and_merge_remaining_objects!(data, options)
312
+ load_and_merge_remaining_objects!(data: data, options: options)
283
313
  data
284
314
  end
285
315
 
286
316
  # Checks if given raw is paginated or not
287
317
  def paginated?(raw)
288
- !!(raw.is_a?(Hash) && raw[total_key] && raw[pagination_key])
318
+ !!(raw.is_a?(Hash) && raw[total_key])
289
319
  end
290
320
 
291
321
  def prepare_options_for_include_all_request!(options)
@@ -466,7 +496,7 @@ class LHS::Record
466
496
  apply_limit!(options) if options[:all]
467
497
  response = LHC.request(process_options(options, endpoint))
468
498
  data = LHS::Data.new(response.body, nil, self, response.request, endpoint)
469
- load_and_merge_remaining_objects!(data, process_options(options, endpoint)) if paginated?(data._raw) && options[:all]
499
+ single_request_load_and_merge_remaining_objects!(data, options, endpoint)
470
500
  expand_items(data, options[:expanded]) if data.collection? && options[:expanded]
471
501
  handle_includes(including, data, referencing) if including.present? && data.present?
472
502
  data
@@ -23,7 +23,7 @@ module LHS::Pagination
23
23
  end
24
24
 
25
25
  def offset
26
- data._raw.dig(*_record.pagination_key) || 0
26
+ data._raw.dig(*_record.pagination_key) || self.class::DEFAULT_OFFSET
27
27
  end
28
28
  alias current_page offset
29
29
  alias start offset
@@ -1,14 +1,20 @@
1
1
  class LHS::Pagination::Offset < LHS::Pagination::Base
2
2
 
3
+ DEFAULT_OFFSET = 0
4
+
3
5
  def current_page
4
6
  (offset + limit) / limit
5
7
  end
6
8
 
7
9
  def next_offset(step = 1)
8
- offset + limit * step
10
+ self.class.next_offset(offset, limit, step)
9
11
  end
10
12
 
11
13
  def self.page_to_offset(page, limit = DEFAULT_LIMIT)
12
14
  (page.to_i - 1) * limit.to_i
13
15
  end
16
+
17
+ def self.next_offset(offset, limit, step = 1)
18
+ offset.to_i + limit.to_i * step.to_i
19
+ end
14
20
  end
@@ -1,10 +1,16 @@
1
1
  class LHS::Pagination::Page < LHS::Pagination::Base
2
2
 
3
+ DEFAULT_OFFSET = 1
4
+
3
5
  def current_page
4
6
  offset
5
7
  end
6
8
 
7
9
  def next_offset(step = 1)
8
- current_page + step
10
+ self.class.next_offset(current_page, limit, step)
11
+ end
12
+
13
+ def self.next_offset(current_page, _limit, step = 1)
14
+ current_page.to_i + step.to_i
9
15
  end
10
16
  end
@@ -1,14 +1,20 @@
1
1
  class LHS::Pagination::Start < LHS::Pagination::Base
2
2
 
3
+ DEFAULT_OFFSET = 1
4
+
3
5
  def current_page
4
6
  (offset + limit - 1) / limit
5
7
  end
6
8
 
7
9
  def next_offset(step = 1)
8
- offset + limit * step
10
+ self.class.next_offset(offset, limit, step)
9
11
  end
10
12
 
11
13
  def self.page_to_offset(page, limit = DEFAULT_LIMIT)
12
14
  (page.to_i - 1) * limit.to_i + 1
13
15
  end
16
+
17
+ def self.next_offset(offset, limit, step = 1)
18
+ offset.to_i + limit.to_i * step.to_i
19
+ end
14
20
  end
@@ -1,3 +1,3 @@
1
1
  module LHS
2
- VERSION = "13.2.3"
2
+ VERSION = "14.0.0"
3
3
  end
@@ -1,13 +1,13 @@
1
1
  require 'rails_helper'
2
2
 
3
3
  describe LHS::Record do
4
- before(:each) do
5
- class Record < LHS::Record
6
- endpoint 'http://datastore/feedbacks'
4
+ context 'all' do
5
+ before(:each) do
6
+ class Record < LHS::Record
7
+ endpoint 'http://datastore/feedbacks'
8
+ end
7
9
  end
8
- end
9
10
 
10
- context 'all' do
11
11
  it 'is querying endpoint without pagination when using all' do
12
12
  stub_request(:get, "http://datastore/feedbacks?limit=100").to_return(body: { items: 300.times.map { { foo: 'bar' } }, total: 300 }.to_json)
13
13
  records = Record.all
@@ -58,4 +58,48 @@ describe LHS::Record do
58
58
  end
59
59
  end
60
60
  end
61
+
62
+ context 'all without current page indicator' do
63
+ before(:each) do
64
+ class Category < LHS::Record
65
+ configuration(
66
+ items_key: %i(response results),
67
+ limit_key: :max,
68
+ pagination_key: :offset,
69
+ pagination_strategy: :offset
70
+ )
71
+
72
+ endpoint 'http://store/categories'
73
+ end
74
+ end
75
+
76
+ def stub_batch(url, items = 10)
77
+ stub_request(:get, url)
78
+ .to_return(
79
+ body: {
80
+ response: {
81
+ results: items.times.map { { name: 'category' } }
82
+ }
83
+ }.to_json
84
+ )
85
+ end
86
+
87
+ it 'is able to fetch all remote objects without any current page indicator by simply increasing the offset until response is empty' do
88
+ stub_batch('http://store/categories?language=en&max=10&offset=0')
89
+ stub_batch('http://store/categories?language=en&max=10&offset=10')
90
+ stub_batch('http://store/categories?language=en&max=10&offset=20')
91
+ stub_batch('http://store/categories?language=en&max=10&offset=30', 0)
92
+ records = Category.limit(10).all(language: 'en').fetch
93
+ expect(records.length).to eq 30
94
+ end
95
+
96
+ it 'is able to fetch all remote objects without any current page indicator by simply increasing the offset until response is empty' do
97
+ stub_batch('http://store/categories?language=en&max=100', 100)
98
+ stub_batch('http://store/categories?language=en&max=100&offset=100', 100)
99
+ stub_batch('http://store/categories?language=en&max=100&offset=200', 100)
100
+ stub_batch('http://store/categories?language=en&max=100&offset=300', 0)
101
+ records = Category.all(language: 'en').fetch
102
+ expect(records.length).to eq 300
103
+ end
104
+ end
61
105
  end
@@ -81,6 +81,8 @@ describe LHS::Record do
81
81
  it 'uses urls instead of trying to find base endpoint of parent class' do
82
82
  stub_request(:get, "#{datastore}/entry/123/contracts?limit=100")
83
83
  .to_return(body: [{ product: { href: "#{datastore}/products/LBC" } }].to_json)
84
+ stub_request(:get, "#{datastore}/entry/123/contracts?limit=100&offset=100")
85
+ .to_return(body: [].to_json)
84
86
  stub_request(:get, "#{datastore}/products/LBC")
85
87
  .to_return(body: { name: 'Local Business Card' }.to_json)
86
88
  expect(lambda {
@@ -346,7 +346,10 @@ describe LHS::Record do
346
346
  .to_return(
347
347
  body: {
348
348
  href: "http://datastore/v2/places/1/contracts?offset=0&limit=10",
349
- items: [{ href: "http://datastore/v2/contracts/1" }]
349
+ items: [{ href: "http://datastore/v2/contracts/1" }],
350
+ offset: 0,
351
+ limit: 10,
352
+ total: 10
350
353
  }.to_json
351
354
  )
352
355
 
@@ -30,7 +30,8 @@ describe LHS::Record do
30
30
  end
31
31
 
32
32
  it 'also works when there is no total in the stubbing' do
33
- stub_request(:get, %r{/feedbacks}).to_return(body: { items: (1..100).to_a }.to_json)
33
+ stub_request(:get, "http://local.ch/v2/feedbacks?limit=100").to_return(body: { items: (1..100).to_a }.to_json)
34
+ stub_request(:get, "http://local.ch/v2/feedbacks?limit=100&offset=100").to_return(body: { items: [] }.to_json)
34
35
  all = Record.all
35
36
  expect(all).to be_kind_of Record
36
37
  expect(all._proxy).to be_kind_of LHS::Collection
@@ -38,7 +39,8 @@ describe LHS::Record do
38
39
  end
39
40
 
40
41
  it 'also works when there is no key "items" in the stubbing' do
41
- stub_request(:get, %r{/feedbacks}).to_return(body: (1..100).to_a.to_json)
42
+ stub_request(:get, "http://local.ch/v2/feedbacks?limit=100").to_return(body: (1..100).to_a.to_json)
43
+ stub_request(:get, "http://local.ch/v2/feedbacks?limit=100&offset=100").to_return(body: [].to_json)
42
44
  all = Record.all
43
45
  expect(all).to be_kind_of Record
44
46
  expect(all._proxy).to be_kind_of LHS::Collection
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lhs
3
3
  version: !ruby/object:Gem::Version
4
- version: 13.2.3
4
+ version: 14.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/local-ch/lhs/graphs/contributors