airtable-orm 0.2.0 → 0.2.1

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: 970cedc7b68168b8add9fa80225982de0e902758bfb0dbfb7b3d04184c292871
4
- data.tar.gz: 9e45c6326f0daa9b07eee7f6aa81601217437f40022ca05a34c85d0fd17714f1
3
+ metadata.gz: dc3056cbf98026ea8a89b01d99c632ad01da304d85841544552b44565f897793
4
+ data.tar.gz: cf839f12c5812c4834604dc46c716fdb671c47110d94829fa863d82f5d56e489
5
5
  SHA512:
6
- metadata.gz: 3812676e76a07ae378a285030f30c8175fc9bb23670d626d2103e44611894785f962554d3cd9737c9c88f2ba6aaf9b8d80c8d5ee6e9401ca0a5da6b413ba7dcd
7
- data.tar.gz: 0bc1ea55739db84a90a92b8389d3df1c37121e496838388ec863621e3e8fb13adc6cbb54ee19e9f09f2c0eeebf8b20dd70521a6e827b6b6d384dca4230f9f35d
6
+ metadata.gz: dfcd829b2a0df28d151770f30c0f51e98680bc6ab0c6f8fd5f0ff9fd2fd6ffefdef4f3c30fbe7dbcb3f3afa08689d6dffb6fc234d28a4403c3ec56f004f0abfa
7
+ data.tar.gz: 6c31d239c24e6c86e6303cc5eb7fdd34a5916c914c27999625c6b0c945178f9d7def6386855ba77cc13493054d14dc8abf1f087ca27474b8ff6a21f1e34f2e57
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2026-07-15
4
+
5
+ ### Fixed
6
+
7
+ - The schema cache stores the full base schema instead of only the tables configured at fetch
8
+ time — a table added to `config.tables` after the cache warmed up (24 h expiry, shared store
9
+ in Rails hosts) now resolves immediately instead of raising `ConfigurationError` until expiry.
10
+
11
+ ### Changed
12
+
13
+ - Record payloads are parsed by string keys directly instead of deep-converting every record
14
+ with `with_indifferent_access` — one avoided deep copy per record on `where`/`all`/`find`,
15
+ two per record on batch updates. The (undocumented) `instantiate_from_api_response` and
16
+ `apply_response_fields` now expect string-keyed parsed payloads; `createdTime` parsing lives
17
+ in the single `Persistence.parse_created_time` helper.
18
+
3
19
  ## [0.2.0] - 2026-07-14
4
20
 
5
21
  Hardening release from a deep code review of the whole gem. Minor (not patch) because a few
@@ -111,21 +111,28 @@ module Airtable
111
111
  record
112
112
  end
113
113
 
114
- # Instantiate a record from API response
114
+ # Instantiate a record from a parsed API payload. Expects string keys (the shape the
115
+ # JSON middleware produces) — this runs once per record on every where/all/find, so no
116
+ # per-record deep conversion here.
115
117
  def instantiate_from_api_response(response)
116
- data = response.with_indifferent_access
117
- symbol_attrs = fields_to_symbol_attributes(data[:fields])
118
-
119
- record = new(**symbol_attrs)
118
+ record = new(**fields_to_symbol_attributes(response["fields"]))
120
119
  record.send(:assign_persistence_state,
121
- id: data[:id],
122
- created_at: data[:createdTime] ? Time.iso8601(data[:createdTime].to_s) : nil,
120
+ id: response["id"],
121
+ created_at: parse_created_time(response),
123
122
  persisted: true)
124
123
  record.clear_changes_information
125
124
 
126
125
  record
127
126
  end
128
127
 
128
+ # @api private — createdTime is ISO-8601 UTC, parsed with Time.iso8601 (Time.zone
129
+ # doesn't exist outside Rails); nil-guarded. The single parse site for both
130
+ # instantiate_from_api_response and #apply_response_fields.
131
+ def parse_created_time(data)
132
+ created_time = data["createdTime"]
133
+ created_time ? Time.iso8601(created_time.to_s) : nil
134
+ end
135
+
129
136
  # Convert API field IDs to symbol attributes.
130
137
  # field_mapping already contains only declared attributes.
131
138
  def fields_to_symbol_attributes(fields)
@@ -148,9 +155,8 @@ module Airtable
148
155
  parsed = response.body
149
156
 
150
157
  if response.success?
151
- parsed = parsed.with_indifferent_access if parsed.is_a?(Hash)
152
- response_records = parsed[:records] || []
153
- response_by_id = response_records.index_by { |r| r[:id] }
158
+ response_records = (parsed.is_a?(Hash) && parsed["records"]) || []
159
+ response_by_id = response_records.index_by { |r| r["id"] }
154
160
 
155
161
  batch.each do |record|
156
162
  record_data = response_by_id[record.id]
@@ -163,7 +169,7 @@ module Airtable
163
169
  end
164
170
  else
165
171
  batch.each { |record| result.failed << record }
166
- error_message = parsed.is_a?(Hash) ? parsed.with_indifferent_access.dig(:error, :message) : parsed
172
+ error_message = parsed.is_a?(Hash) ? parsed.dig("error", "message") : parsed
167
173
  ORM.config.logger.error(
168
174
  "Airtable batch update failed: HTTP #{response.status}: #{error_message.to_s.truncate(200)}"
169
175
  )
@@ -312,13 +318,12 @@ module Airtable
312
318
  def apply_response_fields(data)
313
319
  @previously_new_record = new_record?
314
320
 
315
- data = data.with_indifferent_access
316
- symbol_attrs = self.class.fields_to_symbol_attributes(data[:fields])
321
+ symbol_attrs = self.class.fields_to_symbol_attributes(data["fields"])
317
322
  symbol_attrs.each { |key, value| write_raw_attribute(key, value) }
318
323
 
319
324
  assign_persistence_state(
320
- id: data[:id],
321
- created_at: data[:createdTime] ? Time.iso8601(data[:createdTime].to_s) : nil,
325
+ id: data["id"],
326
+ created_at: self.class.parse_created_time(data),
322
327
  persisted: true
323
328
  )
324
329
  changes_applied
@@ -19,10 +19,6 @@ module Airtable
19
19
 
20
20
  private
21
21
 
22
- def required_table_ids
23
- ORM.config.table_ids
24
- end
25
-
26
22
  def fetch_from_api(base_id)
27
23
  response = client.connection.get("/v0/meta/bases/#{base_id}/tables")
28
24
  parsed_response = response.body
@@ -34,12 +30,13 @@ module Airtable
34
30
  end
35
31
  end
36
32
 
33
+ # Index the FULL base schema — no filtering by the tables configured at fetch time.
34
+ # The cached payload outlives the config (24h, shared store in Rails hosts), so a
35
+ # fetch-time filter would hide tables added to config after the cache warmed up.
36
+ # Reads are keyed lookups (fetch(base_id)[table_id]), so no read-time filter is needed.
37
37
  def indexed_schema(parsed_response)
38
38
  tables = parsed_response.deep_symbolize_keys[:tables]
39
39
 
40
- # Filter only required tables
41
- tables.select! { |table| required_table_ids.include?(table[:id]) }
42
-
43
40
  # Transform fields into hash indexed by field id
44
41
  tables.each do |table|
45
42
  table[:fields] = table[:fields].index_by { |field| field[:id] }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Airtable
4
4
  module ORM
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.1"
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.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Sklenar