home / skills / thebushidocollective / han / ecto-schemas
This skill helps you model Elixir Ecto schemas and associations efficiently, enabling robust data structures, validations, and embedded documents.
npx playbooks add skill thebushidocollective/han --skill ecto-schemasReview the files below or copy the command above to add this skill to your agents.
---
name: ecto-schemas
user-invocable: false
description: Use when defining and working with Ecto schemas including field types, associations, and embedded schemas. Use when modeling database entities in Elixir.
allowed-tools:
- Bash
- Read
---
# Ecto Schemas
Master Ecto schema definitions to model your database entities effectively in Elixir applications.
## Basic Schema Definition
```elixir
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :name, :string
field :age, :integer
field :is_active, :boolean, default: true
field :role, Ecto.Enum, values: [:user, :admin, :moderator]
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:email, :name, :age, :is_active, :role])
|> validate_required([:email, :name])
|> validate_format(:email, ~r/@/)
|> validate_number(:age, greater_than: 0)
|> unique_constraint(:email)
end
end
```
## Field Types
```elixir
schema "products" do
# Basic types
field :name, :string
field :price, :decimal
field :quantity, :integer
field :is_available, :boolean
field :rating, :float
# Date and time
field :released_on, :date
field :sale_starts_at, :naive_datetime
field :sale_ends_at, :utc_datetime
# Complex types
field :tags, {:array, :string}
field :metadata, :map
field :settings, {:map, :string} # map with string values
# Binary data
field :image_data, :binary
field :uuid, :binary_id
timestamps()
end
```
## Associations
```elixir
defmodule MyApp.Blog.Post do
use Ecto.Schema
schema "posts" do
field :title, :string
field :body, :string
# Belongs to
belongs_to :author, MyApp.Accounts.User
# Has many
has_many :comments, MyApp.Blog.Comment
# Has one
has_one :featured_image, MyApp.Blog.Image
# Many to many
many_to_many :tags, MyApp.Blog.Tag, join_through: "posts_tags"
timestamps()
end
end
defmodule MyApp.Blog.Comment do
use Ecto.Schema
schema "comments" do
field :content, :string
belongs_to :post, MyApp.Blog.Post
belongs_to :user, MyApp.Accounts.User
timestamps()
end
end
```
## Embedded Schemas
```elixir
defmodule MyApp.Accounts.Address do
use Ecto.Schema
# Embedded schema - stored as JSON in parent
embedded_schema do
field :street, :string
field :city, :string
field :state, :string
field :zip, :string
field :country, :string, default: "US"
end
def changeset(address, attrs) do
address
|> cast(attrs, [:street, :city, :state, :zip, :country])
|> validate_required([:street, :city, :state, :zip])
end
end
defmodule MyApp.Accounts.User do
use Ecto.Schema
schema "users" do
field :name, :string
# Single embedded
embeds_one :billing_address, MyApp.Accounts.Address, on_replace: :update
# Multiple embedded
embeds_many :shipping_addresses, MyApp.Accounts.Address, on_replace: :delete
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name])
|> cast_embed(:billing_address)
|> cast_embed(:shipping_addresses)
end
end
```
## Virtual Fields
```elixir
defmodule MyApp.Accounts.User do
use Ecto.Schema
schema "users" do
field :email, :string
field :password_hash, :string
# Virtual fields - not persisted
field :password, :string, virtual: true
field :password_confirmation, :string, virtual: true
field :full_name, :string, virtual: true
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:email, :password, :password_confirmation])
|> validate_required([:email, :password])
|> validate_confirmation(:password)
|> hash_password()
end
defp hash_password(changeset) do
case get_change(changeset, :password) do
nil -> changeset
password -> put_change(changeset, :password_hash, Bcrypt.hash_pwd_salt(password))
end
end
end
```
## When to Use This Skill
Use ecto-schemas when you need to:
- Define database table structures
- Model relationships between entities
- Create embedded documents
- Handle complex data types
- Implement virtual computed fields
## Best Practices
- Use meaningful schema module names
- Keep schemas focused on data structure
- Use embedded schemas for nested JSON
- Define changesets in the schema module
- Use Ecto.Enum for constrained values
- Add database constraints with unique_constraint
## Common Pitfalls
- Forgetting timestamps() in schemas
- Not handling on_replace for embeds
- Mixing business logic into schemas
- Forgetting to add associations on both sides
- Not using virtual fields for sensitive data
- Overusing embedded schemas when separate tables are better
This skill helps you define and work with Ecto schemas for modeling database entities in Elixir applications. It covers field types, associations, embedded schemas, virtual fields, and changeset patterns to validate and transform data. Use it to design clear, maintainable mappings between your Elixir structs and database or JSON storage.
The skill inspects schema declarations and provides guidance for declaring fields, types, timestamps, and UUIDs, and for setting defaults and virtual fields. It explains association macros (belongs_to, has_many, has_one, many_to_many) and how embedded_schema stores nested data as JSON. It also outlines changeset patterns for casting, validations, unique constraints, and handling embedded data changes.
When should I use an embedded schema vs a separate table?
Use embedded schemas when data is tightly coupled to the parent and stored as JSON (no independent lifecycle). Use separate tables when the data needs querying, indexing, or independent associations.
How do I handle updates to embedded entries safely?
Set on_replace (for example :update or :delete) on embeds and use cast_embed in the parent changeset; validate required fields in the embedded changeset to avoid accidental data loss.