While belongs_to syncs a fixture and uses the result, sometimes you need to reference data that already exists in the database without syncing it. That's where Sow.lookup comes in.
Overview
| Feature | belongs_to | lookup |
|---|---|---|
| Syncs fixture | Yes | No |
| Queries database | After sync | Directly |
| Creates dependency | Yes | No |
| Use case | Fixture references | Existing data |
Basic Usage
defmodule MyApp.Seeds.Organizations do
use Sow, schema: MyApp.Organization, keys: [:slug]
def records do
[
%{
slug: "acme-norway",
name: "ACME Norway",
# Query the database directly for country.id where code = "NO"
country_id: Sow.lookup(MyApp.Country, :code, "NO")
}
]
end
endWhen to Use Lookup
1. Referencing Core Data
When your fixtures reference data managed elsewhere (migrations, admin UI, etc.):
# Countries are managed in migrations, not fixtures
country_id: Sow.lookup(MyApp.Country, :code, "NO")2. Cross-Module References
When fixtures in different modules reference each other without creating dependencies:
# Don't want to sync Users fixture, just reference existing user
created_by_id: Sow.lookup(MyApp.User, :email, "admin@example.com")3. Avoiding Circular Dependencies
When two fixtures would otherwise create a cycle:
# Instead of belongs_to which would create dependency
parent_org_id: Sow.lookup(MyApp.Organization, :slug, "parent-corp")API Reference
Simple Lookup
# Returns :id field by default
Sow.lookup(schema, key, value)
# Example
country_id: Sow.lookup(MyApp.Country, :code, "NO")
# → Queries: SELECT id FROM countries WHERE code = 'NO'Custom Field
# Return a different field
Sow.lookup(schema, key, value, field: :field_name)
# Example
country_name: Sow.lookup(MyApp.Country, :code, "NO", field: :name)
# → Queries: SELECT name FROM countries WHERE code = 'NO'Multiple Match Criteria
# Match on multiple fields
Sow.lookup(schema, %{field1: value1, field2: value2})
# Example
org_id: Sow.lookup(MyApp.Organization, %{country_id: 1, type: "enterprise"})
# → Queries: SELECT id FROM organizations WHERE country_id = 1 AND type = 'enterprise'Chained Lookups
Lookups can be nested to resolve values in order:
# First lookup country_id, then use it to find organization
org_id: Sow.lookup(MyApp.Organization, %{
country_id: Sow.lookup(MyApp.Country, :code, "NO"),
name: "ACME"
})This resolves as:
- Query countries for
idwherecode = "NO"→ returns42 - Query organizations for
idwherecountry_id = 42 AND name = "ACME"
Examples
E-commerce Fixtures
defmodule MyApp.Seeds.Orders do
use Sow, schema: MyApp.Order, keys: [:reference]
def records do
[
%{
reference: "ORD-001",
# Look up existing customer
customer_id: Sow.lookup(MyApp.Customer, :email, "john@example.com"),
# Look up product by SKU
product_id: Sow.lookup(MyApp.Product, :sku, "WIDGET-PRO"),
# Look up shipping zone
shipping_zone_id: Sow.lookup(MyApp.ShippingZone, %{
country_id: Sow.lookup(MyApp.Country, :code, "NO"),
type: "standard"
})
}
]
end
endMulti-tenant Fixtures
defmodule MyApp.Seeds.TenantData do
use Sow, schema: MyApp.Setting, keys: [:tenant_id, :key]
def records do
[
%{
tenant_id: Sow.lookup(MyApp.Tenant, :slug, "acme"),
key: "theme",
value: "dark"
},
%{
tenant_id: Sow.lookup(MyApp.Tenant, :slug, "acme"),
key: "timezone",
value: "Europe/Oslo"
}
]
end
endUsing Lookup with Inline Records
defmodule MyApp.Seeds.Flows do
use Sow, schema: MyApp.Flow, keys: [:slug]
def records do
[
%{
slug: "checkout",
stages: Sow.has_many_inline(
[
%{
position: 1,
# Lookup stage that's managed elsewhere
stage_id: Sow.lookup(MyApp.Stage, :type, "cart")
},
%{
position: 2,
stage_id: Sow.lookup(MyApp.Stage, :type, "payment")
}
],
schema: MyApp.FlowStage,
foreign_key: :flow_id,
keys: [:flow_id, :position]
)
}
]
end
endError Handling
If a lookup fails to find a record, Sow raises at sync time:
** (Ecto.NoResultsError) expected at least one result but got none in query:
from c0 in MyApp.Country,
where: c0.code == ^"XX"Ensure the data exists before syncing fixtures that depend on it.
Comparison with belongs_to
# belongs_to: Syncs Countries fixture, then uses result
country: Sow.belongs_to(MyApp.Seeds.Countries, :code, "NO")
# lookup: Just queries database, no sync
country_id: Sow.lookup(MyApp.Country, :code, "NO")Key differences:
belongs_tocreates a dependency in the sync graphlookuphas no dependencies, queries at sync timebelongs_tosets the association field (:country)lookupsets the foreign key field (:country_id)
Next Steps
- Create Wrapper Modules to share lookup helpers
- Learn about Associations for fixture-based references