Sow 🌱

Plant your data, watch it grow.

Sow is an Elixir library for seeding databases with code-defined fixtures. Define your data as Elixir maps, and Sow handles planting (inserting), cultivating (updating), and pruning (deleting) records to keep your database in sync.

Features

Installation

def deps do
  [{:sow, "~> 0.1.0"}]
end
# config/config.exs (optional)
config :sow, repo: MyApp.Repo

Quick Start

defmodule MyApp.Seeds.Countries do
  use Sow, schema: MyApp.Country, keys: [:code]

  def records do
    [
      %{code: "NO", name: "Norway"},
      %{code: "SE", name: "Sweden"}
    ]
  end
end

# Sow your seeds
{:ok, countries} = MyApp.Seeds.Countries.sync(MyApp.Repo)

Associations

belongs_to

Seeds the dependency first, then sets the foreign key.

defmodule MyApp.Seeds.Organizations do
  use Sow, schema: MyApp.Organization, keys: [:slug]

  def records do
    [
      %{
        slug: "acme-norway",
        name: "ACME Norway",
        country: Sow.belongs_to(MyApp.Seeds.Countries, :code, "NO")
      }
    ]
  end
end

has_many

Seeds children after the parent, injecting the parent's ID.

defmodule MyApp.Seeds.Products do
  use Sow, schema: MyApp.Product, keys: [:slug]

  def records do
    [
      %{
        slug: "premium-widget",
        name: "Premium Widget",
        variants: Sow.has_many(MyApp.Seeds.ProductVariants, foreign_key: :product_id)
      }
    ]
  end
end

many_to_many

Seeds related records first, then associates them via put_assoc.

def records do
  [
    %{
      slug: "premium-widget",
      tags: [
        Sow.many_to_many(MyApp.Seeds.Tags, :slug, "featured"),
        Sow.many_to_many(MyApp.Seeds.Tags, :slug, "new")
      ]
    }
  ]
end

Auto-detect with Sow.assoc

Let Sow detect the association type from your Ecto schema:

%{
  organization: Sow.assoc(Organizations, :slug, "acme"),  # detects belongs_to
  tags: [Sow.assoc(Tags, :slug, "featured")]              # detects many_to_many
}

Pruning

Remove records that aren't in your fixtures:

# Default: only create/update
{:ok, seeded} = Countries.sync(Repo)

# With pruning: also delete stale records
{:ok, seeded, pruned} = Countries.sync(Repo, prune: true)

Seeding Multiple Fixtures

Sow automatically resolves dependencies and seeds in the correct order:

{:ok, results} = Sow.sync_all([
  MyApp.Seeds.Products,       # depends on Organizations, Tags
  MyApp.Seeds.Countries,      # no dependencies
  MyApp.Seeds.Organizations,  # depends on Countries
  MyApp.Seeds.Tags            # no dependencies
], MyApp.Repo)

# Sow sorts: Countries → Tags → Organizations → Products

How It Works

  1. Search Keys - keys: [:field] identifies unique records for upsert
  2. Upsert - Find by keys → update if exists, insert if not
  3. Dependencies - belongs_to/many_to_many sync first; has_many syncs after
  4. Pruning - With prune: true, deletes records not in fixtures

License

MIT