🎯

ecto-database

🎯Skill

from j-morgan6/elixir-claude-optimization

VibeIndex|
What it does

Manages Ecto database operations by defining schemas, creating changesets, composing queries, preloading associations, and handling transactions efficiently.

πŸ“¦

Part of

j-morgan6/elixir-claude-optimization(7 items)

ecto-database

Installation

Add MarketplaceAdd marketplace to Claude Code
/plugin marketplace add j-morgan6/elixir-claude-optimization
Install PluginInstall plugin from marketplace
/plugin install elixir-optimization --scope user
Claude CodeAdd plugin in Claude Code
/plugin list
Install ScriptRun install script
curl -sL https://raw.githubusercontent.com/j-morgan6/elixir-claude-optimization/main/install.sh | bash
git cloneClone repository
git clone https://github.com/j-morgan6/elixir-claude-optimization.git
πŸ“– Extracted from docs: j-morgan6/elixir-claude-optimization
3Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Use when working with Ecto and database operations. Covers schemas, changesets, queries, associations, preloading, transactions, and migrations.

Overview

# Ecto Database Patterns

Schema Definition

Define schemas with proper types and associations.

```elixir

defmodule MyApp.Media.Image do

use Ecto.Schema

import Ecto.Changeset

schema "images" do

field :title, :string

field :description, :string

field :filename, :string

field :file_path, :string

field :content_type, :string

field :file_size, :integer

belongs_to :folder, MyApp.Media.Folder

timestamps()

end

end

```

Changesets

Always use changesets for data validation and casting.

```elixir

def changeset(image, attrs) do

image

|> cast(attrs, [:title, :description, :filename, :file_path, :content_type, :file_size, :folder_id])

|> validate_required([:title, :filename, :file_path, :content_type, :file_size])

|> validate_length(:title, min: 1, max: 255)

|> validate_inclusion(:content_type, ["image/jpeg", "image/png", "image/gif"])

|> validate_number(:file_size, greater_than: 0, less_than: 10_000_000)

|> foreign_key_constraint(:folder_id)

end

```

Query Composition

Build queries composably using Ecto.Query.

```elixir

import Ecto.Query

def list_images_by_folder(folder_id) do

Image

|> where([i], i.folder_id == ^folder_id)

|> order_by([i], desc: i.inserted_at)

|> Repo.all()

end

def search_images(query_string) do

search = "%#{query_string}%"

Image

|> where([i], ilike(i.title, ^search) or ilike(i.description, ^search))

|> Repo.all()

end

```

Preloading Associations

Use preload to avoid N+1 queries.

Bad:

```elixir

images = Repo.all(Image)

# Later accessing image.folder causes N queries

Enum.each(images, fn image -> image.folder.name end)

```

Good:

```elixir

images =

Image

|> preload(:folder)

|> Repo.all()

Enum.each(images, fn image -> image.folder.name end)

```

Transactions

Use Repo.transaction for operations that must succeed together.

```elixir

def transfer_images(image_ids, from_folder_id, to_folder_id) do

Repo.transaction(fn ->

with {:ok, from_folder} <- get_folder(from_folder_id),

{:ok, to_folder} <- get_folder(to_folder_id),

{count, nil} <- update_images(image_ids, to_folder_id) do

{:ok, count}

else

{:error, reason} -> Repo.rollback(reason)

_ -> Repo.rollback(:unknown_error)

end

end)

end

```

Insert and Update

Use Repo.insert and Repo.update with changesets.

```elixir

def create_image(attrs) do

%Image{}

|> Image.changeset(attrs)

|> Repo.insert()

end

def update_image(%Image{} = image, attrs) do

image

|> Image.changeset(attrs)

|> Repo.update()

end

```

Upsert Operations

Use on_conflict for upsert behavior.

```elixir

def create_or_update_folder(attrs) do

%Folder{}

|> Folder.changeset(attrs)

|> Repo.insert(

on_conflict: {:replace, [:name, :updated_at]},

conflict_target: :name

)

end

```

Associations

Define associations properly in schemas.

```elixir

# Parent schema

defmodule MyApp.Media.Folder do

use Ecto.Schema

schema "folders" do

field :name, :string

has_many :images, MyApp.Media.Image

timestamps()

end

end

# Child schema

defmodule MyApp.Media.Image do

use Ecto.Schema

schema "images" do

field :title, :string

belongs_to :folder, MyApp.Media.Folder

timestamps()

end

end

```

Building Associations

Use Ecto.build_assoc to create associated records.

```elixir

def add_image_to_folder(folder, image_attrs) do

folder

|> Ecto.build_assoc(:images)

|> Image.changeset(image_attrs)

|> Repo.insert()

end

```

Casting Associations

Use cast_assoc when working with nested data.

```elixir

def changeset(folder, attrs) do

folder

|> cast(attrs, [:name])

|> cast_assoc(:images, with: &Image.changeset/2)

|> validate_required([:name])

end

```

Dynamic Queries

Build queries dynamically based on filters.

```elixir

def list_images(filters) do

Image

|> apply_filters(filters)

|> Repo.all()

end

defp apply_filters(query, filters) do

Enum.reduce(filters, query, fn

{:folder_id, folder_id}, query ->

where(query, [i], i.folder_id == ^folder_id)

{:search, term}, query ->

where(query, [i], ilike(i.title, ^"%#{term}%"))

{:min_size, size}, query ->

where(query, [i], i.file_size >= ^size)

_, query ->

query

end)

end

```

Aggregations

Use aggregation functions for statistics.

```elixir

def count_images_by_folder do

Image

|> group_by([i], i.folder_id)

|> select([i], {i.folder_id, count(i.id)})

|> Repo.all()

|> Map.new()

end

def total_storage_used do

Image

|> select([i], sum(i.file_size))

|> Repo.one()

end

```

Repo Functions

Common Repo operations:

```elixir

# Fetch single record

Repo.get(Image, id) # Returns record or nil

Repo.get!(Image, id) # Returns record or raises

Repo.get_by(Image, title: "Photo")

# Fetch all records

Repo.all(Image)

# Insert

Repo.insert(changeset) # Returns {:ok, record} or {:error, changeset}

Repo.insert!(changeset) # Returns record or raises

# Update

Repo.update(changeset)

Repo.update!(changeset)

# Delete

Repo.delete(record)

Repo.delete!(record)

# Delete all matching

Repo.delete_all(Image)

Repo.delete_all(where(Image, [i], i.folder_id == ^folder_id))

```

Migrations

Write clear, reversible migrations.

```elixir

defmodule MyApp.Repo.Migrations.CreateImages do

use Ecto.Migration

def change do

create table(:images) do

add :title, :string, null: false

add :description, :text

add :filename, :string, null: false

add :file_path, :string, null: false

add :content_type, :string, null: false

add :file_size, :integer, null: false

add :folder_id, references(:folders, on_delete: :nilify_all)

timestamps()

end

create index(:images, [:folder_id])

create index(:images, [:inserted_at])

end

end

```

Unique Constraints

Add unique constraints in schema and migration.

```elixir

# Migration

create unique_index(:folders, [:name])

# Schema changeset

def changeset(folder, attrs) do

folder

|> cast(attrs, [:name])

|> validate_required([:name])

|> unique_constraint(:name)

end

```

Virtual Fields

Use virtual fields for computed or temporary data.

```elixir

schema "images" do

field :title, :string

field :file_path, :string

field :url, :string, virtual: true

timestamps()

end

def with_url(%Image{} = image) do

%{image | url: "/uploads/#{Path.basename(image.file_path)}"}

end

```

Custom Types

Define custom Ecto types for special data.

```elixir

defmodule MyApp.FileSize do

use Ecto.Type

def type, do: :integer

def cast(size) when is_integer(size) and size >= 0, do: {:ok, size}

def cast(_), do: :error

def load(size), do: {:ok, size}

def dump(size), do: {:ok, size}

end

```

Context Pattern

Organize database operations in contexts.

```elixir

defmodule MyApp.Media do

alias MyApp.Media.{Image, Folder}

alias MyApp.Repo

def list_images, do: Repo.all(Image)

def get_image!(id), do: Repo.get!(Image, id)

def create_image(attrs) do

%Image{}

|> Image.changeset(attrs)

|> Repo.insert()

end

def update_image(%Image{} = image, attrs) do

image

|> Image.changeset(attrs)

|> Repo.update()

end

def delete_image(%Image{} = image) do

Repo.delete(image)

end

end

```