Skip to content

Writing a Custom Adapter

Evan Gray edited this page Jul 5, 2026 · 1 revision

Writing a Custom Adapter

Planter uses adapters to keep seeding logic separate from the persistence layer. Active Record is the default adapter, but custom adapters can seed through another ORM, raw SQL, an API client, or any other storage system.

A custom adapter is a plain Ruby object. It only needs to implement the adapter methods Planter calls.

Generating an Adapter Stub

You can generate a starting point with:

rails generate planter:adapter sequel

This creates an adapter file and updates config/initializers/planter.rb to use it.

Adapter Interface

Custom adapters must implement:

class MyAdapter
  def create_record(context:, lookup_attributes:, create_attributes:)
  end

  def parent_ids(context:)
  end

  def foreign_key(context:)
  end

  def table_columns(context:)
  end

  def table_names
  end
end

Seed Context

Each adapter method receives a Planter::SeedContext object. This is a small parameter object with normalized seeder configuration.

Available readers:

context.table_name
context.seed_method
context.csv_name
context.parent
context.number_of_records
context.unique_columns
context.erb_trim_mode

For example, a UsersSeeder usually has:

context.table_name # => "users"

If a seeder name does not match the table, users can configure it:

class PeopleSeeder < Planter::Seeder
  seeding_method :csv, table: :users
end

Required Methods

create_record

def create_record(context:, lookup_attributes:, create_attributes:)
end

Create a record in context.table_name unless one already exists.

lookup_attributes are used to find an existing record. create_attributes are used only when creating a new record.

parent_ids

def parent_ids(context:)
end

Return parent record IDs for parent-based seeding. This is only used when the seeder has a parent: option.

The meaning of context.parent is adapter-defined. In the Active Record adapter, it is a belongs_to association name.

foreign_key

def foreign_key(context:)
end

Return the field Planter should use to attach a parent ID to each seeded record.

For example, if context.parent == :user, this might return :user_id.

table_columns

def table_columns(context:)
end

Return native column or field names for context.table_name.

Planter uses this to avoid looking up records by non-column CSV headers. Non-column lookup attributes are moved into create attributes before create_record is called.

table_names

def table_names
end

Return table or collection names that can have seeders generated by:

rails generate planter:seeder ALL

Minimal Example

class SqlAdapter
  def initialize(connection)
    @connection = connection
  end

  def create_record(context:, lookup_attributes:, create_attributes:)
    existing = find_record(context.table_name, lookup_attributes)
    return existing if existing

    insert_record(context.table_name, lookup_attributes.merge(create_attributes))
  end

  def parent_ids(context:)
    @connection.select_values("SELECT id FROM #{context.parent}")
  end

  def foreign_key(context:)
    :"#{context.parent.to_s.singularize}_id"
  end

  def table_columns(context:)
    @connection.columns(context.table_name).map(&:name)
  end

  def table_names
    @connection.tables
  end

  private

  attr_reader :connection
end

Configuring Planter

Planter.configure do |config|
  config.adapter = SqlAdapter.new(MyConnection)
  config.seeders = %i[
    users
  ]
end

Notes

Adapters should not need to know about Planter::Seeder subclasses directly. They should rely on context.table_name and the attributes passed into each method.

The Active Record adapter maps context.table_name to Rails models internally, but custom adapters do not need to use models at all.

Clone this wiki locally