booqable 1.0.0 → 1.2.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: c084014d612e3e317590f31144911c3a3907a85075a17b3172edd2f6eb8c7d9f
4
- data.tar.gz: 23836e46596f37b74175cd45f4e6ee04ee17eb7b0254bb53395972bc801a55aa
3
+ metadata.gz: ce7d8b0be4851e4e78229078331a2f73cbe7cb7702c1555df9dec0903be28d0e
4
+ data.tar.gz: c79ed318eb3a38534291ef5e4abeb6a5749c7a2684bcd1befbf43d6f7f1d0e8a
5
5
  SHA512:
6
- metadata.gz: 0a2eb78b9a78a239bec1fda211b177533102e78e4cdd95470692cb5a21132180827d2339caadf723564de9d8091d15c4e6160489f5a4694dfe40fc8bd4a630c2
7
- data.tar.gz: 7210d24c6985facb00617c0949abe55c0b9034acb53b3120039c7d78e1da3ecd763875ff54c69672e39701049a3ab46f46024c1c6511251840891163a19518c0
6
+ metadata.gz: 9d6dead61b0b4bfd8374c9b51aec7dccc724988b5417f9585018ee549840ee207756642b38aedbfcf4fdb4e313f9764fdc64b1d1d6e7593c057edb20d6095afc
7
+ data.tar.gz: c5e34b3a736ab3bdca397dfd1245d07b8dd8e246e32d4d0f24e097c41f52d47307c645a547592e60b90dc27f98e2e817193a1059f2696c0a726316c0693c1f88
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
- ## [Unreleased]
1
+ ## [1.2.0] - 2026-05-27
2
2
 
3
- ## [0.1.0] - 2025-06-27
3
+ - Add optional `around_refresh_token` configuration. When provided, the OAuth
4
+ middleware yields the read + expiry-check + refresh sequence to the callable
5
+ so host applications can serialize concurrent token refreshes (e.g. with a
6
+ database transaction and advisory lock). The gem keeps no lock dependency.
7
+
8
+
9
+ ## [1.1.0] - 2026-03-11
10
+
11
+ - Add `parse_resource` method (aliased as `deserialize_resource`) for parsing
12
+ JSON:API payloads into Sawyer::Resource objects with dot-notation access.
13
+ Useful for parsing webhook payloads or raw API responses.
14
+ - Add Booqable::RefreshTokenRevoked and Booqable::InvalidGrant error types for
15
+ invalid grant OAuth response scenarios
16
+ - Add "all" as an alias for "list" method on all resources.
17
+ You can now use `Booqable.orders.all` as an alternative to `Booqable.orders.list`.
18
+
19
+ ## [1.0.0] - 2025-10-23
4
20
 
5
21
  - Initial release
data/README.md CHANGED
@@ -5,6 +5,7 @@ Ruby toolkit for the [Booqable API](https://developers.booqable.com/).
5
5
  [Booqable](https://booqable.com) is a rental management platform that helps businesses manage their rental inventory, customers, and orders. This gem provides a Ruby interface to interact with all Booqable API endpoints.
6
6
 
7
7
  ## Table of Contents
8
+
8
9
  - [Installation](#installation)
9
10
  - [Making requests](#making-requests)
10
11
  - [Authentication](#authentication)
@@ -12,6 +13,7 @@ Ruby toolkit for the [Booqable API](https://developers.booqable.com/).
12
13
  - [Pagination](#pagination)
13
14
  - [Rate limiting](#rate-limiting)
14
15
  - [Resources](#resources)
16
+ - [Parsing JSON:API payloads](#parsing-jsonapi-payloads)
15
17
  - [Advanced usage](#advanced-usage)
16
18
  - [Development](#development)
17
19
 
@@ -89,11 +91,11 @@ client = Booqable::Client.new(
89
91
  client_id: 'your_oauth_client_id',
90
92
  client_secret: 'your_oauth_client_secret',
91
93
  company_id: 'your_company_id',
92
- read_token: -> {
94
+ read_token: -> {
93
95
  # Return stored token hash
94
96
  JSON.parse(File.read('token.json'))
95
97
  },
96
- write_token: ->(token) {
98
+ write_token: ->(token) {
97
99
  # Store token hash
98
100
  File.write('token.json', token.to_json)
99
101
  }
@@ -103,6 +105,30 @@ client = Booqable::Client.new(
103
105
  client.authenticate_with_code(params[:code])
104
106
  ```
105
107
 
108
+ #### Serializing concurrent token refreshes
109
+
110
+ When multiple processes share the same OAuth token (e.g. the same installation
111
+ serving concurrent requests), pass an `around_refresh_token` callable to
112
+ serialize the read + expiry-check + refresh sequence. The middleware yields
113
+ to the callable once per request; the host application decides how to lock.
114
+
115
+ ```ruby
116
+ Booqable::Client.new(
117
+ # ...other oauth options...
118
+ around_refresh_token: ->(&block) {
119
+ AppInstallation.transaction do
120
+ installation.with_advisory_lock!("app_installation:#{installation.id}", transaction: true) do
121
+ installation.reload
122
+ block.call
123
+ end
124
+ end
125
+ }
126
+ )
127
+ ```
128
+
129
+ The gem itself has no advisory-lock dependency — `around_refresh_token` is
130
+ just a callable that takes a block.
131
+
106
132
  ### Single-Use Token Authentication
107
133
 
108
134
  For server-to-server communication requiring enhanced security:
@@ -159,7 +185,7 @@ client = Booqable::Client.new(
159
185
 
160
186
  ## Pagination
161
187
 
162
- The Booqable API uses cursor-based pagination. Booqable provides several ways to handle paginated responses:
188
+ Booqable provides several ways to handle paginated responses:
163
189
 
164
190
  ### Manual pagination
165
191
 
@@ -222,6 +248,13 @@ orders = Booqable.orders.list(
222
248
  sort: '-created_at'
223
249
  )
224
250
 
251
+ # `all` is an alias for `list`
252
+ orders = Booqable.orders.all(
253
+ include: 'customer,items',
254
+ filter: { status: 'reserved' },
255
+ sort: '-created_at'
256
+ )
257
+
225
258
  # Find specific order
226
259
  order = Booqable.orders.find('order_id', include: 'customer,items')
227
260
  order.items.count
@@ -236,7 +269,7 @@ new_order = Booqable.orders.create(
236
269
  new_order.status # => 'draft'
237
270
 
238
271
  # Update order
239
- updated_order = Booqable.orders.update('order_id', status: 'reserved')
272
+ updated_order = Booqable.orders.update('order_id', stops_at: '2024-01-03T00:00:00Z')
240
273
  updated_order.status # => 'reserved'
241
274
 
242
275
  # Delete order
@@ -296,20 +329,83 @@ deleted_product.id # => 'product_id'
296
329
  Booqable provides access to all Booqable API resources:
297
330
 
298
331
  **Core Resources:**
332
+
299
333
  - `orders`, `customers`, `products`, `items`
300
334
  - `employees`, `companies`, `locations`
301
335
  - `payments`, `invoices`, `documents`
302
336
 
303
337
  **Inventory Management:**
338
+
304
339
  - `inventory_levels`, `stock_items`, `stock_adjustments`
305
340
  - `transfers`, `plannings`, `clusters`
306
341
 
307
342
  **Configuration:**
343
+
308
344
  - `settings`, `properties`, `tax_rates`
309
345
  - `payment_methods`, `email_templates`
310
346
 
311
347
  **And many more...** See the [full resource list](lib/booqable/resources.json) for all available endpoints.
312
348
 
349
+ ## Parsing JSON:API payloads
350
+
351
+ Booqable provides a convenient way to parse JSON:API formatted data (such as webhook payloads or raw API responses) into Ruby objects with dot-notation access:
352
+
353
+ ```ruby
354
+ # Parse a webhook payload
355
+ payload = {
356
+ "data" => {
357
+ "id" => "abc-123",
358
+ "type" => "customers",
359
+ "attributes" => {
360
+ "name" => "John Doe",
361
+ "email" => "john@example.com",
362
+ "created_at" => "2024-01-15T10:30:00Z"
363
+ }
364
+ }
365
+ }
366
+
367
+ customer = Booqable.parse_resource(payload)
368
+ customer.id # => "abc-123"
369
+ customer.name # => "John Doe"
370
+ customer.email # => "john@example.com"
371
+ customer.created_at # => 2024-01-15 10:30:00 UTC (Time object)
372
+ ```
373
+
374
+ ### With nested relationships
375
+
376
+ ```ruby
377
+ payload = {
378
+ "data" => {
379
+ "id" => "order-123",
380
+ "type" => "orders",
381
+ "attributes" => { "status" => "reserved" },
382
+ "relationships" => {
383
+ "customer" => { "data" => { "id" => "customer-456", "type" => "customers" } }
384
+ }
385
+ },
386
+ "included" => [
387
+ {
388
+ "id" => "customer-456",
389
+ "type" => "customers",
390
+ "attributes" => { "name" => "Jane Smith" }
391
+ }
392
+ ]
393
+ }
394
+
395
+ order = Booqable.parse_resource(payload)
396
+ order.status # => "reserved"
397
+ order.customer.name # => "Jane Smith"
398
+ ```
399
+
400
+ ### Using with a client instance
401
+
402
+ ```ruby
403
+ # If you already have a client instance
404
+ customer = client.parse_resource(payload)
405
+ ```
406
+
407
+ `deserialize_resource` is available as an alias for `parse_resource`.
408
+
313
409
  ## Advanced usage
314
410
 
315
411
  ### Custom middleware
@@ -346,18 +442,6 @@ Booqable.configure do |c|
346
442
  end
347
443
  ```
348
444
 
349
- ### Custom serialization
350
-
351
- ```ruby
352
- # Access raw response data
353
- response = Booqable.orders.list
354
- puts response.class # => Sawyer::Resource
355
-
356
- # Get response metadata
357
- puts Booqable.last_response.status
358
- puts Booqable.last_response.headers
359
- ```
360
-
361
445
  ## Development
362
446
 
363
447
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -376,7 +460,7 @@ bundle exec rspec spec/booqable/client_spec.rb
376
460
 
377
461
  ### Contributing
378
462
 
379
- Bug reports and pull requests are welcome on GitHub at https://github.com/booqable/booqable.rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/booqable/booqable.rb/blob/master/CODE_OF_CONDUCT.md).
463
+ Bug reports and pull requests are welcome on GitHub at https://github.com/booqable/booqable.rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/booqable/booqable.rb/blob/main/CODE_OF_CONDUCT.md).
380
464
 
381
465
  ## Versioning
382
466
 
@@ -396,7 +480,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
396
480
 
397
481
  ## Code of Conduct
398
482
 
399
- Everyone interacting in the Booqable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/booqable/booqable.rb/blob/master/CODE_OF_CONDUCT.md).
483
+ Everyone interacting in the Booqable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/booqable/booqable.rb/blob/main/CODE_OF_CONDUCT.md).
400
484
 
401
485
  ---
402
486
 
data/lib/booqable/auth.rb CHANGED
@@ -57,7 +57,8 @@ module Booqable
57
57
  api_endpoint: api_endpoint,
58
58
  redirect_uri: redirect_uri,
59
59
  read_token: read_token,
60
- write_token: write_token
60
+ write_token: write_token,
61
+ around_refresh_token: around_refresh_token
61
62
  } if oauth_authenticated?
62
63
 
63
64
  builder.use Booqable::Middleware::Auth::ApiKey, {
@@ -56,6 +56,23 @@ module Booqable
56
56
  end
57
57
  end
58
58
 
59
+ # Parse a JSON:API payload into a Sawyer::Resource
60
+ #
61
+ # Converts JSON:API formatted data (from webhooks or API responses) into
62
+ # Ruby objects with dot-notation access for convenient attribute access.
63
+ #
64
+ # @param payload [String, Hash] JSON:API payload (string or parsed hash)
65
+ # @return [Sawyer::Resource, nil] Parsed resource object, or nil for empty input
66
+ #
67
+ # @example
68
+ # customer = client.parse_resource(webhook_payload)
69
+ # customer.name # => "John"
70
+ #
71
+ def parse_resource(payload)
72
+ Booqable::ResourceParser.parse(payload)
73
+ end
74
+ alias_method :deserialize_resource, :parse_resource
75
+
59
76
  # String representation of the client with sensitive information masked
60
77
  #
61
78
  # Overrides the default inspect method to hide sensitive configuration
@@ -48,6 +48,7 @@ module Booqable
48
48
  :proxy,
49
49
  :read_token,
50
50
  :redirect_uri,
51
+ :around_refresh_token,
51
52
  :single_use_token,
52
53
  :single_use_token_algorithm,
53
54
  :single_use_token_company_id,
@@ -81,6 +82,7 @@ module Booqable
81
82
  proxy
82
83
  read_token
83
84
  redirect_uri
85
+ around_refresh_token
84
86
  single_use_token
85
87
  single_use_token_algorithm
86
88
  single_use_token_company_id
@@ -151,6 +151,17 @@ module Booqable
151
151
  Proc.new { }
152
152
  end
153
153
 
154
+ # Default `around_refresh_token` callable
155
+ #
156
+ # When non-nil, the OAuth middleware yields its read+check+refresh
157
+ # sequence to this callable so the host application can serialize
158
+ # concurrent refreshes (e.g. with an advisory lock).
159
+ #
160
+ # @return [Proc, nil]
161
+ def around_refresh_token
162
+ nil
163
+ end
164
+
154
165
  # Default API key from ENV
155
166
  # @return [String, nil] API key for authentication
156
167
  def api_key
@@ -42,8 +42,8 @@ module Booqable
42
42
  # headers = response[:response_headers]
43
43
 
44
44
  if klass = case status
45
- when 400 then error_for_400(body)
46
- when 401 then Booqable::Unauthorized
45
+ when 400 then error_for_400(response)
46
+ when 401 then error_for_401(response)
47
47
  when 402 then error_for_402(body)
48
48
  when 403 then Booqable::Forbidden
49
49
  when 404 then error_for_404(body)
@@ -85,8 +85,8 @@ module Booqable
85
85
  # Return most appropriate error for 400 HTTP status code
86
86
  # @private
87
87
  # rubocop:disable Metrics/CyclomaticComplexity
88
- def self.error_for_400(body)
89
- case body
88
+ def self.error_for_400(response)
89
+ case response.body
90
90
  when /unwrittable_attribute/i
91
91
  Booqable::ReadOnlyAttribute
92
92
  when /unknown_attribute/i
@@ -103,12 +103,25 @@ module Booqable
103
103
  Booqable::InvalidFilter
104
104
  when /required filter/i
105
105
  Booqable::RequiredFilter
106
+ when /invalid_grant/i
107
+ error_for_invalid_grant(response)
106
108
  else
107
109
  Booqable::BadRequest
108
110
  end
109
111
  end
110
112
  # rubocop:enable Metrics/CyclomaticComplexity
111
113
 
114
+ # Return most appropriate error for 401 HTTP status code
115
+ # @private
116
+ def self.error_for_401(response)
117
+ case response.body
118
+ when /token is invalid \(revoked\)/i
119
+ Booqable::TokenRevoked
120
+ else
121
+ Booqable::Unauthorized
122
+ end
123
+ end
124
+
112
125
  # Return most appropriate error for 402 HTTP status code
113
126
  # @private
114
127
  # rubocop:disable Metrics/CyclomaticComplexity
@@ -163,6 +176,26 @@ module Booqable
163
176
  end
164
177
  end
165
178
 
179
+ # Return most appropriate error for invalid_grant OAuth error
180
+ #
181
+ # Determines whether the invalid_grant error is due to a revoked refresh token
182
+ # or a different OAuth grant error by examining the grant_type parameter
183
+ # in the request body.
184
+ #
185
+ # @param response [Hash] HTTP response containing the request body
186
+ # @return [Class] RefreshTokenRevoked if grant_type is refresh_token, InvalidGrant otherwise
187
+ # @private
188
+ def self.error_for_invalid_grant(response)
189
+ grant_type = CGI.parse(response.request_body).dig("grant_type", 0)
190
+
191
+ case grant_type
192
+ when /refresh_token/i
193
+ Booqable::RefreshTokenRevoked
194
+ else
195
+ Booqable::InvalidGrant
196
+ end
197
+ end
198
+
166
199
  # Array of validation errors
167
200
  # @return [Array<Hash>] Error info
168
201
  def errors
@@ -300,6 +333,20 @@ module Booqable
300
333
  # and body matches 'required filter'
301
334
  class RequiredFilter < ClientError; end
302
335
 
336
+ # Raised when Booqable returns a 401 HTTP status code
337
+ # and body matches 'token is invalid (revoked)'
338
+ class TokenRevoked < ClientError; end
339
+
340
+ # Raised when Booqable returns a 400 HTTP status code
341
+ # and body matches 'invalid_grant' and
342
+ # the grant type is refresh token (OAuth error)
343
+ class RefreshTokenRevoked < TokenRevoked; end
344
+
345
+ # Raised when Booqable returns a 400 HTTP status code
346
+ # and body matches 'invalid_grant' and
347
+ # grant type is not refresh token (OAuth error)
348
+ class InvalidGrant < ClientError; end
349
+
303
350
  # Raised when Booqable returns a 401 HTTP status code
304
351
  class Unauthorized < ClientError; end
305
352
 
@@ -24,6 +24,10 @@ module Booqable
24
24
  # @option options [String] :api_endpoint API endpoint URL for the OAuth provider
25
25
  # @option options [Proc] :read_token Proc to read stored token
26
26
  # @option options [Proc] :write_token Proc to store new token
27
+ # @option options [Proc, nil] :around_refresh_token Optional callable
28
+ # invoked with a block around the read+check+refresh sequence. The
29
+ # host application can use it to serialize concurrent refreshes
30
+ # (e.g. wrap the block in a database transaction + advisory lock).
27
31
  # @raise [KeyError] If required options are not provided
28
32
  def initialize(app, options = {})
29
33
  super(app)
@@ -33,6 +37,7 @@ module Booqable
33
37
  @api_endpoint = options.fetch(:api_endpoint)
34
38
  @read_token = options.fetch(:read_token)
35
39
  @write_token = options.fetch(:write_token)
40
+ @around_refresh_token = options[:around_refresh_token]
36
41
 
37
42
  @client = OAuthClient.new(
38
43
  client_id: @client_id,
@@ -50,10 +55,12 @@ module Booqable
50
55
  # @param env [Faraday::Env] The request environment
51
56
  # @return [Faraday::Response] The response from the next middleware
52
57
  def call(env)
53
- @token = @client.get_access_token_from_hash(@read_token.call)
58
+ around_refresh_token do
59
+ @token = @client.get_access_token_from_hash(@read_token.call)
54
60
 
55
- if @token.expired? || @token.expires_at.nil?
56
- @token = refresh_token!
61
+ if @token.expired? || @token.expires_at.nil?
62
+ @token = refresh_token!
63
+ end
57
64
  end
58
65
 
59
66
  env.request_headers["Authorization"] ||= "Bearer #{@token.token}"
@@ -63,6 +70,16 @@ module Booqable
63
70
 
64
71
  private
65
72
 
73
+ # Yield to the configured around-callback, if any
74
+ #
75
+ # When a host application provides one (e.g. an advisory lock), the
76
+ # read+check+refresh sequence runs inside it so concurrent callers
77
+ # cannot interleave a read with another caller's refresh.
78
+ def around_refresh_token(&block)
79
+ return yield unless @around_refresh_token
80
+ @around_refresh_token.call(&block)
81
+ end
82
+
66
83
  # Refresh the expired OAuth token
67
84
  #
68
85
  # Uses the refresh token to obtain a new access token and stores it
@@ -78,7 +95,7 @@ module Booqable
78
95
 
79
96
  new_token
80
97
  rescue OAuth2::Error => e
81
- response = e.response.response.env.to_h
98
+ response = e.response.response.env
82
99
 
83
100
  Booqable::Error.from_response(response)
84
101
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booqable
4
+ # Parses JSON:API payloads into Sawyer::Resource objects
5
+ #
6
+ # JSON:API formatted data (from webhooks or API responses) is converted
7
+ # into Ruby objects with dot-notation access for convenient attribute access.
8
+ #
9
+ # @example Parse a JSON:API payload
10
+ # payload = '{"data":{"id":"123","type":"customers","attributes":{"name":"John"}}}'
11
+ # customer = Booqable::ResourceParser.parse(payload)
12
+ # customer.id # => "123"
13
+ # customer.name # => "John"
14
+ #
15
+ # @example Parse with nested relationships
16
+ # payload = {
17
+ # "data" => {
18
+ # "id" => "123",
19
+ # "type" => "orders",
20
+ # "attributes" => { "status" => "reserved" },
21
+ # "relationships" => {
22
+ # "customer" => { "data" => { "id" => "456", "type" => "customers" } }
23
+ # }
24
+ # },
25
+ # "included" => [
26
+ # { "id" => "456", "type" => "customers", "attributes" => { "name" => "John" } }
27
+ # ]
28
+ # }
29
+ # order = Booqable::ResourceParser.parse(payload)
30
+ # order.customer.name # => "John"
31
+ #
32
+ class ResourceParser
33
+ # Parse a JSON:API payload into a Sawyer::Resource
34
+ #
35
+ # @param payload [String, Hash] JSON:API payload (string or parsed hash)
36
+ # @return [Sawyer::Resource, nil] Parsed resource object with dot-notation access,
37
+ # or nil for empty/nil input
38
+ def self.parse(payload)
39
+ new(payload).parse
40
+ end
41
+
42
+ # Initialize a new ResourceParser
43
+ #
44
+ # @param payload [String, Hash] JSON:API payload
45
+ def initialize(payload)
46
+ @payload = payload
47
+ end
48
+
49
+ # Parse the payload into a Sawyer::Resource
50
+ #
51
+ # @return [Sawyer::Resource, nil] Parsed resource or nil for empty input
52
+ def parse
53
+ return nil if @payload.nil?
54
+
55
+ json_string = @payload.is_a?(String) ? @payload : @payload.to_json
56
+ return nil if json_string.strip.empty?
57
+
58
+ serializer = JsonApiSerializer.any_json
59
+ decoded = serializer.decode(json_string)
60
+
61
+ return nil unless decoded && decoded[:data]
62
+
63
+ Sawyer::Resource.new(sawyer_agent, decoded[:data])
64
+ end
65
+
66
+ private
67
+
68
+ # Create a minimal Sawyer agent for wrapping resources
69
+ #
70
+ # The agent URL is a placeholder - we don't make any HTTP requests.
71
+ # We just need the agent to create Sawyer::Resource objects that
72
+ # provide dot-notation attribute access.
73
+ #
74
+ # @return [Sawyer::Agent]
75
+ def sawyer_agent
76
+ @sawyer_agent ||= Sawyer::Agent.new("https://example.com") do |http|
77
+ http.headers[:content_type] = "application/json"
78
+ end
79
+ end
80
+ end
81
+ end
@@ -58,6 +58,13 @@ module Booqable
58
58
  paginate @resource, params
59
59
  end
60
60
 
61
+ # Alias for list
62
+ #
63
+ # @see list
64
+ def all(params = {})
65
+ list(params)
66
+ end
67
+
61
68
  # Find a specific resource by ID
62
69
  #
63
70
  # Retrieves a single resource by its unique identifier.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Booqable
4
- VERSION = "1.0.0"
4
+ VERSION = "1.2.0"
5
5
  end
data/lib/booqable.rb CHANGED
@@ -25,6 +25,7 @@ require_relative "booqable/resources"
25
25
  require_relative "booqable/auth"
26
26
  require_relative "booqable/http"
27
27
  require_relative "booqable/client"
28
+ require_relative "booqable/resource_parser"
28
29
 
29
30
  # Main Booqable module providing access to the Booqable API
30
31
  #
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: booqable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hrvoje Šimić
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-10-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: faraday
@@ -138,6 +137,7 @@ files:
138
137
  - lib/booqable/middleware/raise_error.rb
139
138
  - lib/booqable/oauth_client.rb
140
139
  - lib/booqable/rate_limit.rb
140
+ - lib/booqable/resource_parser.rb
141
141
  - lib/booqable/resource_proxy.rb
142
142
  - lib/booqable/resources.json
143
143
  - lib/booqable/resources.rb
@@ -149,10 +149,9 @@ licenses:
149
149
  metadata:
150
150
  homepage_uri: https://github.com/booqable/booqable.rb
151
151
  source_code_uri: https://github.com/booqable/booqable.rb
152
- changelog_uri: https://github.com/booqable/booqable.rb/blob/master/CHANGELOG.md
152
+ changelog_uri: https://github.com/booqable/booqable.rb/blob/main/CHANGELOG.md
153
153
  documentation_uri: https://developers.booqable.com/
154
154
  bug_tracker_uri: https://github.com/booqable/booqable.rb/issues
155
- post_install_message:
156
155
  rdoc_options: []
157
156
  require_paths:
158
157
  - lib
@@ -167,8 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
166
  - !ruby/object:Gem::Version
168
167
  version: '0'
169
168
  requirements: []
170
- rubygems_version: 3.5.22
171
- signing_key:
169
+ rubygems_version: 4.0.10
172
170
  specification_version: 4
173
171
  summary: Official Booqable API client for Ruby.
174
172
  test_files: []