Documentation/Jutsu/Ecto/ skills /ecto-schemas

📖 ecto-schemas

Use when defining and working with Ecto schemas including field types, associations, and embedded schemas. Use when modeling database entities in Elixir.



Overview

Master Ecto schema definitions to model your database entities effectively in Elixir applications.

Basic Schema Definition

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

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

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

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

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