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

$curl -o ~/.claude/skills/ecto-database-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/ratnesh-maurya/cursor-claude-personas/main/senior-elixir-developer/.claude/skills/ecto-database-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ecto-database-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ecto-database-patterns Compares

Feature / Agentecto-database-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/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 prefixes

Related Skills

wcag-audit-patterns

5
from ratnesh-maurya/cursor-claude-personas

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

5
from ratnesh-maurya/cursor-claude-personas

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

5
from ratnesh-maurya/cursor-claude-personas

Implement efficient similarity search with vector databases. Use when building semantic search, implementing nearest neighbor queries, or optimizing retrieval performance.

projection-patterns

5
from ratnesh-maurya/cursor-claude-personas

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

5
from ratnesh-maurya/cursor-claude-personas

Apply DDD tactical patterns in code using entities, value objects, aggregates, repositories, and domain events with explicit invariants.

database-migrations-sql-migrations

5
from ratnesh-maurya/cursor-claude-personas

SQL database migrations with zero-downtime strategies for PostgreSQL, MySQL, and SQL Server. Focus on data integrity and rollback plans.

database-migrations-migration-observability

5
from ratnesh-maurya/cursor-claude-personas

Migration monitoring, CDC, and observability infrastructure

rust-async-patterns

5
from ratnesh-maurya/cursor-claude-personas

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

5
from ratnesh-maurya/cursor-claude-personas

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

5
from ratnesh-maurya/cursor-claude-personas

Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.

react-ui-patterns

5
from ratnesh-maurya/cursor-claude-personas

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

5
from ratnesh-maurya/cursor-claude-personas

Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.