Claude
Skills
Sign in
Back

ecto-schema-patterns

Included with Lifetime
$97 forever

Use when defining data structures using Ecto schemas including fields, associations, embedded schemas, and schema metadata. Use for modeling domain data in Elixir applications.

General

What this skill does


# Ecto Schema Patterns

Master Ecto schemas to define robust data structures for your Elixir applications.
This skill covers schema definitions, field types, associations, embedded schemas,
and advanced patterns for modeling complex domain data.

## Basic Schema Definition

```elixir
defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :email, :string
    field :age, :integer
    field :confirmed_at, :naive_datetime

    timestamps()
  end
end
```

Schemas map to database tables and define the structure of your data. Each schema
has a source name (table name) and a list of fields with their types. The `timestamps()`
macro automatically adds `inserted_at` and `updated_at` fields.

## Field Types and Options

```elixir
defmodule MyApp.Product do
  use Ecto.Schema

  schema "products" do
    # Standard field types
    field :title, :string
    field :description, :string
    field :price, :decimal
    field :quantity, :integer
    field :is_active, :boolean, default: true
    field :published_at, :utc_datetime

    # Enum type
    field :status, Ecto.Enum, values: [:draft, :published, :archived]

    # Map type for unstructured data
    field :metadata, :map

    # Array type
    field :tags, {:array, :string}

    # Binary type for binary data
    field :image_data, :binary

    # Virtual field (not persisted to database)
    field :display_price, :string, virtual: true

    timestamps()
  end
end
```

Ecto supports a wide range of field types including strings, integers, decimals,
booleans, datetime types, enums, maps, arrays, and binary data. Virtual fields
exist only in memory and are useful for computed values.

## Using Map Type for Flexible Data

```elixir
defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :email, :string
    field :data, :map

    timestamps()
  end
end

# Usage
user = %MyApp.User{
  name: "John Doe",
  email: "[email protected]",
  data: %{
    preferences: %{
      theme: "dark",
      notifications: true
    },
    settings: %{
      language: "en"
    }
  }
}
```

The `:map` type allows storing arbitrary Elixir maps in the database, providing
flexibility for unstructured or semi-structured data without requiring schema changes.

## Embedded Schemas

```elixir
defmodule MyApp.Address do
  use Ecto.Schema

  embedded_schema do
    field :street, :string
    field :city, :string
    field :state, :string
    field :zip_code, :string
    field :country, :string, default: "US"
  end
end
```

Embedded schemas define data structures that are not tied to a database table.
They can be embedded within other schemas or used independently in memory for
data validation and casting.

## Embedding One Association

```elixir
defmodule MyApp.Order do
  use Ecto.Schema

  schema "orders" do
    field :total, :decimal
    field :status, :string

    embeds_one :item, Item

    timestamps()
  end
end

defmodule MyApp.Item do
  use Ecto.Schema

  embedded_schema do
    field :title, :string
    field :price, :decimal
    field :quantity, :integer
  end
end
```

The `embeds_one` macro defines a one-to-one relationship with an embedded schema.
The embedded data is stored as a JSON or map column in the parent table, not in
a separate table.

## Inline Embedded Schema Definition

```elixir
defmodule MyApp.Parent do
  use Ecto.Schema

  schema "parents" do
    field :name, :string

    embeds_one :child, Child do
      field :name, :string
      field :age, :integer
    end

    timestamps()
  end
end
```

Schemas can be embedded inline using a `do` block, which creates a nested module
(e.g., `MyApp.Parent.Child`). This is useful for simpler embedded structures that
don't need to be defined separately.

## Embedding Many Association

```elixir
defmodule MyApp.Order do
  use Ecto.Schema

  schema "orders" do
    field :customer_name, :string
    field :total, :decimal

    embeds_many :items, OrderItem do
      field :product_name, :string
      field :quantity, :integer
      field :price, :decimal
    end

    timestamps()
  end
end
```

The `embeds_many` macro defines a one-to-many relationship with embedded schemas.
Multiple embedded records are stored as a JSON array in the parent table.

## Complex Embedded Schema with Custom Changeset

```elixir
defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :full_name, :string
    field :email, :string
    field :avatar_url, :string
    field :confirmed_at, :naive_datetime

    embeds_one :profile, Profile do
      field :online, :boolean
      field :dark_mode, :boolean
      field :visibility, Ecto.Enum, values: [:public, :private, :friends_only]
    end

    timestamps()
  end

  def changeset(%__MODULE__{} = user, attrs \\ %{}) do
    user
    |> Ecto.Changeset.cast(attrs, [:full_name, :email])
    |> Ecto.Changeset.cast_embed(:profile, required: true, with: &profile_changeset/2)
  end

  def profile_changeset(profile, attrs \\ %{}) do
    profile
    |> Ecto.Changeset.cast(attrs, [:online, :dark_mode, :visibility])
    |> Ecto.Changeset.validate_required([:online, :visibility])
  end
end
```

Custom changeset functions can be defined for embedded schemas using the `:with`
option in `cast_embed/3`. This allows for specific validation logic on nested data.

## Extracted Embedded Schema Module

```elixir
# user/user.ex
defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :full_name, :string
    field :email, :string
    field :avatar_url, :string
    field :confirmed_at, :naive_datetime

    embeds_one :profile, MyApp.UserProfile

    timestamps()
  end
end

# user/user_profile.ex
defmodule MyApp.UserProfile do
  use Ecto.Schema

  embedded_schema do
    field :online, :boolean
    field :dark_mode, :boolean
    field :visibility, Ecto.Enum, values: [:public, :private, :friends_only]
  end

  def changeset(%__MODULE__{} = profile, attrs \\ %{}) do
    profile
    |> Ecto.Changeset.cast(attrs, [:online, :dark_mode, :visibility])
    |> Ecto.Changeset.validate_required([:online, :visibility])
  end
end
```

Extracting embedded schemas into dedicated modules improves organization and
allows the embedded schema to have its own changeset functions, validations,
and behavior.

## Belongs To Association

```elixir
defmodule MyApp.Comment do
  use Ecto.Schema

  schema "comments" do
    field :body, :string
    field :author, :string

    belongs_to :post, MyApp.Post

    timestamps()
  end
end

defmodule MyApp.Post do
  use Ecto.Schema

  schema "posts" do
    field :title, :string
    field :body, :string

    has_many :comments, MyApp.Comment

    timestamps()
  end
end
```

The `belongs_to` macro defines a foreign key relationship. By default, it creates
a `post_id` field in the `comments` table. The parent schema typically defines
the inverse relationship with `has_many`.

## Custom Belongs To Field

```elixir
defmodule MyApp.Comment do
  use Ecto.Schema

  schema "comments" do
    field :post_id, :integer
    belongs_to :post, MyApp.Post, define_field: false
  end
end
```

You can customize the foreign key field definition by setting `define_field: false`
and manually defining the field. This is useful when you need special options on
the foreign key field.

## Has One Association

```elixir
defmodule MyApp.Account do
  use Ecto.Schema

  schema "accounts" do
    field :email, :string

    has_one :profile, MyApp.Profile

    timestamps()
  end
end

defmodule MyApp.Profile do
  use Ecto.Schema

  schema "profiles" do
    field :name, :string
    field :age, :integer

    belongs_to :account, MyApp.Account

    timestamps()
  end
end
```

The `has_one` macro defines a one-to-one relationship where the foreign key is
stored in the associated schema. The associated schema must have a corresponding
`belongs_to` relationship.

## Has Many Association

```elixir
defmodule MyApp.User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :email, :string

   

Related in General