A library for synchronizing code-defined fixtures with a database.
Usage
defmodule MyApp.Fixtures.Countries do
use Sow,
schema: MyApp.Country,
keys: [:code]
def records do
[
%{code: "NO", name: "Norway"},
%{code: "SE", name: "Sweden"}
]
end
endbelongs_to (sync dependency first)
defmodule MyApp.Fixtures.Organizations do
use Sow,
schema: MyApp.Organization,
keys: [:slug]
def records do
[
%{
slug: "org-norway",
country: Sow.belongs_to(MyApp.Fixtures.Countries, :code, "NO")
}
]
end
endhas_many (sync children after parent)
defmodule MyApp.Fixtures.Products do
use Sow,
schema: MyApp.Product,
keys: [:org_id, :type]
def records do
%{
type: :subscription,
variants: Sow.has_many(MyApp.Fixtures.ProductVariants, foreign_key: :product_id)
}
end
endmany_to_many
For many_to_many, the related records are synced first, then passed to put_assoc:
defmodule MyApp.Fixtures.Products do
use Sow,
schema: MyApp.Product,
keys: [:slug]
def records do
%{
slug: "premium",
tags: [
Sow.many_to_many(MyApp.Fixtures.Tags, :slug, "featured"),
Sow.many_to_many(MyApp.Fixtures.Tags, :slug, "new")
]
}
end
endAuto-detect with assoc (shorthand)
Use Sow.assoc/1-3 to auto-detect the association type from the Ecto schema:
defmodule MyApp.Fixtures.Products do
use Sow,
schema: MyApp.Product,
keys: [:slug]
def records do
%{
slug: "premium",
# Auto-detects belongs_to from schema
organization: Sow.assoc(MyApp.Fixtures.Organizations, :slug, "org"),
# Auto-detects many_to_many from schema
tags: [
Sow.assoc(MyApp.Fixtures.Tags, :slug, "featured")
],
# Auto-detects has_many from schema
variants: Sow.assoc(MyApp.Fixtures.ProductVariants)
}
end
endNote: Your schema's changeset must handle many_to_many with put_assoc:
def changeset(product, attrs) do
product
|> cast(attrs, [:slug, :name])
|> maybe_put_assoc(:tags, attrs)
end
defp maybe_put_assoc(changeset, key, attrs) do
case Map.get(attrs, key) do
nil -> changeset
assoc -> put_assoc(changeset, key, assoc)
end
end
Summary
Functions
Auto-detect association type from the Ecto schema.
Marks a belongs_to association. The referenced fixture is synced first,
and its ID is assigned to {field_name}_id.
Returns the default repo configured in application env.
Marks a has_many association. Children are synced after the parent, with the parent's ID injected via the foreign_key.
Marks a has_many association with inline records (no separate fixture module needed).
Runtime database lookup to get a field value from an existing record.
Marks a many_to_many association. The referenced fixture is synced first,
and the model is passed to put_assoc in the changeset.
Sync multiple fixture modules in dependency order.
Types
Callbacks
@callback records() :: fixture_record() | [fixture_record()]
Returns the fixture records to sync. Can return a single map or a list of maps.
The callback name can be customized with the :callback option:
use Sow, schema: MyApp.Country, keys: [:code], callback: :modify
def modify, do: [...]
Functions
Auto-detect association type from the Ecto schema.
This is a shorthand that inspects the schema to determine whether the field is belongs_to, has_many, or many_to_many, and handles it appropriately.
Examples
# Auto-detect and sync
%{country: Sow.assoc(MyApp.Fixtures.Countries)}
# With lookup
%{country: Sow.assoc(MyApp.Fixtures.Countries, :code, "NO")}
# Works for all association types
%{
organization: Sow.assoc(Organizations, :slug, "org"), # belongs_to
tags: [Sow.assoc(Tags, :slug, "featured")], # many_to_many
variants: Sow.assoc(ProductVariants) # has_many
}
Marks a belongs_to association. The referenced fixture is synced first,
and its ID is assigned to {field_name}_id.
Examples
# Sync the Countries fixture, use the first record's ID
%{country: Sow.belongs_to(MyApp.Fixtures.Countries)}
# Sync Countries fixture, find by code "NO", use that record's ID
%{country: Sow.belongs_to(MyApp.Fixtures.Countries, :code, "NO")}
Returns the default repo configured in application env.
Marks a has_many association. Children are synced after the parent, with the parent's ID injected via the foreign_key.
Examples
%{
variants: Sow.has_many(MyApp.Fixtures.ProductVariants, foreign_key: :product_id)
}
Marks a has_many association with inline records (no separate fixture module needed).
Children are synced after the parent, with the parent's ID injected via the foreign_key.
Options
:schema- (required) the Ecto schema module for the nested records:foreign_key- (required) the foreign key field to inject parent's ID:keys- (optional) search keys for upsert, defaults to schema's primary key
Examples
%{
slug: "premium",
variants: Sow.has_many_inline(
[
%{sku: "SMALL", name: "Small"},
%{sku: "LARGE", name: "Large"}
],
schema: MyApp.ProductVariant,
foreign_key: :product_id,
keys: [:product_id, :sku]
)
}Records can also contain relations:
flow_stages: Sow.has_many_inline(
[
%{position: 1, stage: Sow.belongs_to(StageFixture, :type, :select_client)},
%{position: 2, stage: Sow.belongs_to(StageFixture, :type, :payment)}
],
schema: MyApp.FlowStage,
foreign_key: :flow_id,
keys: [:flow_id, :stage_id]
)
Runtime database lookup to get a field value from an existing record.
Unlike belongs_to which syncs a fixture, lookup queries the database
directly for an existing record and extracts a field (default: :id).
Examples
# Simple lookup - get country.id where code = "NO"
country_id: Sow.lookup(MyApp.Country, :code, "NO")
# With custom field extraction
country_name: Sow.lookup(MyApp.Country, :code, "NO", field: :name)
# Multiple match criteria
org_id: Sow.lookup(MyApp.Organization, %{country_id: 1, name: "ACME"})
# Chained lookups - resolve country_id first, then find organization
org_id: Sow.lookup(MyApp.Organization, %{
country_id: Sow.lookup(MyApp.Country, :code, "NO"),
name: "ACME"
})
Marks a many_to_many association. The referenced fixture is synced first,
and the model is passed to put_assoc in the changeset.
Use as a list for multiple associations:
%{
tags: [
Sow.many_to_many(MyApp.Fixtures.Tags, :slug, "featured"),
Sow.many_to_many(MyApp.Fixtures.Tags, :slug, "new")
]
}
Sync multiple fixture modules in dependency order.