airtable-orm 0.2.1 → 0.2.3

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: dc3056cbf98026ea8a89b01d99c632ad01da304d85841544552b44565f897793
4
- data.tar.gz: cf839f12c5812c4834604dc46c716fdb671c47110d94829fa863d82f5d56e489
3
+ metadata.gz: 628a8ae1f053bb9b1f598608785b9a09062797d152e234d7cd2e2b1a4fefb982
4
+ data.tar.gz: 30813eda1cfb49e6e74418be674490526697264c3d67b6d6f6edd0d3f194c810
5
5
  SHA512:
6
- metadata.gz: dfcd829b2a0df28d151770f30c0f51e98680bc6ab0c6f8fd5f0ff9fd2fd6ffefdef4f3c30fbe7dbcb3f3afa08689d6dffb6fc234d28a4403c3ec56f004f0abfa
7
- data.tar.gz: 6c31d239c24e6c86e6303cc5eb7fdd34a5916c914c27999625c6b0c945178f9d7def6386855ba77cc13493054d14dc8abf1f087ca27474b8ff6a21f1e34f2e57
6
+ metadata.gz: ba0c3abab62e8f8931c6f7ca3f84fe35b343e5341df5ea9652f641c1b8b6d146241be1cd3b554d84232b2d44a1651c475aee2f5abb8580a4b75b2faf9c971806
7
+ data.tar.gz: 3394db605f8b6910d0e41a40c9087ba74daa36b3b5b60d5db65487fa870d4dbc4e0c56c4aed43d5476473e327fc7cf6bd9b6856078578a6809d78b9bc6a5fc49
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.3] - 2026-09-24
4
+
5
+ ### Fixed
6
+
7
+ - Require `faraday >= 2.14.4` (was `~> 2.9`). Older Faraday releases can't parse responses
8
+ once the host resolves json 3.0 (released 2026-09-09): every request raises
9
+ `Faraday::ParsingError: wrong number of arguments`. This affects every earlier airtable-orm
10
+ release, which all allowed any Faraday 2.9+.
11
+
12
+ ## [0.2.2] - 2026-09-24
13
+
14
+ ### Fixed
15
+
16
+ - `find` raises `RecordNotFound` for a deleted/nonexistent record ID. Airtable answers such a
17
+ GET with `403 INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND`, not 404, so it used to surface as a
18
+ plain `ApiError`. Because a revoked token or lost table permission returns the same 403, `find`
19
+ now disambiguates with one probe of the table (a one-record, no-fields list request): readable
20
+ → `RecordNotFound`, otherwise the original `ApiError` is re-raised; a `ConnectionError` during
21
+ the probe propagates. The 404 mapping is unchanged. `reload` and `belongs_to` (which reads a
22
+ stale link as `nil`) benefit accordingly.
23
+ - `ApiError` is raised (instead of a `TypeError`) for Airtable's bare string error body such as
24
+ `{"error": "NOT_FOUND"}`, which previously broke the 404 → `RecordNotFound` mapping in `find`.
25
+
3
26
  ## [0.2.1] - 2026-07-15
4
27
 
5
28
  ### Fixed
@@ -66,11 +66,13 @@ module Airtable
66
66
  ERB::Util.url_encode(string)
67
67
  end
68
68
 
69
- # Parse an Airtable API error response and raise an ApiError.
69
+ # Parse an Airtable API error response and raise an ApiError. Airtable sends either
70
+ # {"error" => {"type", "message"}} or a bare {"error" => "NOT_FOUND"} (e.g. on 404).
70
71
  def self.raise_api_error(status, error)
71
- type = (error.is_a?(Hash) && error.dig("error", "type")) || "Communication error"
72
+ detail = error.is_a?(Hash) ? error["error"] : nil
73
+ type = (detail.is_a?(Hash) ? detail["type"] : detail) || "Communication error"
72
74
  msg = case error
73
- when Hash then error.dig("error", "message")
75
+ when Hash then detail.is_a?(Hash) ? detail["message"] : nil
74
76
  when String then error
75
77
  when NilClass then "invalid or empty response body (not valid JSON)"
76
78
  else error.inspect
@@ -74,7 +74,9 @@ module Airtable
74
74
  Airtable::ORM::Http::Client.raise_api_error(response.status, parsed_response)
75
75
  end
76
76
  rescue Airtable::ORM::ApiError => e
77
- raise Airtable::ORM::RecordNotFound, "Couldn't find record with id=#{id}" if e.status == 404
77
+ if e.status == 404 || (missing_record_forbidden?(e) && table_readable?)
78
+ raise Airtable::ORM::RecordNotFound, "Couldn't find record with id=#{id}"
79
+ end
78
80
 
79
81
  raise
80
82
  end
@@ -145,6 +147,22 @@ module Airtable
145
147
 
146
148
  private
147
149
 
150
+ # Airtable answers a GET for a nonexistent (e.g. deleted) record ID with this 403, not
151
+ # 404 — the same response a token without access to the table/base gets.
152
+ def missing_record_forbidden?(error)
153
+ error.status == 403 && error.response.is_a?(Hash) &&
154
+ error.response.dig("error", "type") == "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND"
155
+ end
156
+
157
+ # Disambiguates that 403 with one cheap list request (one record, no fields): readable
158
+ # table → the record is gone. Never map the 403 blindly — a revoked token or lost
159
+ # permission would then look like "every record deleted" to the host. A ConnectionError
160
+ # still propagates (retryable, not evidence either way).
161
+ def table_readable?
162
+ response = client.connection.post(api_path("listRecords"), { maxRecords: 1, fields: [] })
163
+ response.success?
164
+ end
165
+
148
166
  # Send a single batch PATCH request to Airtable.
149
167
  def send_batch_update(batch, result)
150
168
  body = {
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Airtable
4
4
  module ORM
5
- VERSION = "0.2.1"
5
+ VERSION = "0.2.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airtable-orm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Sklenar
@@ -35,14 +35,20 @@ dependencies:
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: '2.9'
38
+ version: '2.14'
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 2.14.4
39
42
  type: :runtime
40
43
  prerelease: false
41
44
  version_requirements: !ruby/object:Gem::Requirement
42
45
  requirements:
43
46
  - - "~>"
44
47
  - !ruby/object:Gem::Version
45
- version: '2.9'
48
+ version: '2.14'
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 2.14.4
46
52
  - !ruby/object:Gem::Dependency
47
53
  name: faraday-net_http_persistent
48
54
  requirement: !ruby/object:Gem::Requirement