ecto-database-patterns
Ecto database patterns: changesets, composable queries, Multi transactions, migrations, multi-tenancy. Use when designing database interactions in Elixir applications.
Best use case
ecto-database-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Ecto database patterns: changesets, composable queries, Multi transactions, migrations, multi-tenancy. Use when designing database interactions in Elixir applications.
Teams using ecto-database-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/ecto-database-patterns/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How ecto-database-patterns Compares
| Feature / Agent | ecto-database-patterns | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Ecto database patterns: changesets, composable queries, Multi transactions, migrations, multi-tenancy. Use when designing database interactions in Elixir applications.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Ecto Database Patterns
Production-ready database interaction patterns with Ecto for Elixir applications.
## When to Use
- Designing database schemas and relationships in Elixir
- Building composable query pipelines
- Implementing complex transactions with Ecto.Multi
- Writing safe migrations for production databases
- Multi-tenant database architectures
## Changesets
### Purpose-Specific Changesets
Create multiple changesets per schema — never one monolithic changeset:
```elixir
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :name, :string
field :password_hash, :string
field :role, Ecto.Enum, values: [:user, :admin]
timestamps()
end
def registration_changeset(user, attrs) do
user
|> cast(attrs, [:email, :name, :password])
|> validate_required([:email, :name, :password])
|> validate_format(:email, ~r/@/)
|> validate_length(:password, min: 8)
|> unique_constraint(:email)
|> hash_password()
end
def profile_changeset(user, attrs) do
user
|> cast(attrs, [:name])
|> validate_required([:name])
|> validate_length(:name, min: 2, max: 100)
end
def admin_changeset(user, attrs) do
user
|> cast(attrs, [:role])
|> validate_inclusion(:role, [:user, :admin])
end
end
```
### Key Rules
- `cast/4` for external (untrusted) data — validates and coerces types
- `change/2` for internal (trusted) data — no validation needed
- `unique_constraint/3` catches DB-level errors after insert
- Compose changesets by piping smaller functions together
## Composable Queries
Build queries incrementally:
```elixir
defmodule MyApp.Products do
import Ecto.Query
def list_products(filters \\ %{}) do
Product
|> maybe_filter_category(filters[:category])
|> maybe_filter_price(filters[:min_price], filters[:max_price])
|> maybe_search(filters[:search])
|> order_by([p], desc: p.inserted_at)
|> Repo.all()
end
defp maybe_filter_category(query, nil), do: query
defp maybe_filter_category(query, cat), do: where(query, [p], p.category == ^cat)
defp maybe_filter_price(query, nil, nil), do: query
defp maybe_filter_price(query, min, max) do
query
|> then(fn q -> if min, do: where(q, [p], p.price >= ^min), else: q end)
|> then(fn q -> if max, do: where(q, [p], p.price <= ^max), else: q end)
end
defp maybe_search(query, nil), do: query
defp maybe_search(query, term) do
where(query, [p], ilike(p.name, ^"%#{term}%"))
end
end
```
### Avoid N+1 — Always Preload
```elixir
# Query-level preload (preferred — single query with JOIN)
from(p in Post, join: a in assoc(p, :author), preload: [author: a])
# Separate preload (two queries)
Repo.all(Post) |> Repo.preload(:author)
```
## Ecto.Multi (Transactions)
```elixir
def create_order(user, cart_items) do
Ecto.Multi.new()
|> Ecto.Multi.insert(:order, Order.changeset(%Order{}, %{user_id: user.id}))
|> Ecto.Multi.run(:line_items, fn repo, %{order: order} ->
items = Enum.map(cart_items, &Map.put(&1, :order_id, order.id))
{count, _} = repo.insert_all(LineItem, items)
{:ok, count}
end)
|> Ecto.Multi.run(:update_stock, fn repo, %{order: _order} ->
# Decrement stock for each item
Enum.each(cart_items, fn item ->
repo.update_all(
from(p in Product, where: p.id == ^item.product_id),
inc: [stock: -item.quantity]
)
end)
{:ok, :done}
end)
|> Repo.transaction()
|> case do
{:ok, %{order: order, line_items: count}} -> {:ok, order}
{:error, failed_op, changeset, _changes} -> {:error, failed_op, changeset}
end
end
```
## Migrations
### Rules
- Migrations are **immutable history** — never edit existing migrations in production
- **Never use Ecto schemas in migrations** (schema changes break old migrations)
- Use `execute/2` for raw SQL forward/backward
```elixir
defmodule MyApp.Repo.Migrations.AddProductsTable do
use Ecto.Migration
def change do
create table(:products) do
add :name, :string, null: false
add :price, :decimal, precision: 10, scale: 2, null: false
add :category_id, references(:categories, on_delete: :restrict)
timestamps()
end
create index(:products, [:category_id])
create index(:products, [:name])
end
end
```
### Concurrent Index (no table lock)
```elixir
@disable_ddl_transaction true
@disable_migration_lock true
def change do
create index(:products, [:name], concurrently: true)
end
```
## Multi-Tenancy
### Foreign Key Approach (simpler)
```elixir
# Add org_id to all tenant resources
schema "projects" do
belongs_to :organization, Organization
field :name, :string
end
# Always scope queries
def list_projects(org_id) do
from(p in Project, where: p.organization_id == ^org_id)
|> Repo.all()
end
```
### Query Prefix Approach (PostgreSQL schemas)
```elixir
# Per-query prefix
Repo.all(Project, prefix: "tenant_#{tenant_id}")
# Per-changeset prefix
%Project{} |> Ecto.put_meta(prefix: "tenant_#{tenant_id}") |> Project.changeset(attrs)
```
## Anti-Patterns
- **Mass assignment**: Always use `cast/4` with explicit field list
- **Schemas in migrations**: Couples migrations to current code — breaks when schema changes
- **N+1 queries**: Always preload associations
- **Single monolithic changeset**: Create purpose-specific changesets
- **Skipping Ecto.Multi**: Individual inserts without transactions leave data inconsistent on failure
- **Dynamic atom creation**: Never `String.to_atom/1` on external data
## Key Libraries
- `Ecto` (3.13+), `Ecto.SQL`, `Postgrex`
- `Flop` — declarative filtering, sorting, pagination
- `Triplex` — multi-tenancy with query prefixesRelated Skills
wcag-audit-patterns
Conduct WCAG 2.2 accessibility audits with automated testing, manual verification, and remediation guidance. Use when auditing websites for accessibility, fixing WCAG violations, or implementing ac...
vector-index-tuning
Optimize vector index performance for latency, recall, and memory. Use when tuning HNSW parameters, selecting quantization strategies, or scaling vector search infrastructure.
similarity-search-patterns
Implement efficient similarity search with vector databases. Use when building semantic search, implementing nearest neighbor queries, or optimizing retrieval performance.
projection-patterns
Build read models and projections from event streams. Use when implementing CQRS read sides, building materialized views, or optimizing query performance in event-sourced systems.
ddd-tactical-patterns
Apply DDD tactical patterns in code using entities, value objects, aggregates, repositories, and domain events with explicit invariants.
database-migrations-sql-migrations
SQL database migrations with zero-downtime strategies for PostgreSQL, MySQL, and SQL Server. Focus on data integrity and rollback plans.
database-migrations-migration-observability
Migration monitoring, CDC, and observability infrastructure
rust-async-patterns
Master Rust async programming with Tokio, async traits, error handling, and concurrent patterns. Use when building async Rust applications, implementing concurrent systems, or debugging async code.
go-concurrency-patterns
Master Go concurrency with goroutines, channels, sync primitives, and context. Use when building concurrent Go applications, implementing worker pools, or debugging race conditions.
cc-skill-backend-patterns
Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.
react-ui-patterns
Modern React UI patterns for loading states, error handling, and data fetching. Use when building UI components, handling async data, or managing UI states.
cc-skill-frontend-patterns
Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.