Module: Airtable::ORM::Persistence

Extended by:
ActiveSupport::Concern
Included in:
Base
Defined in:
lib/airtable/orm/persistence.rb

Constant Summary collapse

BATCH_SIZE =
10
MAX_FIND_MANY_IDS =
500
RECORD_ID_FORMAT =

Airtable record IDs: rec + alphanumeric characters. Validated before any ID reaches a URL path or formula interpolation (injection guard).

/\Arec[a-zA-Z0-9_-]+\z/

Instance Method Summary collapse

Instance Method Details

#apply_response_fields(data) ⇒ Object

Apply response fields from a create, update, or batch operation. Uses changes_applied (not clear_changes_information) so after_save callbacks can inspect previous_changes / saved_change_to_*? methods.



318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/airtable/orm/persistence.rb', line 318

def apply_response_fields(data)
  @previously_new_record = new_record?

  symbol_attrs = self.class.fields_to_symbol_attributes(data["fields"])
  symbol_attrs.each { |key, value| write_raw_attribute(key, value) }

  assign_persistence_state(
    id: data["id"],
    created_at: self.class.parse_created_time(data),
    persisted: true
  )
  changes_applied
end

#assign_persistence_state(id:, created_at:, persisted:) ⇒ Object

Assign persistence metadata (id, created_at, persisted flag)



309
310
311
312
313
# File 'lib/airtable/orm/persistence.rb', line 309

def assign_persistence_state(id:, created_at:, persisted:)
  @id = id
  @created_at = created_at
  @persisted = persisted
end

#clientObject



332
333
334
# File 'lib/airtable/orm/persistence.rb', line 332

def client
  self.class.client
end

#create_recordObject

Create a new record via API



348
349
350
351
352
353
354
355
# File 'lib/airtable/orm/persistence.rb', line 348

def create_record
  run_callbacks :create do
    body = { fields: fields_for_create }
    response = client.connection.post(self.class.api_path, body)
    handle_persistence_response(response)
    true
  end
end

#destroyObject

Destroy the record



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/airtable/orm/persistence.rb', line 273

def destroy
  raise Airtable::ORM::RecordNotDestroyed, "Cannot destroy a new record" if new_record?

  run_callbacks :destroy do
    response = client.connection.delete(self.class.api_path(id))
    parsed_response = response.body

    if response.success?
      @persisted = false
      @destroyed = true
      freeze
      self
    else
      Airtable::ORM::Http::Client.raise_api_error(response.status, parsed_response)
    end
  end
end

#destroyed?Boolean

Check if this record has been destroyed

Returns:

  • (Boolean)


224
225
226
# File 'lib/airtable/orm/persistence.rb', line 224

def destroyed?
  @destroyed == true
end

#handle_persistence_response(response) ⇒ Object

Handle API response from create/update operations



337
338
339
340
341
342
343
344
345
# File 'lib/airtable/orm/persistence.rb', line 337

def handle_persistence_response(response)
  parsed = response.body

  if response.success?
    apply_response_fields(parsed)
  else
    Airtable::ORM::Http::Client.raise_api_error(response.status, parsed)
  end
end

#initialize(**attributes) ⇒ Object

Initialize a record with symbol attributes Usage: new(email: "test@example.com", first_name: "John")



198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/airtable/orm/persistence.rb', line 198

def initialize(**attributes)
  # Initialize as a new record (not persisted, not destroyed)
  @id = nil
  @created_at = nil
  @persisted = false
  @destroyed = false
  @previously_new_record = false

  # Initialize ActiveModel::Attributes
  super()

  # Set attributes using ActiveModel's assign_attributes
  assign_attributes(attributes) if attributes.present?
end

#new_record?Boolean

Check if this is a new record (not yet saved)

Returns:

  • (Boolean)


214
215
216
# File 'lib/airtable/orm/persistence.rb', line 214

def new_record?
  !@persisted && !@destroyed
end

#persisted?Boolean

Check if this record has been saved

Returns:

  • (Boolean)


219
220
221
# File 'lib/airtable/orm/persistence.rb', line 219

def persisted?
  @persisted == true
end

#previously_new_record?Boolean

Check if this record was new before the last save

Returns:

  • (Boolean)


229
230
231
# File 'lib/airtable/orm/persistence.rb', line 229

def previously_new_record?
  @previously_new_record == true
end

#reloadObject

Reload the record from the API



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/airtable/orm/persistence.rb', line 292

def reload
  raise Airtable::ORM::RecordNotPersisted, "Cannot reload a new record" if new_record?

  fresh_record = self.class.find(id)

  # Copy attributes from fresh record using raw attribute access
  self.class.attribute_types.each_key do |name|
    write_raw_attribute(name.to_sym, fresh_record.read_raw_attribute(name.to_sym))
  end

  @created_at = fresh_record.created_at
  clear_changes_information

  self
end

#save(validate: true) ⇒ Object

Save the record (create or update)



234
235
236
237
238
239
240
241
242
243
244
# File 'lib/airtable/orm/persistence.rb', line 234

def save(validate: true)
  return false if validate && invalid?

  result = run_callbacks(:save) do
    new_record? ? create_record : update_record
  end

  result != false
rescue Airtable::ORM::ApiError
  false
end

#save!(validate: true) ⇒ Object

Save the record, raising an error if it fails. Unlike save, lets ApiError propagate with full error details.



248
249
250
251
252
253
254
255
256
257
258
# File 'lib/airtable/orm/persistence.rb', line 248

def save!(validate: true)
  raise Airtable::ORM::RecordInvalid, self if validate && invalid?

  result = run_callbacks(:save) do
    new_record? ? create_record : update_record
  end

  raise Airtable::ORM::RecordNotSaved, "Failed to save the record" if result == false

  true
end

#update(attributes) ⇒ Object

Update attributes and save



261
262
263
264
# File 'lib/airtable/orm/persistence.rb', line 261

def update(attributes)
  assign_attributes(attributes)
  save
end

#update!(attributes) ⇒ Object

Update attributes and save, raising an error if it fails



267
268
269
270
# File 'lib/airtable/orm/persistence.rb', line 267

def update!(attributes)
  assign_attributes(attributes)
  save!
end

#update_recordObject

Update only changed fields via PATCH. Fields changed TO nil are sent (to clear them in Airtable); unchanged nil fields are not sent.



359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/airtable/orm/persistence.rb', line 359

def update_record
  unless changed?
    clear_changes_information
    return true
  end

  run_callbacks :update do
    body = { fields: fields_for_update }
    response = client.connection.patch(self.class.api_path(id), body)
    handle_persistence_response(response)
    true
  end
end