Wrapper modules let you create a custom base for your fixtures, sharing default options and helper functions across all fixtures in your application.
Overview
Instead of using Sow directly:
defmodule MyApp.Seeds.Products do
use Sow, schema: MyApp.Product, keys: [:slug]
# ...
endYou create a wrapper and use that:
defmodule MyApp.Seeds.Products do
use MyApp.Seeds, schema: MyApp.Product, keys: [:slug]
# Has access to shared helpers!
endCreating a Wrapper
Basic Wrapper
defmodule MyApp.Seeds do
use Sow.Wrapper
endWith Default Options
Set defaults that apply to all fixtures using this wrapper:
defmodule MyApp.Seeds do
use Sow.Wrapper
def __sow_defaults__ do
[callback: :seed_data] # All fixtures use :seed_data instead of :records
end
endWith Helper Functions
Define functions that are available in all fixtures:
defmodule MyApp.Seeds do
use Sow.Wrapper
# Lookup helpers
def country_id(code) do
MyApp.Repo.get_by!(MyApp.Country, code: code).id
end
def user_id(email) do
MyApp.Repo.get_by!(MyApp.User, email: email).id
end
# Formatting helpers
def image_url(path) do
"https://cdn.example.com/#{path}"
end
def slug(name) do
name
|> String.downcase()
|> String.replace(~r/[^a-z0-9]+/, "-")
end
endUsing the Wrapper
defmodule MyApp.Seeds.Products do
use MyApp.Seeds, schema: MyApp.Product, keys: [:slug]
def records do
[
%{
name: "Premium Widget",
slug: slug("Premium Widget"), # Helper from wrapper
image: image_url("products/widget.png"), # Helper from wrapper
created_by_id: user_id("admin@example.com") # Helper from wrapper
}
]
end
endComplete Example
The Wrapper Module
defmodule MyApp.Seeds do
@moduledoc """
Base module for all seed fixtures.
Use this instead of `Sow` directly to get access to
shared helpers and default configuration.
## Usage
defmodule MyApp.Seeds.Countries do
use MyApp.Seeds, schema: MyApp.Country, keys: [:code]
def records do
[%{code: "NO", name: "Norway"}]
end
end
"""
use Sow.Wrapper
alias MyApp.Repo
@doc """
Default options for all fixtures.
"""
def __sow_defaults__ do
[]
end
# ─────────────────────────────────────────────────────
# Lookup Helpers
# ─────────────────────────────────────────────────────
@doc """
Get a country's ID by its code.
"""
def country_id(code) do
Repo.get_by!(MyApp.Country, code: code).id
end
@doc """
Get an organization's ID by its slug.
"""
def org_id(slug) do
Repo.get_by!(MyApp.Organization, slug: slug).id
end
@doc """
Get a user's ID by their email.
"""
def user_id(email) do
Repo.get_by!(MyApp.User, email: email).id
end
# ─────────────────────────────────────────────────────
# Formatting Helpers
# ─────────────────────────────────────────────────────
@doc """
Generate a URL-safe slug from a name.
"""
def slugify(name) do
name
|> String.downcase()
|> String.replace(~r/[^a-z0-9]+/, "-")
|> String.trim("-")
end
@doc """
Generate a CDN URL for an asset.
"""
def cdn_url(path) do
base = Application.get_env(:my_app, :cdn_url, "https://cdn.example.com")
"#{base}/#{path}"
end
@doc """
Generate a placeholder image URL.
"""
def placeholder_image(width, height) do
"https://via.placeholder.com/#{width}x#{height}"
end
# ─────────────────────────────────────────────────────
# Data Helpers
# ─────────────────────────────────────────────────────
@doc """
Get current UTC datetime for timestamps.
"""
def now do
DateTime.utc_now() |> DateTime.truncate(:second)
end
@doc """
Generate a price in cents from a decimal amount.
"""
def cents(amount) when is_float(amount) do
round(amount * 100)
end
def cents(amount) when is_integer(amount) do
amount * 100
end
endUsing the Wrapper in Fixtures
defmodule MyApp.Seeds.Products do
use MyApp.Seeds, schema: MyApp.Product, keys: [:slug]
def records do
[
%{
name: "Basic Widget",
slug: slugify("Basic Widget"),
price: cents(29.99),
image: cdn_url("products/basic-widget.png"),
organization_id: org_id("acme"),
created_by_id: user_id("admin@example.com"),
created_at: now()
},
%{
name: "Premium Widget",
slug: slugify("Premium Widget"),
price: cents(99.99),
image: cdn_url("products/premium-widget.png"),
organization_id: org_id("acme"),
created_by_id: user_id("admin@example.com"),
created_at: now()
}
]
end
endMultiple Wrappers
You can create different wrappers for different contexts:
# For production seed data
defmodule MyApp.Seeds do
use Sow.Wrapper
# Production helpers...
end
# For test fixtures
defmodule MyApp.TestFixtures do
use Sow.Wrapper
def __sow_defaults__ do
[callback: :fixtures]
end
# Test-specific helpers...
def random_email do
"user-#{:rand.uniform(100_000)}@test.example.com"
end
endOption Precedence
Options specified in the fixture override wrapper defaults:
defmodule MyApp.Seeds do
use Sow.Wrapper
def __sow_defaults__ do
[callback: :seed_data]
end
end
# Uses :seed_data callback from wrapper defaults
defmodule MyApp.Seeds.Countries do
use MyApp.Seeds, schema: MyApp.Country, keys: [:code]
def seed_data do
[%{code: "NO", name: "Norway"}]
end
end
# Overrides with :records callback
defmodule MyApp.Seeds.SpecialData do
use MyApp.Seeds, schema: MyApp.Special, keys: [:id], callback: :records
def records do
[%{id: 1, name: "Special"}]
end
endBest Practices
1. Keep Helpers Focused
Each helper should do one thing:
# Good: Single purpose
def country_id(code), do: Repo.get_by!(Country, code: code).id
def country_name(code), do: Repo.get_by!(Country, code: code).name
# Avoid: Too generic
def get_field(schema, where, field), do: ...2. Document Your Helpers
@doc """
Get a country's ID by its ISO code.
## Examples
country_id("NO") # => 42
"""
def country_id(code), do: ...3. Handle Errors Gracefully
def country_id(code) do
case Repo.get_by(Country, code: code) do
nil -> raise "Country not found: #{code}"
country -> country.id
end
end4. Organize by Category
Group helpers logically:
# Lookups
def country_id(code), do: ...
def org_id(slug), do: ...
# Formatting
def slugify(name), do: ...
def cdn_url(path), do: ...
# Data generation
def now, do: ...
def cents(amount), do: ...Next Steps
- Review Getting Started for basic setup
- Learn about Associations for relationships
- Explore Runtime Lookups for database queries