Airrecord
Ruby wrapper for Airtable, your personal database
Install / Use
/learn @sirupsen/AirrecordREADME
Airrecord
Airrecord is an alternative Airtable Ruby libary to
airtable-ruby. Airrecord attempts
to enforce a more database-like API to
Airtable. However, there's also
an ad-hoc API available that
skips the class definitions!
You can add this line to your Gemfile to use Airrecord:
gem 'airrecord'
A quick example to give an idea of the API that Airrecord provides:
Airrecord.api_key = "key1"
class Tea < Airrecord::Table
self.base_key = "app1"
self.table_name = "Teas"
has_many :brews, class: "Brew", column: "Brews"
def self.chinese
all(filter: '{Country} = "China"')
end
def self.cheapest_and_best
all(sort: { "Rating" => "desc", "Price" => "asc" })
end
def location
[self["Village"], self["Country"], self["Region"]].compact.join(", ")
end
def green?
self["Type"] == "Green"
end
end
class Brew < Airrecord::Table
self.base_key = "app1"
self.table_name = "Brews"
belongs_to :tea, class: "Tea", column: "Tea"
def self.hot
all(filter: "{Temperature} > 90")
end
def done_brewing?
Time.parse(self["Created At"]) + self["Duration"] > Time.now
end
end
teas = Tea.all
tea = teas.first
tea["Country"] # access atribute
tea.location # instance methods
tea.brews # associated brews
A short-hand API for definitions and more ad-hoc querying is also available:
Tea = Airrecord.table("api_key", "app_key", "Teas")
Tea.all.each do |record|
puts "#{record.id}: #{record["Name"]}"
end
Tea.find("rec3838")
Documentation
Authentication
Based on Changelog There are two ways to authenticate with Airtable API:
- API key
- Personal Access Token
API key
To obtain your API client, navigate to the Airtable's API page, select your base and obtain your API key and application token.

You can provide a global API key with:
Airrecord.api_key = "your api key"
The app token has to be set on the definitions of the tables (see API below). You can override the API key per table.
Personal Access Token (PAT)
- To create a PAT, navigate to the Airtable's Personal Access Tokens page and create a new token.
- Give your token a unique name. This name will be visible in record revision history.
- Choose the scopes to grant to your token. This controls what API endpoints the token will be able to use. For more information, see API scopes.
- Click ‘add a base’ to grant the token access to a base or workspace.
- Click ‘create token’ to create the token. You will be shown the token’s value. This is the only time you will be able to see the token’s value, so be sure to copy it to a secure location.
You can provide a global PAT with:
Airrecord.api_key = "your PAT"
Table
The Airrecord API is centered around definitions of Airrecord::Table from
which the definitions of your tables inherit. This is analogous to
ActiveRecord::Base. For example, we may have a Base to track teas we have
tried.
Airrecord.api_key = "your api key" # see authentication section
class Tea < Airrecord::Table
self.base_key = "app1"
self.table_name = "Teas" # or the tblX id
def location
[self["Village"], self["Country"], self["Region"]].compact.join(", ")
end
end
This gives us a class that maps to records in a table. Class methods are available to fetch records on the table.
Reading a Single Record
Retrieve a single record via #find:
tea = Tea.find("someid")
Listing Records
Retrieval of multiple records is usually done through #all. To get all records
in a table:
Tea.all # array of Tea instances
You can use all options supported by the API (they are documented on the API page for your base). By default #all will traverse all pages, see below on how to control pagination.
To use filterbyFormula to filter returned records:
# Retrieve all teas from China
Tea.all(filter: '{Country} = "China"')
# Retrieve all teas created in the past week
Tea.all(filter: "DATETIME_DIFF(CREATED_TIME(), TODAY(), 'days') < 7")
# Retrieve all teas that don't have a country defined
Tea.all(filter: "{Country} = \"\"")
This filtering can, of course, also be done in Ruby directly after calling
#all without filter, however, it may be more efficient to let Airtable
filter if you have a lot of records.
You can use view to only fetch records from a specific view. This is less
ad-hoc than filterByFormula:
# Retrieve all teas in the green tea view
Tea.all(view: "Green")
# Retrieve all Japanese teas
Tea.all(view: "Japan")
The sort option can be used to sort results returned from the Airtable API.
# Sort teas by the Name column in ascending order
Tea.all(sort: { "Name" => "asc" })
# Sort teas by Type (green, black, oolong, ..) in descending order
Tea.all(sort: { "Type" => "desc" })
# Sort teas by price in descending order
Tea.all(sort: { "Price" => "desc" })
Note again that the key must be the full column name.
As mentioned above, by default Airrecord will return results from all pages.
This can be slow if you have 1000s of records. You may wish to use the view
and/or filter option to sort in the results early, instead of doing 10s of
calls. Airrecord will always fetch the maximum possible amount of records
(100). This means that fetching 1,000 records will take 10 (at least) roundtrips. You can disable pagination (which fetches the first page) by passing paginate: false. This is especially useful if you're looking to fetch a set of recent records from a view or formula in tandem with a sort:
# Only fetch the first page. Sorting is undefined.
Tea.all(paginate: false)
# Give me only the most recent teas
Tea.all(sort: { "Created At" => "desc" }, paginate: false)
When you know the IDs of the records you want, and you want them in an ad-hoc
order, use #find_many instead of #all:
teas = Tea.find_many(["someid", "anotherid", "yetanotherid"])
#=> [<Tea @id="someid">,<Tea @id="anotherid">, <Tea @id="yetanotherid">]
Creating
Creating a new record is done through Table.create.
tea = Tea.create("Name" => "Feng Gang", "Type" => "Green", "Country" => "China")
tea.id # id of the new record
tea["Name"] # "Feng Gang"
If you need to manipulate a record before saving it, you can use Table.new
instead of create, then call #save when you're ready.
tea = Tea.new("Type" => "Green", "Country" => "China")
tea["Name"] = "Feng Gang"
tea.save
Note that column names need to match the exact column names in Airtable, otherwise Airrecord will throw an error that no column matches it.
If you need to include optional request parameters, such as the typecast parameter, these can be passed to either Table.create or #save.
This is also supported when updating existing records with the #save method.
tea = Tea.create(
{"Name" => "Feng Gang", "Type" => "Green", "Country" => "China"},
{"typecast" => true},
)
# Or with the #save method:
tea = Tea.new({"Name" => "Feng Gang", "Type" => "Green"})
tea["Name"] = "Feng Gang"
tea.save("typecast" => true)
Earlier versions of airrecord provided methods for snake-cased column names and symbols, however this proved error-prone without a proper schema API from Airtable which has still not been released.
Updating
Updating can be done in two ways:
- With a record instance, change the attributes and persist to Airtable with
#save
tea = Tea.find("someid")
tea["Name"] = "Feng Gang Organic"
tea["Village"] = "Feng Gang"
tea.save # persist to Airtable
- With the
Table.updateclass method (to save an API find call)
Tea.update("someid", { "Name" => "Feng Gang Organic", "Village" => "Feng Gang" })
Airtable's API doesn't allow you to change attachment's filename. As a workaround you can delete the original attachment and upload a new one with the original URL and a new filename.
Deleting
An instantiated record can be deleted through #destroy:
tea = Tea.find("rec839")
tea.destroy # deletes record
File Uploads
Airtable's API requires you to have uploaded your file to an intermediary and providing the URL. Unfortunately, it does not allow uploading directly.
word = World.find("cantankerous")
word["Pronounciation"] = [{url: "https://s3.ca-central-1.amazonaws.com/word-pronunciations/cantankerous.mp3"}]
word.save
S3 is a good place to upload files for Airtable. Airrecord does not support this directly, but the snippet below may be helpful:
# Add this to your gemfile
# Full docs at https://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html
require 'aws-sdk-s3'
Aws.config.update(
credentials: Aws::Credentials.new(access_key, secret_key) # obtain from AWS
region: 'ca-central-1', # region
)
s3 = Aws::S3::Client.new
s3.put_object({
body: File.open("cantankerous.mp3"), # IO object
bucket: 'word-pronunciations',
key: 'cantankerous.mp3',
acl: "public-read",
})
Associations
Airrecord supports managing associations between tables by linking
Airrecord::Table classes. To continue with our tea example, we may have
another table in the base to track brews of a specific tea (temperature,
steeping time, rating, ..). A tea thus has many brews:
class Tea < Airrecord::Table
self.base_key = "app1"
self.table_name = "Teas"
has_many :brews, class: "Brew", column: "Brews"
has_one :teapot, class: "Teapot", column: "Teapot
