Airrecord

repository·master·Indexed 43 days ago

https://github.com/sirupsen/airrecord

A Ruby library providing a database-like API for Airtable. It allows mapping Airtable tables to Ruby classes via Airrecord::Table, supporting associations (has_many, belongs_to), custom scopes, and an ad-hoc querying interface. Features include automatic pagination, rate limiting, and support for both API keys and Personal Access Tokens (PAT).

Tokens
4.1K
Snippets
21
Records
22
Agent score
56%

What's inside Airrecord

  1. Manage table associations

    master

    Airrecord supports has_many, has_one, and belongs_to associations between Airrecord::Table classes, provided they reside in the same Airtable base.

    Retrieving Associated Records

    • Calling the association method on a record returns an array of Airrecord instances (for has_many) or a single instance (for has_one).
    • Accessing the raw column via [] returns the raw Airtable field (usually an array of IDs).

    Creating Associations

    You can associate records by assigning the instance of the parent record to the child record's association method before calling #create (or #save).

    # Setup
    class Tea < Airrecord::Table
      has_many :brews, class: "Brew", column: "Brews"
    end
    
    class Brew < Airrecord::Table
      belongs_to :tea, class: "Tea", column: "Tea"
    end
    
    # Usage: Creating an association
    tea = Tea.find("rec123")
    brew = Brew.new("Temperature" => "80")
    brew.tea = tea
    brew.create
  2. Authenticate with Airrecord

    master

    Airrecord supports two authentication methods for the Airtable API: API keys and Personal Access Tokens (PAT).

    API Key

    You can set a global API key for all tables using Airrecord.api_key. If you have an application token, it must be set on the specific table definition using self.base_key. You can also override the API key on a per-table basis.

    Personal Access Token (PAT)

    To use a PAT, create one in your Airtable settings with the necessary scopes and base access, then provide it globally via Airrecord.api_key.

    # Using an API Key
    Airrecord.api_key = "your api key"
    
    # Using a Personal Access Token
    Airrecord.api_key = "your PAT"
  3. Use the Airrecord database-like API with class definitions

    master

    Airrecord allows you to map Airtable tables to Ruby classes by inheriting from Airrecord::Table. This approach enforces a database-like API, allowing you to define associations, custom scopes (using all), and instance methods.

    Key Configuration

    • Airrecord.api_key = "...": Sets the global API key.
    • self.base_key = "...": Sets the Airtable Base ID within the class.
    • self.table_name = "...": Sets the Airtable table name within the class.

    Associations

    • has_many :association_name, class: "ClassName", column: "ColumnName"
    • belongs_to :association_name, class: "ClassName", column: "ColumnName"

    Querying and Attributes

    • all(filter: "...", sort: { ... }): Retrieves records using Airtable filter formulas or sort parameters.
    • record["ColumnName"]: Accesses an attribute value by its column name.
    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
    end
    
    class Brew < Airrecord::Table
      self.base_key = "app1"
      self.table_name = "Brews"
    
      belongs_to :tea, class: "Tea", column: "Tea"
    end
    
    teas = Tea.all
    tea = teas.first
    puts tea["Country"] # access attribute
    puts tea.brews      # access associated brews
  4. Configure Retries and Circuit Breakers for Production

    master

    Airrecord uses the Faraday gem for HTTP requests. For production stability, you can add middleware to the Airrecord::Table.client.connection to handle retries and circuit breaking.

    Retries

    You can configure the retry middleware to handle transient failures. It is recommended to be explicit about exceptions.

    Circuit Breakers

    To prevent hanging processes when Airtable is unavailable, use a circuit breaker middleware (like faraday_middleware-circuit_breaker). This allows the application to 'fail fast' after a certain threshold of failures is reached.

    # Configure Retries
    Airrecord::Table.client.connection.request :retry,
      max: 5, interval: 1, interval_randomness: 2, backoff_factor: 2, exceptions: [StandardError]
    
    # Configure Circuit Breaker
    Airrecord::Table.client.connection.request :circuit_breaker,
      timeout: 20, threshold: 5
  5. Configure Rate Limiting

    master

    Airtable throttles requests if you exceed 5 requests per second. Airrecord::Client includes built-in support for rate limiting to prevent this.

    If Airrecord.throttle? is enabled, the client uses a Faraday middleware (airrecord_rate_limiter) to ensure requests do not exceed the AIRTABLE_RPS_LIMIT (5 requests per second).

  6. Define an Airtable model using Table.table

    master

    To interact with a specific Airtable table, use the Airrecord.Table.table method to dynamically create a model class. This class inherits from Airrecord::Table and is configured with your api_key, base_key, and the specific table_name.

    # Example model definition
    Tea = Airrecord::Table.table('YOUR_API_KEY', 'YOUR_BASE_ID', 'Teas')
    
    # Usage
    first_tea = Tea.find('recXXXXXXXXXXXX')
    puts first_tea['Name']
    Tea = Airrecord::Table.table('YOUR_API_KEY', 'YOUR_BASE_ID', 'Teas')
  7. Use the Airrecord ad-hoc API for quick querying

    master

    If you do not want to define formal classes, you can use the Airrecord.table method to create a table object on the fly. This is useful for quick scripts or ad-hoc querying.

    Airrecord.table(api_key, base_key, table_name) returns a class-like object that supports standard querying methods like .all and .find.

    Tea = Airrecord.table("api_key", "app_key", "Teas")
    
    Tea.all.each do |record|
      puts "#{record.id}: #{record["Name"]}"
    end
    
    Tea.find("rec3838")
  8. Define an Airrecord::Table

    master

    Airrecord uses class definitions inheriting from Airrecord::Table to map Ruby classes to Airtable tables, similar to how ActiveRecord::Base works. Each class must specify its base_key (the Airtable base ID) and table_name (the table name or ID).

    You can define custom methods within the class to manipulate or format data retrieved from the record columns.

    Airrecord.api_key = "your api key"
    
    class Tea < Airrecord::Table
      self.base_key = "app1"
      self.table_name = "Teas"
    
      def location
        [self["Village"], self["Country"], self["Region"]].compact.join(", ")
      end
    end
  9. Read and find specific records

    master

    To retrieve a single record by its Airtable ID, use #find.

    To retrieve a specific set of records when you already know their IDs, use #find_many.

    # Find a single record
    tea = Tea.find("someid")
    
    # Find multiple specific records
    teas = Tea.find_many(["someid", "anotherid"])
  10. Create and update records

    master

    Creating

    Use Table.create to instantiate and save a record in one step. Alternatively, use Table.new to manipulate the object before calling #save.

    To pass optional Airtable request parameters (like typecast), pass them as a second hash argument to create or save.

    Updating

    1. Instance method: Fetch a record, change its attributes via the []= syntax, and call #save.
    2. Class method: Use Table.update(id, attributes_hash) to update a record without fetching it first.

    Note: Column names must match the exact Airtable column names, or an error will be raised.

    # Create a record
    tea = Tea.create("Name" => "Feng Gang", "Type" => "Green")
    
    # Create with optional parameters
    tea = Tea.create({"Name" => "Feng Gang"}, {"typecast" => true})
    
    # Update via instance
    tea = Tea.find("someid")
    tea["Name"] = "New Name"
    tea.save
    
    # Update via class method
    Tea.update("someid", { "Name" => "New Name" })