class Airrecord::Table

Attributes

api_key[W]
base_key[RW]
table_name[RW]
created_at[R]
fields[R]
id[R]
updated_keys[R]

Public Class Methods

all(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil, max_records: nil, page_size: nil)
Alias for: records
api_key() click to toggle source
# File lib/airrecord/table.rb, line 14
def api_key
  defined?(@api_key) ? @api_key : Airrecord.api_key
end
belongs_to(method_name, options) click to toggle source
# File lib/airrecord/table.rb, line 34
def belongs_to(method_name, options)
  has_many(method_name, options.merge(single: true))
end
Also aliased as: has_one
client() click to toggle source
# File lib/airrecord/table.rb, line 9
def client
  @@clients ||= {}
  @@clients[api_key] ||= Client.new(api_key)
end
create(fields, options = {}) click to toggle source
# File lib/airrecord/table.rb, line 59
def create(fields, options = {})
  new(fields).tap { |record| record.save(options) }
end
find(id) click to toggle source
# File lib/airrecord/table.rb, line 40
def find(id)
  response = client.connection.get("/v0/#{base_key}/#{client.escape(table_name)}/#{id}")
  parsed_response = client.parse(response.body)

  if response.success?
    self.new(parsed_response["fields"], id: id, created_at: parsed_response["createdTime"])
  else
    client.handle_error(response.status, parsed_response)
  end
end
find_many(ids) click to toggle source
# File lib/airrecord/table.rb, line 51
def find_many(ids)
  return [] if ids.empty?

  or_args = ids.map { |id| "RECORD_ID() = '#{id}'"}.join(',')
  formula = "OR(#{or_args})"
  records(filter: formula).sort_by { |record| or_args.index(record.id) }
end
has_many(method_name, options) click to toggle source
# File lib/airrecord/table.rb, line 18
def has_many(method_name, options)
  define_method(method_name.to_sym) do
    # Get association ids in reverse order, because Airtable's UI and API
    # sort associations in opposite directions. We want to match the UI.
    ids = (self[options.fetch(:column)] || []).reverse
    table = Kernel.const_get(options.fetch(:class))
    return table.find_many(ids) unless options[:single]

    (id = ids.first) ? table.find(id) : nil
  end

  define_method("#{method_name}=".to_sym) do |value|
    self[options.fetch(:column)] = Array(value).map(&:id).reverse
  end
end
has_one(method_name, options)
Alias for: belongs_to
new(*one, **two) click to toggle source
# File lib/airrecord/table.rb, line 115
def initialize(*one, **two)
  @id = one.first && two.delete(:id)
  self.created_at = one.first && two.delete(:created_at)
  self.fields = one.first || two
end
records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil, max_records: nil, page_size: nil) click to toggle source
# File lib/airrecord/table.rb, line 63
def records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil, max_records: nil, page_size: nil)
  options = {}
  options[:filterByFormula] = filter if filter

  if sort
    options[:sort] = sort.map { |field, direction|
      { field: field.to_s, direction: direction }
    }
  end

  options[:view] = view if view
  options[:offset] = offset if offset
  options[:fields] = fields if fields
  options[:maxRecords] = max_records if max_records
  options[:pageSize] = page_size if page_size

  path = "/v0/#{base_key}/#{client.escape(table_name)}"
  response = client.connection.get(path, options)
  parsed_response = client.parse(response.body)

  if response.success?
    records = parsed_response["records"]
    records = records.map { |record|
      self.new(record["fields"], id: record["id"], created_at: record["createdTime"])
    }

    if paginate && parsed_response["offset"]
      records.concat(records(
        filter: filter,
        sort: sort,
        view: view,
        paginate: paginate,
        fields: fields,
        offset: parsed_response["offset"],
        max_records: max_records,
        page_size: page_size,
      ))
    end

    records
  else
    client.handle_error(response.status, parsed_response)
  end
end
Also aliased as: all

Public Instance Methods

==(other) click to toggle source
# File lib/airrecord/table.rb, line 204
def ==(other)
  self.class == other.class &&
    serializable_fields == other.serializable_fields
end
Also aliased as: eql?
[](key) click to toggle source
# File lib/airrecord/table.rb, line 132
def [](key)
  validate_key(key)
  fields[key]
end
[]=(key, value) click to toggle source
# File lib/airrecord/table.rb, line 137
def []=(key, value)
  validate_key(key)
  return if fields[key] == value # no-op

  @updated_keys << key
  fields[key] = value
end
create(options = {}) click to toggle source
# File lib/airrecord/table.rb, line 145
def create(options = {})
  raise Error, "Record already exists (record has an id)" unless new_record?

  body = {
    fields: serializable_fields,
    **options
  }.to_json

  response = client.connection.post("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}", body, { 'Content-Type' => 'application/json' })
  parsed_response = client.parse(response.body)

  if response.success?
    @id = parsed_response["id"]
    self.created_at = parsed_response["createdTime"]
    self.fields = parsed_response["fields"]
  else
    client.handle_error(response.status, parsed_response)
  end
end
destroy() click to toggle source
# File lib/airrecord/table.rb, line 187
def destroy
  raise Error, "Unable to destroy new record" if new_record?

  response = client.connection.delete("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}")
  parsed_response = client.parse(response.body)

  if response.success?
    true
  else
    client.handle_error(response.status, parsed_response)
  end
end
eql?(other)
Alias for: ==
hash() click to toggle source
# File lib/airrecord/table.rb, line 210
def hash
  serializable_fields.hash
end
new_record?() click to toggle source
# File lib/airrecord/table.rb, line 128
def new_record?
  !id
end
save(options = {}) click to toggle source
# File lib/airrecord/table.rb, line 165
def save(options = {})
  return create(options) if new_record?
  return true if @updated_keys.empty?

  # To avoid trying to update computed fields we *always* use PATCH
  body = {
    fields: Hash[@updated_keys.map { |key|
      [key, fields[key]]
    }],
    **options
  }.to_json

  response = client.connection.patch("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}", body, { 'Content-Type' => 'application/json' })
  parsed_response = client.parse(response.body)

  if response.success?
    self.fields = parsed_response["fields"]
  else
    client.handle_error(response.status, parsed_response)
  end
end
serializable_fields() click to toggle source
# File lib/airrecord/table.rb, line 200
def serializable_fields
  fields
end

Protected Instance Methods

client() click to toggle source
# File lib/airrecord/table.rb, line 227
def client
  self.class.client
end
created_at=(created_at) click to toggle source
# File lib/airrecord/table.rb, line 221
def created_at=(created_at)
  return unless created_at

  @created_at = Time.parse(created_at)
end
fields=(fields) click to toggle source
# File lib/airrecord/table.rb, line 216
def fields=(fields)
  @updated_keys = []
  @fields = fields
end
validate_key(key) click to toggle source
# File lib/airrecord/table.rb, line 231
def validate_key(key)
  return true unless key.is_a?(Symbol)

  raise(Error, [
    "Airrecord 1.0 dropped support for Symbols as field names.",
    "Please use the raw field name, a String, instead.",
    "You might try: record['#{key.to_s.tr('_', ' ')}']"
  ].join("\n"))
end