hudu 0.3.0 → 0.3.2
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/CHANGELOG.md +14 -1
- data/Gemfile +1 -1
- data/README.md +22 -10
- data/hudu.gemspec +2 -2
- data/lib/hudu/api.rb +4 -3
- data/lib/hudu/asset_helper.rb +50 -17
- data/lib/hudu/authentication.rb +8 -7
- data/lib/hudu/client.rb +103 -38
- data/lib/hudu/configuration.rb +5 -4
- data/lib/hudu/connection.rb +3 -5
- data/lib/hudu/error.rb +3 -2
- data/lib/hudu/pagination.rb +49 -9
- data/lib/hudu/rate_throttle_middleware.rb +55 -50
- data/lib/hudu/version.rb +1 -1
- data/lib/hudu.rb +4 -6
- metadata +7 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1e4245c0dce3f094901ea8fdfbcf9b4c2d395b702968d5b869e60dad16adfc5
|
4
|
+
data.tar.gz: 2e44553c6bbac29f51fae2453a55c36b5fdeb26745cabf73c4cb6cfcc97e2eea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bc275aebd8cb6dbf89be44e4fdd59eb24761ab5d7b63c8687cf480f5ad6a3074dc911850705881e1bc2b173979393b6a0a859c6e13c7077e151d1853c71fe2d
|
7
|
+
data.tar.gz: c50428e0ec1922c8cc11fc1710ce82d99af88e1ac110c2a559da7b6baeed3c3183b5e8a28087e79e8bc40434baabb3ac9aef90d548b57941ce371152866b1bbd
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,21 @@
|
|
1
|
-
|
1
|
+
# Changelog
|
2
2
|
|
3
3
|
## [0.1.0] - 2024-02-20
|
4
|
+
|
4
5
|
- Initial release
|
5
6
|
|
6
7
|
## [0.2.0] - 2024-03-13
|
8
|
+
|
7
9
|
- update_company_asset added
|
8
10
|
|
11
|
+
## [0.3.0] - 2024-03-13
|
12
|
+
|
13
|
+
- throtling connection rate
|
14
|
+
|
15
|
+
## [0.3.1] - 2024-03-13
|
16
|
+
|
17
|
+
- rubocop fixes
|
18
|
+
|
19
|
+
## [0.3.2] - 2025-03-20
|
20
|
+
|
21
|
+
- update wrapi dependency
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# Hudu API
|
2
|
+
|
2
3
|
[](https://rubygems.org/gems/hudu)
|
3
4
|
[](https://codeclimate.com/github/jancotanis/hudu/maintainability)
|
4
5
|
[](https://codeclimate.com/github/jancotanis/hudu/test_coverage)
|
@@ -17,15 +18,20 @@ gem 'hudu'
|
|
17
18
|
|
18
19
|
And then execute:
|
19
20
|
|
20
|
-
|
21
|
+
```console
|
22
|
+
> bundle install
|
23
|
+
```
|
21
24
|
|
22
25
|
Or install it yourself as:
|
23
26
|
|
24
|
-
|
27
|
+
```console
|
28
|
+
> gem install hudu
|
29
|
+
```
|
25
30
|
|
26
31
|
## Usage
|
27
32
|
|
28
|
-
Before you start making the requests to API provide the endpoint and
|
33
|
+
Before you start making the requests to API provide the endpoint and
|
34
|
+
api key using the configuration wrapping.
|
29
35
|
|
30
36
|
```ruby
|
31
37
|
require 'hudu'
|
@@ -44,20 +50,23 @@ client.login
|
|
44
50
|
```
|
45
51
|
|
46
52
|
## Resources
|
53
|
+
|
47
54
|
### Authentication
|
48
|
-
|
55
|
+
|
56
|
+
```ruby
|
49
57
|
# setup
|
50
58
|
#
|
51
59
|
client.login
|
52
60
|
```
|
61
|
+
|
53
62
|
|Resource|API endpoint|Description|
|
54
63
|
|:--|:--|:--|
|
55
64
|
|.login| none |uses api_info to check if credentials are correct. Raises Hudu:AuthenticationError incase this fails|
|
56
65
|
|
57
|
-
|
58
|
-
|
59
66
|
### Data resources
|
67
|
+
|
60
68
|
Endpoint for data related requests
|
69
|
+
|
61
70
|
```ruby
|
62
71
|
|
63
72
|
# list all asset layouts/fields for a company
|
@@ -99,17 +108,20 @@ end
|
|
99
108
|
2. Add release to [CHANGELOG.md](CHANGELOG.md)
|
100
109
|
3. Commit.
|
101
110
|
4. Test build.
|
102
|
-
```
|
103
|
-
> rake build
|
104
111
|
|
112
|
+
```console
|
113
|
+
> rake build
|
105
114
|
```
|
115
|
+
|
106
116
|
5. Release
|
107
|
-
|
117
|
+
|
118
|
+
```console
|
108
119
|
> rake release
|
120
|
+
```
|
109
121
|
|
110
122
|
## Contributing
|
111
123
|
|
112
|
-
Bug reports and pull requests are welcome on GitHub
|
124
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/jancotanis/hudu).
|
113
125
|
|
114
126
|
## License
|
115
127
|
|
data/hudu.gemspec
CHANGED
@@ -29,9 +29,9 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
30
30
|
s.platform = Gem::Platform::RUBY
|
31
31
|
s.add_runtime_dependency 'faraday'
|
32
|
-
s.add_runtime_dependency 'wrapi',
|
32
|
+
s.add_runtime_dependency 'wrapi', '>= 0.4.9'
|
33
33
|
s.add_development_dependency 'dotenv'
|
34
34
|
s.add_development_dependency 'minitest'
|
35
|
-
s.add_development_dependency 'simplecov'
|
36
35
|
s.add_development_dependency 'rubocop'
|
36
|
+
s.add_development_dependency 'simplecov'
|
37
37
|
end
|
data/lib/hudu/api.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wrapi'
|
2
4
|
require File.expand_path('authentication', __dir__)
|
3
5
|
require File.expand_path('configuration', __dir__)
|
4
6
|
require File.expand_path('connection', __dir__)
|
@@ -6,9 +8,8 @@ require File.expand_path('connection', __dir__)
|
|
6
8
|
module Hudu
|
7
9
|
# @private
|
8
10
|
class API
|
9
|
-
|
10
11
|
# @private
|
11
|
-
attr_accessor
|
12
|
+
attr_accessor(*Configuration::VALID_OPTIONS_KEYS)
|
12
13
|
|
13
14
|
# Creates a new API and copies settings from singleton
|
14
15
|
def initialize(options = {})
|
data/lib/hudu/asset_helper.rb
CHANGED
@@ -1,32 +1,65 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Hudu
|
4
|
+
# The AssetHelper class contains helper methods for constructing and creating asset data.
|
3
5
|
class AssetHelper
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# Constructs an asset for updates by extracting key attributes and formatting custom fields.
|
7
|
+
#
|
8
|
+
# @param asset [Object] An Hudu entity that represents an asset, expected to respond to `attributes` and `fields`.
|
9
|
+
# @return [Hash] A formatted hash representing the asset for update operations.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# asset = SomeAssetEntity.new(attributes: { id: 1, name: "Asset 1", company_id: 101 }, fields: [field1, field2])
|
13
|
+
# Hudu::AssetHelper.construct_asset(asset)
|
14
|
+
# # => { asset:
|
15
|
+
# # { id: 1, company_id: 101, asset_layout_id: nil, slug: nil, name: "Asset 1", custom_fields: [...]
|
16
|
+
# # }
|
17
|
+
# # }
|
18
|
+
def self.construct_asset(asset)
|
19
|
+
custom_asset = asset.attributes.slice(
|
20
|
+
*%w[
|
21
|
+
id company_id asset_layout_id slug name
|
22
|
+
primary_serial primary_model primary_mail
|
23
|
+
primary_manufacturer
|
24
|
+
]
|
10
25
|
)
|
11
|
-
custom_asset['custom_fields'] =
|
26
|
+
custom_asset['custom_fields'] = custom_fields(asset.fields)
|
12
27
|
{ asset: custom_asset }
|
13
28
|
end
|
14
29
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
30
|
+
# Creates a new asset from the given layout and fields.
|
31
|
+
#
|
32
|
+
# @param name [String] The name of the new asset.
|
33
|
+
# @param asset_layout_id [Integer] The ID of the asset layout to use.
|
34
|
+
# @param fields [Array<Object>] A collection of field objects representing the asset's custom fields.
|
35
|
+
# @return [Hash] A formatted hash representing the new asset.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# fields = [Field.new(label: "Warranty", value: "2025"), Field.new(label: "Location", value: "NYC")]
|
39
|
+
# Hudu::AssetHelper.create_asset("New Asset", 10, fields)
|
40
|
+
# # => { asset: { name: "New Asset", asset_layout_id: 10, custom_fields: [...] } }
|
41
|
+
def self.create_asset(name, asset_layout_id, fields)
|
42
|
+
{
|
18
43
|
asset: {
|
19
44
|
name: name,
|
20
45
|
asset_layout_id: asset_layout_id,
|
21
|
-
custom_fields:
|
46
|
+
custom_fields: custom_fields(fields)
|
22
47
|
}
|
23
48
|
}
|
24
49
|
end
|
25
|
-
|
26
|
-
|
50
|
+
|
51
|
+
# Formats custom fields into a standardized hash structure.
|
52
|
+
#
|
53
|
+
# @param fields [Array<Object>] A collection of field objects, each expected to respond to `label` and `value`.
|
54
|
+
# @return [Array<Hash>] An array containing a single hash mapping field labels
|
55
|
+
# (downcased and underscored) to their values.
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# fields = [Field.new(label: "Warranty", value: "2025"), Field.new(label: "Location", value: "NYC")]
|
59
|
+
# Hudu::AssetHelper.custom_fields(fields)
|
60
|
+
# # => [{ "warranty" => "2025", "location" => "NYC" }]
|
27
61
|
def self.custom_fields(fields)
|
28
|
-
[fields.map{|field| [field.label.downcase.gsub(' ','_'),field.value]}.to_h]
|
62
|
+
[fields.map { |field| [field.label.downcase.gsub(' ', '_'), field.value] }.to_h]
|
29
63
|
end
|
30
|
-
|
31
64
|
end
|
32
|
-
end
|
65
|
+
end
|
data/lib/hudu/authentication.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('error', __dir__)
|
2
4
|
|
3
5
|
module Hudu
|
4
6
|
# Deals with authentication flow and stores it within global configuration
|
5
7
|
module Authentication
|
6
|
-
|
7
|
-
#
|
8
|
+
#
|
8
9
|
# Authorize to the Hudu portal and return access_token
|
9
10
|
def login(options = {})
|
10
|
-
raise ArgumentError,
|
11
|
-
|
12
|
-
|
11
|
+
raise ArgumentError, 'Accesstoken/api-key not set' unless api_key
|
12
|
+
|
13
|
+
connection_options.merge!({ headers: { "x-api-key": api_key } })
|
14
|
+
# only api key needed
|
13
15
|
# will do sanitty check if token if valid
|
14
|
-
get('/api/v1/api_info')
|
16
|
+
get('/api/v1/api_info', options)
|
15
17
|
rescue Faraday::Error => e
|
16
18
|
raise AuthenticationError, e
|
17
19
|
end
|
18
|
-
|
19
20
|
end
|
20
21
|
end
|
data/lib/hudu/client.rb
CHANGED
@@ -1,53 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('api', __dir__)
|
2
4
|
require File.expand_path('asset_helper', __dir__)
|
3
5
|
|
4
6
|
module Hudu
|
5
|
-
#
|
7
|
+
# The Client class serves as a wrapper for the Hudu REST API, providing methods to interact
|
8
|
+
# with various Hudu resources.
|
9
|
+
#
|
10
|
+
# This class dynamically defines methods to fetch, create, update, and manipulate Hudu resources
|
11
|
+
# such as companies, articles, assets, and more.
|
6
12
|
#
|
7
|
-
# @
|
13
|
+
# @example Basic Usage
|
14
|
+
# client.companies # Fetch all companies
|
15
|
+
# client.company(1) # Fetch a company by ID
|
16
|
+
# client.update_company(1, { name: "Updated Company" }) # Update a company
|
17
|
+
# client.create_company({ name: "New Company" }) # Create a new company
|
8
18
|
class Client < API
|
9
|
-
|
10
|
-
|
19
|
+
# Dynamically defines methods for interacting with Hudu API resources.
|
20
|
+
#
|
21
|
+
# Depending on the arguments, this will define methods to:
|
22
|
+
# - Fetch all records for a resource
|
23
|
+
# - Fetch a specific record by ID
|
24
|
+
# - Update a record
|
25
|
+
# - Create a new record
|
26
|
+
#
|
27
|
+
# @param method [Symbol] The method name for fetching all records.
|
28
|
+
# @param singular_method [Symbol, nil] The method name for fetching a single record by ID. Optional.
|
29
|
+
# @param path [String] The API path for the resource. Defaults to the method name.
|
30
|
+
#
|
31
|
+
# @example Defining endpoints
|
32
|
+
# api_endpoint :companies, :company
|
33
|
+
# # Defines:
|
34
|
+
# # - `companies(params = {})` to fetch all companies.
|
35
|
+
# # - `company(id, params = {})` to fetch a single company by ID.
|
36
|
+
# # - `update_companies(id, params = {})` to update a company.
|
37
|
+
# # - `create_companies(params = {})` to create a new company.
|
11
38
|
def self.api_endpoint(method, singular_method = nil, path = method)
|
12
|
-
# generate all and by id
|
13
39
|
if singular_method
|
14
|
-
# all records
|
15
|
-
|
40
|
+
# Define method to fetch all records and one by id
|
41
|
+
send(:define_method, method) do |params = {}|
|
16
42
|
r = get_paged(api_url(path), params)
|
17
|
-
|
43
|
+
hudu_data(r, method)
|
18
44
|
end
|
19
|
-
# record by
|
20
|
-
|
45
|
+
# Define method to fetch a single record by ID
|
46
|
+
send(:define_method, singular_method) do |id, params = {}|
|
21
47
|
r = get(api_url("#{path}/#{id}"), params)
|
22
|
-
|
48
|
+
hudu_data(r, singular_method)
|
23
49
|
end
|
24
50
|
else
|
25
|
-
#
|
26
|
-
|
51
|
+
# Define simple method to fetch data
|
52
|
+
send(:define_method, method) do |params = {}|
|
27
53
|
get(api_url(path), params)
|
28
54
|
end
|
29
55
|
end
|
30
|
-
|
31
|
-
|
56
|
+
|
57
|
+
# Define method to update a record
|
58
|
+
send(:define_method, "update_#{method}") do |id = nil, params = {}|
|
32
59
|
r = put(api_url("#{path}/#{id}"), params)
|
33
|
-
|
60
|
+
hudu_data(r, method)
|
34
61
|
end
|
35
|
-
|
36
|
-
|
62
|
+
|
63
|
+
# Define method to create a record
|
64
|
+
send(:define_method, "create_#{method}") do |id = nil, params = {}|
|
37
65
|
r = post(api_url("#{path}/#{id}"), params)
|
38
|
-
|
66
|
+
hudu_data(r, method)
|
39
67
|
end
|
40
|
-
|
41
68
|
end
|
42
69
|
|
43
|
-
|
70
|
+
# Define API endpoints for various resources
|
44
71
|
api_endpoint :api_info
|
45
|
-
|
46
|
-
# Activity logs can be filtered on
|
47
|
-
# user_id, user_email
|
48
|
-
# resource_id, resource_type
|
49
|
-
# action_message
|
50
|
-
# start_date - Must be in ISO 8601 format
|
51
72
|
api_endpoint :activity_logs
|
52
73
|
api_endpoint :companies, :company
|
53
74
|
api_endpoint :articles, :article
|
@@ -61,31 +82,75 @@ module Hudu
|
|
61
82
|
api_endpoint :relations
|
62
83
|
api_endpoint :magic_dashes, :magic_dash, 'magic_dash'
|
63
84
|
|
64
|
-
|
65
|
-
|
85
|
+
# Fetches all articles for a specific company.
|
86
|
+
#
|
87
|
+
# @param company_id [Integer] The ID of the company.
|
88
|
+
# @param params [Hash] Additional query parameters.
|
89
|
+
# @return [Array<Hash>] A list of articles.
|
90
|
+
def company_articles(company_id, params = {})
|
91
|
+
articles({ company_id: company_id }.merge(params))
|
66
92
|
end
|
67
|
-
|
93
|
+
|
94
|
+
# Fetches all assets for a specific company.
|
95
|
+
#
|
96
|
+
# @param id [Integer] The ID of the company.
|
97
|
+
# @param params [Hash] Additional query parameters.
|
98
|
+
# @return [Array<Hash>] A list of assets.
|
99
|
+
def company_assets(id, params = {})
|
68
100
|
get_paged(api_url("companies/#{id}/assets"), params)
|
69
101
|
end
|
70
|
-
|
102
|
+
|
103
|
+
# Fetches a specific asset for a company.
|
104
|
+
#
|
105
|
+
# @param company_id [Integer] The ID of the company.
|
106
|
+
# @param asset_id [Integer] The ID of the asset.
|
107
|
+
# @param params [Hash] Additional query parameters.
|
108
|
+
# @return [Hash] The asset details.
|
109
|
+
def company_asset(company_id, asset_id, params = {})
|
71
110
|
get(api_url("companies/#{company_id}/assets/#{asset_id}"), params)
|
72
111
|
end
|
73
112
|
|
113
|
+
# Updates an existing company asset.
|
114
|
+
#
|
115
|
+
# @param asset [Object] The asset object to update.
|
116
|
+
# @return [Hash] The updated asset data.
|
74
117
|
def update_company_asset(asset)
|
75
|
-
hudu_data(
|
118
|
+
hudu_data(
|
119
|
+
put(api_url("companies/#{asset.company_id}/assets/#{asset.id}"), AssetHelper.construct_asset(asset), false),
|
120
|
+
:asset
|
121
|
+
)
|
76
122
|
end
|
77
123
|
|
78
|
-
|
79
|
-
|
124
|
+
# Creates a new asset for a company.
|
125
|
+
#
|
126
|
+
# @param company_id [Integer] The ID of the company.
|
127
|
+
# @param asset_layout [Object] The asset layout object.
|
128
|
+
# @param fields [Array<Hash>] The custom fields for the asset.
|
129
|
+
# @return [Hash] The newly created asset data.
|
130
|
+
def create_company_asset(company_id, asset_layout, fields)
|
131
|
+
hudu_data(
|
132
|
+
post(
|
133
|
+
api_url("companies/#{company_id}/assets"),
|
134
|
+
AssetHelper.create_asset(asset_layout.name, asset_layout.id, fields),
|
135
|
+
false
|
136
|
+
), :asset
|
137
|
+
)
|
80
138
|
end
|
81
139
|
|
82
|
-
#
|
83
|
-
|
140
|
+
# Constructs the full API URL for a given path.
|
141
|
+
#
|
142
|
+
# @param path [String] The API path.
|
143
|
+
# @return [String] The full API URL.
|
144
|
+
def api_url(path)
|
84
145
|
"/api/v1/#{path}"
|
85
146
|
end
|
86
147
|
|
87
|
-
#
|
88
|
-
|
148
|
+
# Extracts resource data from the API response.
|
149
|
+
#
|
150
|
+
# @param result [Hash, Object] The API response.
|
151
|
+
# @param resource [Symbol] The name of the resource to extract.
|
152
|
+
# @return [Object] The resource data.
|
153
|
+
def hudu_data(result, resource)
|
89
154
|
if result.is_a?(WrAPI::Request::Entity) && result.attributes[resource.to_s]
|
90
155
|
result.send resource.to_s
|
91
156
|
else
|
data/lib/hudu/configuration.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'wrapi'
|
2
4
|
require File.expand_path('version', __dir__)
|
3
5
|
require File.expand_path('pagination', __dir__)
|
@@ -11,11 +13,11 @@ module Hudu
|
|
11
13
|
VALID_OPTIONS_KEYS = (WrAPI::Configuration::VALID_OPTIONS_KEYS + [:api_key]).freeze
|
12
14
|
|
13
15
|
# @private
|
14
|
-
attr_accessor
|
16
|
+
attr_accessor(*VALID_OPTIONS_KEYS)
|
15
17
|
|
16
|
-
DEFAULT_UA = "Ruby Hudu API wrapper #{Hudu::VERSION}"
|
18
|
+
DEFAULT_UA = "Ruby Hudu API wrapper #{Hudu::VERSION}"
|
17
19
|
DEFAULT_PAGINATION = RequestPagination::PagingInfoPager
|
18
|
-
DEFAULT_PAGE_SIZE =
|
20
|
+
DEFAULT_PAGE_SIZE = 100
|
19
21
|
|
20
22
|
# When this module is extended, set all configuration options to their default values
|
21
23
|
def self.extended(base)
|
@@ -40,4 +42,3 @@ module Hudu
|
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
43
|
-
|
data/lib/hudu/connection.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('rate_throttle_middleware', __dir__)
|
2
4
|
|
3
5
|
module Hudu
|
4
|
-
# Create connection including
|
5
|
-
# By default
|
6
|
-
# - Bearer authorization is access_token is not nil override with @setup_authorization
|
7
|
-
# - Headers setup for client-id and client-secret when client_id and client_secret are not nil @setup_headers
|
8
|
-
# @private
|
6
|
+
# Create connection including and keep it persistent so we add the rate throtling middleware only once
|
9
7
|
module Connection
|
10
8
|
private
|
11
9
|
|
data/lib/hudu/error.rb
CHANGED
data/lib/hudu/pagination.rb
CHANGED
@@ -1,42 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'json'
|
3
5
|
|
4
6
|
module Hudu
|
5
|
-
|
6
7
|
# Defines HTTP request methods
|
7
8
|
# @see https://support.hudu.com/hc/en-us/articles/11422780787735-REST-API#pagination-0-5
|
8
9
|
module RequestPagination
|
9
|
-
|
10
|
+
# The PagingInfoPager class provides a mechanism to handle pagination information for API responses.
|
11
|
+
#
|
12
|
+
# It manages the current page, page size, and provides utilities for determining if there are more pages to fetch.
|
13
|
+
#
|
14
|
+
# @example Basic Usage
|
15
|
+
# pager = PagingInfoPager.new(50)
|
16
|
+
# while pager.more_pages?
|
17
|
+
# response = api_client.get_data(pager.page_options)
|
18
|
+
# pager.next_page!(response.body)
|
19
|
+
# end
|
10
20
|
class PagingInfoPager
|
11
21
|
attr_reader :offset, :limit, :total
|
22
|
+
|
23
|
+
# Initializes a new PagingInfoPager instance.
|
24
|
+
#
|
25
|
+
# @param page_size [Integer] The number of records to fetch per page.
|
12
26
|
def initialize(page_size)
|
13
27
|
@page = 1
|
14
28
|
@page_total = @page_size = page_size
|
15
29
|
end
|
16
30
|
|
31
|
+
# Provides the current pagination parameter options for each rest request.
|
32
|
+
#
|
33
|
+
# @return [Hash] A hash containing the current page and page size.
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# pager.page_options # => { page: 1, page_size: 50 }
|
17
37
|
def page_options
|
18
38
|
{ page: @page, page_size: @page_size }
|
19
39
|
end
|
20
40
|
|
41
|
+
# Advances to the next page based on the response body and updates internal pagination state.
|
42
|
+
#
|
43
|
+
# @param body [Hash] The response body from the API, expected to contain a paginated resource.
|
44
|
+
# @return [Integer] The updated page total, typically the count of items on the current page.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# response_body = { "items" => [...] }
|
48
|
+
# pager.next_page!(response_body)
|
21
49
|
def next_page!(body)
|
22
50
|
@page += 1
|
23
51
|
a = PagingInfoPager.data(body)
|
24
52
|
@page_total = a.is_a?(Array) ? a.count : 1
|
25
53
|
end
|
26
54
|
|
55
|
+
# Determines whether there are more pages to fetch.
|
56
|
+
#
|
57
|
+
# @return [Boolean] Returns `true` if the current page is full, indicating another page might exist.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# pager.more_pages? # => true or false
|
27
61
|
def more_pages?
|
28
62
|
# while full page we have next page
|
29
63
|
@page_total == @page_size
|
30
64
|
end
|
31
65
|
|
32
|
-
|
66
|
+
# Extracts paginated data from the response body.
|
67
|
+
#
|
68
|
+
# @param body [Hash] The response body containing resource data, expected to be in a hash format.
|
69
|
+
# @return [Array, Hash, Object] Returns the extracted data, which could be an array, hash, or other object.
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# response_body = { "items" => [1, 2, 3] }
|
73
|
+
# PagingInfoPager.data(response_body) # => [1, 2, 3]
|
74
|
+
def self.data(body)
|
33
75
|
# assume hash {"resource":[...]}, get first key and return array data
|
34
|
-
result = body
|
35
|
-
if result
|
36
|
-
|
37
|
-
if v.is_a?(Array) || v.is_a?(Hash)
|
38
|
-
result = v
|
39
|
-
end
|
76
|
+
result = body
|
77
|
+
if result.respond_to?(:first)
|
78
|
+
_k, v = body.first
|
79
|
+
result = v if v.is_a?(Array) || v.is_a?(Hash)
|
40
80
|
end
|
41
81
|
result
|
42
82
|
end
|
@@ -1,67 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'faraday'
|
2
|
-
require 'thread'
|
3
4
|
|
4
|
-
|
5
|
-
#
|
6
|
-
# This middleware ensures that the number of requests made through a Faraday connection
|
7
|
-
# does not exceed a specified limit within a given time period.
|
8
|
-
#
|
9
|
-
# @example Add middleware to a Faraday connection
|
10
|
-
# connection = Faraday.new(url: 'https://api.example.com') do |faraday|
|
11
|
-
# faraday.use RateThrottleMiddleware, limit: 300, period: 60
|
12
|
-
# faraday.adapter Faraday.default_adapter
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# @see https://github.com/lostisland/faraday Faraday Documentation
|
16
|
-
#
|
17
|
-
class RateThrottleMiddleware < Faraday::Middleware
|
18
|
-
# Initializes the RateThrottleMiddleware.
|
5
|
+
module Hudu
|
6
|
+
# A Faraday middleware for rate limiting requests.
|
19
7
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# @param period [Integer] The time period in seconds over which the limit applies. Default is 60 seconds.
|
8
|
+
# This middleware ensures that the number of requests made through a Faraday connection
|
9
|
+
# does not exceed a specified limit within a given time period.
|
23
10
|
#
|
24
|
-
# @example
|
25
|
-
#
|
11
|
+
# @example Add middleware to a Faraday connection
|
12
|
+
# connection = Faraday.new(url: 'https://api.example.com') do |faraday|
|
13
|
+
# faraday.use RateThrottleMiddleware, limit: 300, period: 60
|
14
|
+
# faraday.adapter Faraday.default_adapter
|
15
|
+
# end
|
26
16
|
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@
|
33
|
-
@
|
34
|
-
|
17
|
+
# @see https://github.com/lostisland/faraday Faraday Documentation
|
18
|
+
#
|
19
|
+
class RateThrottleMiddleware < Faraday::Middleware
|
20
|
+
# Initializes the RateThrottleMiddleware.
|
21
|
+
#
|
22
|
+
# @param app [#call] The next middleware or the actual Faraday adapter.
|
23
|
+
# @param limit [Integer] The maximum number of requests allowed within the specified period. Default is 300.
|
24
|
+
# @param period [Integer] The time period in seconds over which the limit applies. Default is 60 seconds.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# middleware = RateThrottleMiddleware.new(app, limit: 300, period: 60)
|
28
|
+
#
|
29
|
+
def initialize(app, limit: 300, period: 60)
|
30
|
+
super(app)
|
31
|
+
@limit = limit
|
32
|
+
@period = period
|
33
|
+
@requests = []
|
34
|
+
@mutex = Mutex.new
|
35
|
+
@condition = ConditionVariable.new
|
36
|
+
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def call(env)
|
39
|
+
throttle_request
|
40
|
+
@app.call(env)
|
41
|
+
end
|
40
42
|
|
41
|
-
private
|
43
|
+
private
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
def throttle_request
|
46
|
+
@mutex.synchronize do
|
47
|
+
now = Time.now.to_f
|
48
|
+
remove_expired_requests(now)
|
49
|
+
|
50
|
+
rate_limited(now)
|
51
|
+
|
52
|
+
# Record the new request
|
53
|
+
@requests.push(Time.now.to_f)
|
54
|
+
@condition.broadcast
|
49
55
|
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def remove_expired_requests(now)
|
59
|
+
# Clear requests older than the rate limit period
|
60
|
+
@requests.pop while !@requests.empty? && @requests[0] < (now - @period)
|
61
|
+
end
|
50
62
|
|
63
|
+
def rate_limited(now)
|
51
64
|
# Wait if the request limit is reached
|
52
65
|
while @requests.size >= @limit
|
53
66
|
sleep_time = @requests[0] + @period - now
|
54
|
-
|
55
|
-
|
56
|
-
now = Time.now.to_f
|
57
|
-
while !@requests.empty? && @requests[0] < (now - @period)
|
58
|
-
@requests.pop
|
59
|
-
end
|
67
|
+
@condition.wait(@mutex, sleep_time) if sleep_time.positive?
|
68
|
+
remove_expired_requests(Time.now.to_f)
|
60
69
|
end
|
61
|
-
|
62
|
-
# Record the new request
|
63
|
-
@requests.push(Time.now.to_f)
|
64
|
-
@condition.broadcast
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
data/lib/hudu/version.rb
CHANGED
data/lib/hudu.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wrapi'
|
2
4
|
require File.expand_path('hudu/configuration', __dir__)
|
3
5
|
require File.expand_path('hudu/client', __dir__)
|
4
6
|
|
7
|
+
# The Hudu module provides utilities to manage and manipulate assets within the Hudu api
|
5
8
|
module Hudu
|
6
9
|
extend Configuration
|
7
10
|
extend WrAPI::RespondTo
|
@@ -11,9 +14,4 @@ module Hudu
|
|
11
14
|
def self.client(options = {})
|
12
15
|
Hudu::Client.new(options)
|
13
16
|
end
|
14
|
-
|
15
|
-
def self.reset
|
16
|
-
super
|
17
|
-
|
18
|
-
end
|
19
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hudu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janco Tanis
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-03-20 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: faraday
|
@@ -30,14 +29,14 @@ dependencies:
|
|
30
29
|
requirements:
|
31
30
|
- - ">="
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
32
|
+
version: 0.4.9
|
34
33
|
type: :runtime
|
35
34
|
prerelease: false
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
37
|
- - ">="
|
39
38
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
39
|
+
version: 0.4.9
|
41
40
|
- !ruby/object:Gem::Dependency
|
42
41
|
name: dotenv
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,7 +66,7 @@ dependencies:
|
|
67
66
|
- !ruby/object:Gem::Version
|
68
67
|
version: '0'
|
69
68
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
69
|
+
name: rubocop
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
72
71
|
requirements:
|
73
72
|
- - ">="
|
@@ -81,7 +80,7 @@ dependencies:
|
|
81
80
|
- !ruby/object:Gem::Version
|
82
81
|
version: '0'
|
83
82
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
83
|
+
name: simplecov
|
85
84
|
requirement: !ruby/object:Gem::Requirement
|
86
85
|
requirements:
|
87
86
|
- - ">="
|
@@ -94,7 +93,6 @@ dependencies:
|
|
94
93
|
- - ">="
|
95
94
|
- !ruby/object:Gem::Version
|
96
95
|
version: '0'
|
97
|
-
description:
|
98
96
|
email: gems@jancology.com
|
99
97
|
executables: []
|
100
98
|
extensions: []
|
@@ -125,7 +123,6 @@ licenses:
|
|
125
123
|
metadata:
|
126
124
|
homepage_uri: https://rubygems.org/gems/hudu
|
127
125
|
source_code_uri: https://github.com/jancotanis/hudu
|
128
|
-
post_install_message:
|
129
126
|
rdoc_options: []
|
130
127
|
require_paths:
|
131
128
|
- lib
|
@@ -140,8 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
137
|
- !ruby/object:Gem::Version
|
141
138
|
version: '0'
|
142
139
|
requirements: []
|
143
|
-
rubygems_version: 3.
|
144
|
-
signing_key:
|
140
|
+
rubygems_version: 3.6.2
|
145
141
|
specification_version: 4
|
146
142
|
summary: A Ruby wrapper for the Hudu APIs (readonly)
|
147
143
|
test_files: []
|