dhs 1.2.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca31fdaf95b8fbff1a8de50591e9861554d00fed99645e79f01cd2363de9e8b7
4
- data.tar.gz: 06d8d350c5da6878fd6c54a76826bb56c4fb506f6bc8c9be732743014b875f50
3
+ metadata.gz: c31dbca2778853f9bafb3a74e2c7ef830d3897c0caa78b66e5bb58203fc8bb11
4
+ data.tar.gz: 8e5968134c395c0b4e6196506ef5502f5941f68754423bc885bc536a44c2ba33
5
5
  SHA512:
6
- metadata.gz: 00035a95ea3b634fc97f6af5c7592b32e09a7135a371d29e234c54ee240da99b1b71ce8305b6342aa61b7fb9103c26576a6e729cfead930d7754745d106b5bda
7
- data.tar.gz: 8f844da13a8f5d011b6179310b5ac1629853f3be24496eeba52b425412a5d7159fd2cb89744953afa2a0d615ef696c04993e3b3f921891bac060bd8d1702d63d
6
+ metadata.gz: 54e1c9ec3c0b71566a8b6831690250d0eecc00f00155617dc39873c20d49a26ef57051ae04d7d3a1bbb3733f75fc3ccc0024b96c3b4bd086dafe48a1634cb8cf
7
+ data.tar.gz: 1bea7f70ccd0cba8dc488604d5f4a668d1bc602a2cb30d19058b195a17b2d9f1aaa5d72e21286e910570a89be0e78c25adac4bb45186a6f3f11ffcc81128b39c
data/README.md CHANGED
@@ -155,6 +155,107 @@ GET https://service.example.com/records
155
155
 
156
156
  **Be aware that, if you configure ambigious endpoints accross multiple classes, the order of things is not deteministic. Ambigious endpoints accross multiple classes need to be avoided.**
157
157
 
158
+ #### GraphQL Endpoints
159
+
160
+ You can use DHS also to fetch records from GraphQL Endpoints:
161
+
162
+ ```ruby
163
+ # app/models/record.rb
164
+
165
+ class Record < DHS::Record
166
+
167
+ configuration items_key: [:data, :ethereum, :address, 0, :balances]
168
+
169
+ endpoint 'https://graphql.bitquery.io/',
170
+ graphql: {
171
+ query: %Q{
172
+ query ($network: EthereumNetwork!, $address: String!) {
173
+ ethereum(network: $network) {
174
+ address(address: {is: $address}) {
175
+ balances {
176
+ currency {
177
+ address
178
+ name
179
+ symbol
180
+ decimals
181
+ tokenType
182
+ }
183
+ value
184
+ }
185
+ }
186
+ }
187
+ }
188
+ },
189
+ variables: [:network, :address]
190
+ }
191
+
192
+ end
193
+ ```
194
+
195
+ ```ruby
196
+ # app/controllers/some_controller.rb
197
+
198
+ records = Record.where(network: 'ethereum', address: '0x317D875cA3B9f8d14f960486C0d1D1913be74e90')
199
+ ```
200
+
201
+ ```
202
+ POST https://graphql.bitquery.io/
203
+
204
+ BODY
205
+ {
206
+ "query": "
207
+ query ($network: EthereumNetwork!, $address: String!) {
208
+ ethereum(network: $network) {
209
+ address(address: {is: $address}) {
210
+ balances {
211
+ currency {
212
+ address
213
+ name
214
+ symbol
215
+ decimals
216
+ tokenType
217
+ }
218
+ value
219
+ }
220
+ }
221
+ }
222
+ }
223
+ ",
224
+ "variables": {
225
+ "network": "ethereum",
226
+ "address": "0x317D875cA3B9f8d14f960486C0d1D1913be74e90"
227
+ }
228
+ }
229
+
230
+ RESPONSE
231
+ "data": {
232
+ "ethereum": {
233
+ "address": [
234
+ {
235
+ balances: [
236
+ {
237
+ "currency": {
238
+ "address": "-",
239
+ "name": "Ether",
240
+ "decimals": 18,
241
+ "symbol": "ETH",
242
+ "tokenType": ""
243
+ },
244
+ "value": 0.11741978
245
+ }
246
+ ]
247
+ }
248
+ ]
249
+ }
250
+ }
251
+ ```
252
+
253
+ ```ruby
254
+ # app/controllers/some_controller.rb
255
+
256
+ records.first.currency.name # Ethereum
257
+ ```
258
+
158
259
  ### Provider
159
260
 
160
261
  Providers in DHS allow you to group shared endpoint options under a common provider.
@@ -1285,6 +1386,51 @@ Sequentially:
1285
1386
  GET https://service.example.com/records?from_record_id=xcaoXBmuMyFFEcFDSgNgDQ&limit=100
1286
1387
  ```
1287
1388
 
1389
+ ##### Pagination strategy: next_offset
1390
+
1391
+ The `next_offset` strategy continuously follows in-response offset information to following pages until the last page is reached (indicated by next offset being either empty or 0).
1392
+
1393
+ *WARNING*
1394
+
1395
+ Loading all pages from a resource paginated with next_offset only can result in very poor performance, as pages can only be loaded sequentially!
1396
+
1397
+ ```ruby
1398
+ # app/models/record.rb
1399
+
1400
+ class Search < DHS::Record
1401
+ configuration pagination_strategy: 'next_offset'
1402
+
1403
+ endpoint '{+service}/assets'
1404
+ end
1405
+ ```
1406
+
1407
+ ```ruby
1408
+ # app/controllers/some_controller.rb
1409
+
1410
+ Record.all
1411
+
1412
+ ```
1413
+ ```
1414
+ GET https://service.example.com/assets?limit=100
1415
+ {
1416
+ items: [{...}, ...],
1417
+ limit: 10,
1418
+ next_offset: 29
1419
+ }
1420
+ GET https://service.example.com/assets?offset=29
1421
+ {
1422
+ items: [{...}, ...],
1423
+ limit: 10,
1424
+ next_offset: 39
1425
+ }
1426
+ GET https://service.example.com/assets?offset=39
1427
+ {
1428
+ items: [{...}, ...],
1429
+ limit: 10,
1430
+ next_offset: 0
1431
+ }
1432
+ ```
1433
+
1288
1434
  #### Pagination keys
1289
1435
 
1290
1436
  ##### limit_key
@@ -2851,4 +2997,3 @@ expect(
2851
2997
  ## License
2852
2998
 
2853
2999
  [GNU General Public License Version 3.](https://www.gnu.org/licenses/gpl-3.0.en.html)
2854
-
data/dhs.gemspec CHANGED
@@ -24,14 +24,14 @@ Gem::Specification.new do |s|
24
24
 
25
25
  s.add_dependency 'activemodel'
26
26
  s.add_dependency 'activesupport', '>= 6'
27
- s.add_dependency 'dhc'
27
+ s.add_dependency 'dhc', '>= 2'
28
28
  s.add_dependency 'local_uri'
29
29
 
30
30
  s.add_development_dependency 'capybara'
31
31
  s.add_development_dependency 'json', '>= 1.8.2'
32
32
  s.add_development_dependency 'pry'
33
33
  s.add_development_dependency 'pry-byebug'
34
- s.add_development_dependency 'rails', '>= 6'
34
+ s.add_development_dependency 'rails', '>= 6', '< 7'
35
35
  s.add_development_dependency 'rollbar', '<= 2.24.0'
36
36
  s.add_development_dependency 'rspec-rails', '>= 3.7.0'
37
37
  s.add_development_dependency 'rubocop'
@@ -30,6 +30,8 @@ class DHS::Record
30
30
  DHS::Pagination::Start
31
31
  when :link
32
32
  DHS::Pagination::Link
33
+ when :next_offset
34
+ DHS::Pagination::NextOffset
33
35
  else
34
36
  DHS::Pagination::Offset
35
37
  end
@@ -43,7 +45,8 @@ class DHS::Record
43
45
  def paginated?(raw)
44
46
  raw.is_a?(Hash) && (
45
47
  raw.dig(*total_key).present? ||
46
- raw.dig(*limit_key(:body)).present?
48
+ raw.dig(*limit_key(:body)).present? ||
49
+ raw.dig(*pagination_key(:body)).present?
47
50
  )
48
51
  end
49
52
  end
@@ -237,7 +237,7 @@ class DHS::Record
237
237
  if pagination.parallel?
238
238
  load_and_merge_parallel_requests!(record, data, pagination, options)
239
239
  else
240
- load_and_merge_sequential_requests!(record, data, options, data._raw.dig(:next, :href), pagination)
240
+ load_and_merge_sequential_requests!(record, data, options, pagination)
241
241
  end
242
242
  end
243
243
 
@@ -249,13 +249,16 @@ class DHS::Record
249
249
  end
250
250
  end
251
251
 
252
- def load_and_merge_sequential_requests!(record, data, options, next_link, pagination)
253
- warn '[WARNING] You are loading all pages from a resource paginated with links only. As this is performed sequentially, it can result in very poor performance! (https://github.com/DePayFi/dhs#pagination-strategy-link).'
254
- while next_link.present?
255
- page_data = record.request(
256
- options.except(:all).merge(url: next_link)
257
- )
258
- next_link = page_data._raw.dig(:next, :href)
252
+ def load_and_merge_sequential_requests!(record, data, options, pagination)
253
+ warn '[WARNING] You are loading all pages from a resource paginated with sequential pagination.'
254
+ next_value = pagination.next(data._raw)
255
+ while next_value.present?
256
+ page_data = if next_value.is_a?(String) && next_value.match(/^http/)
257
+ record.request(options.except(:all).merge(url: next_value))
258
+ else
259
+ record.request(options.except(:all).merge(params: (options.dig(:params) || {}).merge(next_value) ))
260
+ end
261
+ next_value = pagination.next(page_data._raw)
259
262
  merge_batch_data_with_parent!(page_data, data, pagination)
260
263
  end
261
264
  end
@@ -469,6 +472,7 @@ class DHS::Record
469
472
  options = (provider_options || {})
470
473
  .deep_merge(endpoint.options || {})
471
474
  .deep_merge(options)
475
+ set_graphql_options!(options) if options.dig(:graphql).present?
472
476
  options[:url] = compute_url!(options[:params]) unless options.key?(:url)
473
477
  merge_explicit_params!(options[:params])
474
478
  options.delete(:params) if options[:params]&.empty?
@@ -476,6 +480,19 @@ class DHS::Record
476
480
  options
477
481
  end
478
482
 
483
+ def set_graphql_options!(options)
484
+ options[:method] = :post
485
+ variables = {}
486
+ options.dig(:graphql, :variables).each do |key|
487
+ variables[key] = options[:params][key]
488
+ options[:params].delete(key)
489
+ end
490
+ options[:body] = {
491
+ query: options.dig(:graphql, :query).squish,
492
+ variables: variables.to_json
493
+ }
494
+ end
495
+
479
496
  def inject_interceptors!(options)
480
497
  if DHS.config.request_cycle_cache_enabled
481
498
  inject_interceptor!(
@@ -8,7 +8,7 @@ class DHS::Record
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  included do
11
- class <<self
11
+ class << self
12
12
  alias_method :update, :create
13
13
  alias_method :update!, :create!
14
14
  end
@@ -7,6 +7,10 @@ class DHS::Pagination::Link < DHS::Pagination::Base
7
7
 
8
8
  alias count total
9
9
 
10
+ def next(current)
11
+ current.dig(:next, :href)
12
+ end
13
+
10
14
  def pages_left
11
15
  pages_left? ? 1 : 0
12
16
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHS::Pagination::NextOffset < DHS::Pagination::Base
4
+
5
+ DEFAULT_OFFSET = 0
6
+
7
+ def total
8
+ data._raw.dig(*_record.items_key).count || 0
9
+ end
10
+ alias count total
11
+
12
+ def parallel?
13
+ false
14
+ end
15
+
16
+ def pages_left?
17
+ next_offset = data._raw.dig(*_record.pagination_key(:body))
18
+ next_offset.present? && !next_offset.zero?
19
+ end
20
+
21
+ def next(current)
22
+ next_value = current.dig(*_record.pagination_key(:body))
23
+ return if next_value.blank? || next_value.zero?
24
+ {
25
+ _record.pagination_key(:parameter) => current.dig(*_record.pagination_key(:body))
26
+ }
27
+ end
28
+ end
data/lib/dhs/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DHS
4
- VERSION = '1.2.0'
4
+ VERSION = '1.4.0'
5
5
  end
data/lib/dhs.rb CHANGED
@@ -39,6 +39,7 @@ module DHS
39
39
  autoload :Page, 'dhs/pagination/page'
40
40
  autoload :TotalPages, 'dhs/pagination/total_pages'
41
41
  autoload :OffsetPage, 'dhs/pagination/offset_page'
42
+ autoload :NextOffset, 'dhs/pagination/next_offset'
42
43
  autoload :Start, 'dhs/pagination/start'
43
44
  autoload :Link, 'dhs/pagination/link'
44
45
  end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe 'main graphql support' do
6
+ let(:network) { 'ethereum' }
7
+ let(:address) { '0x317D875cA3B9f8d14f960486C0d1D1913be74e90' }
8
+
9
+ let!(:stubbed_request) do
10
+ stub_request(:post, 'https://graphql.bitquery.io/')
11
+ .with(
12
+ body: {
13
+ query: %{
14
+ query ($network: EthereumNetwork!, $address: String!) {
15
+ ethereum(network: $network) {
16
+ address(address: {is: $address}) {
17
+ balances {
18
+ currency {
19
+ address
20
+ name
21
+ symbol
22
+ decimals
23
+ tokenType
24
+ }
25
+ value
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }.squish,
31
+ variables: {
32
+ "network": network,
33
+ "address": address
34
+ }.to_json
35
+ }.to_json
36
+ ).to_return(body: {
37
+ "data": {
38
+ "ethereum": {
39
+ "address": [
40
+ {
41
+ balances: [
42
+ {
43
+ "currency": {
44
+ "address": '-',
45
+ "name": 'Ether',
46
+ "decimals": 18,
47
+ "symbol": 'ETH',
48
+ "tokenType": ''
49
+ },
50
+ "value": 0.11741978
51
+ },
52
+ {
53
+ "currency": {
54
+ "address": '0xb63b606ac810a52cca15e44bb630fd42d8d1d83d',
55
+ "name": 'Monaco',
56
+ "decimals": 8,
57
+ "symbol": 'MCO',
58
+ "tokenType": 'ERC20'
59
+ },
60
+ "value": 0
61
+ },
62
+ {
63
+ "currency": {
64
+ "address": '0x06012c8cf97bead5deae237070f9587f8e7a266d',
65
+ "name": 'CryptoKitties',
66
+ "decimals": 0,
67
+ "symbol": 'CK',
68
+ "tokenType": 'ERC721'
69
+ },
70
+ "value": 90
71
+ },
72
+ {
73
+ "currency": {
74
+ "address": '0xdac17f958d2ee523a2206206994597c13d831ec7',
75
+ "name": 'Tether USD',
76
+ "decimals": 6,
77
+ "symbol": 'USDT',
78
+ "tokenType": 'ERC20'
79
+ },
80
+ "value": 10
81
+ }
82
+ ]
83
+ }
84
+ ]
85
+ }
86
+ }
87
+ }.to_json)
88
+ end
89
+
90
+ before do
91
+ DHC.config.placeholder('bitquery', 'https://graphql.bitquery.io/')
92
+
93
+ class Record < DHS::Record
94
+
95
+ configuration items_key: [:data, :ethereum, :address, 0, :balances]
96
+
97
+ endpoint '{+bitquery}',
98
+ graphql: {
99
+ query: %{
100
+ query ($network: EthereumNetwork!, $address: String!) {
101
+ ethereum(network: $network) {
102
+ address(address: {is: $address}) {
103
+ balances {
104
+ currency {
105
+ address
106
+ name
107
+ symbol
108
+ decimals
109
+ tokenType
110
+ }
111
+ value
112
+ }
113
+ }
114
+ }
115
+ }
116
+ },
117
+ variables: %i[network address]
118
+ }
119
+ end
120
+ end
121
+
122
+ it 'fetches data from graphql and converts it into DHS Record structure' do
123
+ records = Record.where(network: 'ethereum', address: '0x317D875cA3B9f8d14f960486C0d1D1913be74e90').fetch
124
+ expect(records.first.currency.name).to eq 'Ether'
125
+ end
126
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe DHS::Record do
6
+
7
+ context 'pagination' do
8
+
9
+ def stub_api_request(items: [], offset: nil, next_offset:)
10
+ stub_request(:get, ["http://depay.fi/v2/transactions?limit=100", offset ? "offset=#{offset}" : nil].compact.join('&'))
11
+ .to_return(body: { items: items, next_offset: next_offset }.to_json)
12
+ end
13
+
14
+ let!(:requests) do
15
+ stub_api_request(items: (0...100).to_a, next_offset: 99)
16
+ stub_api_request(items: (100...200).to_a, offset: 99, next_offset: 199)
17
+ stub_api_request(items: (200...300).to_a, offset: 199, next_offset: 0)
18
+ end
19
+
20
+ before do
21
+ class Transaction < DHS::Record
22
+ configuration pagination_strategy: :next_offset, pagination_key: { body: :next_offset, parameter: :offset }
23
+
24
+ endpoint 'http://depay.fi/v2/transactions'
25
+ end
26
+ end
27
+
28
+ it 'fetches all the pages' do
29
+ transactions = Transaction.all.fetch
30
+ expect(transactions.to_a).to eq (0...300).to_a
31
+ end
32
+ end
33
+ end
@@ -167,7 +167,7 @@ describe DHS::Record do
167
167
  .includes(:users, contracts: :products)
168
168
  .find(1)
169
169
  end).to output(
170
- %r{\[WARNING\] You are loading all pages from a resource paginated with links only. As this is performed sequentially, it can result in very poor performance! \(https://github.com/DePayFi/dhs#pagination-strategy-link\).}
170
+ %r{\[WARNING\] You are loading all pages from a resource paginated with sequential pagination.}
171
171
  ).to_stderr
172
172
 
173
173
  expect(customer.users.length).to eq amount_of_users
@@ -348,7 +348,7 @@ describe DHS::Record do
348
348
  expect(lambda do
349
349
  all = Record.all.fetch
350
350
  end).to output(
351
- %r{\[WARNING\] You are loading all pages from a resource paginated with links only. As this is performed sequentially, it can result in very poor performance! \(https://github.com/DePayFi/dhs#pagination-strategy-link\).}
351
+ %r{\[WARNING\] You are loading all pages from a resource paginated with sequential pagination.}
352
352
  ).to_stderr
353
353
 
354
354
  expect(all).to be_kind_of Record
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dhs
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/DePayFi/dhs/graphs/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-06 00:00:00.000000000 Z
11
+ date: 2023-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: local_uri
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -129,6 +129,9 @@ dependencies:
129
129
  - - ">="
130
130
  - !ruby/object:Gem::Version
131
131
  version: '6'
132
+ - - "<"
133
+ - !ruby/object:Gem::Version
134
+ version: '7'
132
135
  type: :development
133
136
  prerelease: false
134
137
  version_requirements: !ruby/object:Gem::Requirement
@@ -136,6 +139,9 @@ dependencies:
136
139
  - - ">="
137
140
  - !ruby/object:Gem::Version
138
141
  version: '6'
142
+ - - "<"
143
+ - !ruby/object:Gem::Version
144
+ version: '7'
139
145
  - !ruby/object:Gem::Dependency
140
146
  name: rollbar
141
147
  requirement: !ruby/object:Gem::Requirement
@@ -321,6 +327,7 @@ files:
321
327
  - lib/dhs/item.rb
322
328
  - lib/dhs/pagination/base.rb
323
329
  - lib/dhs/pagination/link.rb
330
+ - lib/dhs/pagination/next_offset.rb
324
331
  - lib/dhs/pagination/offset.rb
325
332
  - lib/dhs/pagination/offset_page.rb
326
333
  - lib/dhs/pagination/page.rb
@@ -429,6 +436,7 @@ files:
429
436
  - spec/dummy/public/favicon.ico
430
437
  - spec/endpoint/for_url_spec.rb
431
438
  - spec/extended_rollbar_spec.rb
439
+ - spec/graphql/main_spec.rb
432
440
  - spec/item/access_errors_spec.rb
433
441
  - spec/item/accessors_spec.rb
434
442
  - spec/item/add_error_spec.rb
@@ -459,6 +467,7 @@ files:
459
467
  - spec/pagination/link/pages_left_spec.rb
460
468
  - spec/pagination/link/parallel_spec.rb
461
469
  - spec/pagination/link/total_spec.rb
470
+ - spec/pagination/next_offset_spec.rb
462
471
  - spec/pagination/offset/pages_left_spec.rb
463
472
  - spec/pagination/offset_page_spec.rb
464
473
  - spec/pagination/parameters_spec.rb
@@ -571,7 +580,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
571
580
  version: '0'
572
581
  requirements:
573
582
  - Ruby >= 2.7.2
574
- rubygems_version: 3.2.3
583
+ rubygems_version: 3.2.33
575
584
  signing_key:
576
585
  specification_version: 4
577
586
  summary: 'REST services accelerator: Rails gem providing an easy, active-record-like
@@ -665,6 +674,7 @@ test_files:
665
674
  - spec/dummy/public/favicon.ico
666
675
  - spec/endpoint/for_url_spec.rb
667
676
  - spec/extended_rollbar_spec.rb
677
+ - spec/graphql/main_spec.rb
668
678
  - spec/item/access_errors_spec.rb
669
679
  - spec/item/accessors_spec.rb
670
680
  - spec/item/add_error_spec.rb
@@ -695,6 +705,7 @@ test_files:
695
705
  - spec/pagination/link/pages_left_spec.rb
696
706
  - spec/pagination/link/parallel_spec.rb
697
707
  - spec/pagination/link/total_spec.rb
708
+ - spec/pagination/next_offset_spec.rb
698
709
  - spec/pagination/offset/pages_left_spec.rb
699
710
  - spec/pagination/offset_page_spec.rb
700
711
  - spec/pagination/parameters_spec.rb