elixir-ecto-patterns

Master Ecto, Elixir's database wrapper and query generator. This skill covers schemas, changesets, queries, associations, and transactions for building robust database applications.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "elixir-ecto-patterns" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-elixir-ecto-patterns

Elixir Ecto Patterns

Master Ecto, Elixir's database wrapper and query generator. This skill covers schemas, changesets, queries, associations, and transactions for building robust database applications.

Schema Definition

defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset

schema "users" do field :name, :string field :email, :string field :age, :integer field :is_active, :boolean, default: true field :role, Ecto.Enum, values: [:user, :admin, :moderator]

has_many :posts, MyApp.Post
belongs_to :organization, MyApp.Organization

timestamps()

end

def changeset(user, attrs) do user |> cast(attrs, [:name, :email, :age, :is_active, :role]) |> validate_required([:name, :email]) |> validate_format(:email, ~r/@/) |> validate_number(:age, greater_than: 0, less_than: 150) |> unique_constraint(:email) end end

Changeset Validations

defmodule MyApp.Post do use Ecto.Schema import Ecto.Changeset

schema "posts" do field :title, :string field :body, :text field :published, :boolean, default: false field :tags, {:array, :string}, default: []

belongs_to :user, MyApp.User

timestamps()

end

def changeset(post, attrs) do post |> cast(attrs, [:title, :body, :published, :tags, :user_id]) |> validate_required([:title, :body, :user_id]) |> validate_length(:title, min: 3, max: 100) |> validate_length(:body, min: 10) |> foreign_key_constraint(:user_id) end

def publish_changeset(post) do post |> change(published: true) end end

Basic Queries

import Ecto.Query

Get all users

Repo.all(User)

Get user by ID

Repo.get(User, 1) Repo.get!(User, 1) # Raises if not found

Get by specific field

Repo.get_by(User, email: "user@example.com")

Filter with where clause

query = from u in User, where: u.age > 18 Repo.all(query)

Select specific fields

query = from u in User, select: {u.id, u.name} Repo.all(query)

Order results

query = from u in User, order_by: [desc: u.inserted_at] Repo.all(query)

Limit and offset

query = from u in User, limit: 10, offset: 20 Repo.all(query)

Complex Queries

Combining multiple conditions

query = from u in User, where: u.is_active == true, where: u.age >= 18, order_by: [desc: u.inserted_at], limit: 10

Repo.all(query)

Using pipe syntax

User |> where([u], u.is_active == true) |> where([u], u.age >= 18) |> order_by([u], desc: u.inserted_at) |> limit(10) |> Repo.all()

Dynamic queries

def filter_users(params) do User |> filter_by_name(params["name"]) |> filter_by_age(params["min_age"]) |> Repo.all() end

defp filter_by_name(query, nil), do: query defp filter_by_name(query, name) do where(query, [u], ilike(u.name, ^"%#{name}%")) end

defp filter_by_age(query, nil), do: query defp filter_by_age(query, min_age) do where(query, [u], u.age >= ^min_age) end

Associations and Preloading

Preload associations

user = Repo.get(User, 1) |> Repo.preload(:posts)

Preload nested associations

user = Repo.get(User, 1) |> Repo.preload([posts: :comments])

Query with preload

query = from u in User, preload: [:posts, :organization] Repo.all(query)

Custom preload query

posts_query = from p in Post, where: p.published == true

query = from u in User, preload: [posts: ^posts_query] Repo.all(query)

Join and preload

query = from u in User, join: p in assoc(u, :posts), where: p.published == true, preload: [posts: p]

Repo.all(query)

Aggregations and Grouping

Count records

Repo.aggregate(User, :count)

Count with condition

query = from u in User, where: u.is_active == true Repo.aggregate(query, :count)

Other aggregations

Repo.aggregate(User, :avg, :age) Repo.aggregate(User, :sum, :age) Repo.aggregate(User, :max, :age)

Group by

query = from u in User, group_by: u.role, select: {u.role, count(u.id)}

Repo.all(query)

Group with having

query = from u in User, group_by: u.role, having: count(u.id) > 5, select: {u.role, count(u.id)}

Repo.all(query)

Inserting and Updating

Insert with changeset

attrs = %{name: "John", email: "john@example.com", age: 30}

%User{} |> User.changeset(attrs) |> Repo.insert()

Insert without changeset

Repo.insert(%User{name: "Jane", email: "jane@example.com"})

Update

user = Repo.get(User, 1)

user |> User.changeset(%{age: 31}) |> Repo.update()

Update all

query = from u in User, where: u.is_active == false Repo.update_all(query, set: [is_active: true])

Delete

user = Repo.get(User, 1) Repo.delete(user)

Delete all

query = from u in User, where: u.is_active == false Repo.delete_all(query)

Transactions

Basic transaction

Repo.transaction(fn -> user = Repo.insert!(%User{name: "Alice"}) Repo.insert!(%Post{title: "First post", user_id: user.id}) end)

Multi for complex transactions

alias Ecto.Multi

Multi.new() |> Multi.insert(:user, User.changeset(%User{}, user_attrs)) |> Multi.insert(:post, fn %{user: user} -> Post.changeset(%Post{}, Map.put(post_attrs, :user_id, user.id)) end) |> Multi.run(:send_email, fn _repo, %{user: user} -> send_welcome_email(user) end) |> Repo.transaction()

Embedded Schemas

defmodule MyApp.Address do use Ecto.Schema import Ecto.Changeset

embedded_schema do field :street, :string field :city, :string field :state, :string field :zip, :string end

def changeset(address, attrs) do address |> cast(attrs, [:street, :city, :state, :zip]) |> validate_required([:city, :state]) end end

defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset

schema "users" do field :name, :string embeds_one :address, MyApp.Address

timestamps()

end

def changeset(user, attrs) do user |> cast(attrs, [:name]) |> cast_embed(:address, required: true) end end

Custom Ecto Types

defmodule MyApp.Encrypted do use Ecto.Type

def type, do: :binary

def cast(value) when is_binary(value), do: {:ok, value} def cast(_), do: :error

def dump(value) when is_binary(value) do {:ok, encrypt(value)} end

def load(value) when is_binary(value) do {:ok, decrypt(value)} end

defp encrypt(value) do # Encryption logic value end

defp decrypt(value) do # Decryption logic value end end

Usage in schema

schema "users" do field :secret, MyApp.Encrypted end

When to Use This Skill

Use elixir-ecto-patterns when you need to:

  • Build database-backed Elixir applications

  • Define schemas and data models with validations

  • Write complex database queries with Ecto's DSL

  • Manage database relationships and associations

  • Handle data transformations with changesets

  • Implement transactions for data consistency

  • Work with PostgreSQL, MySQL, or other databases

  • Build Phoenix applications with database access

  • Create robust data validation layers

Best Practices

  • Always use changesets for data validation

  • Preload associations to avoid N+1 queries

  • Use transactions for multi-step database operations

  • Leverage Ecto.Multi for complex transaction logic

  • Keep schemas focused and avoid god objects

  • Use virtual fields for computed or temporary data

  • Index foreign keys and frequently queried fields

  • Use fragments for complex SQL when needed

  • Write composable query functions

  • Test database constraints and validations

Common Pitfalls

  • Not preloading associations (N+1 query problem)

  • Forgetting to validate required fields

  • Not using transactions for related operations

  • Hardcoding queries instead of composing them

  • Ignoring database constraints in schemas

  • Not handling changeset errors properly

  • Overusing embedded schemas for relational data

  • Missing indexes on foreign keys

  • Not using Repo.transaction for multi-step operations

  • Exposing raw Ecto queries in business logic

Resources

  • Ecto Documentation

  • Ecto Query Guide

  • Ecto Changeset

  • Phoenix with Ecto

  • Ecto Best Practices

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review