Inicio / Elixir / Elixir: Programación Funcional y Concurrente / Ecto y Bases de Datos

Ecto y Bases de Datos

Repo, Schema, changesets, queries y migrations.

Avanzado Bases de datos
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Ecto y Bases de Datos en Elixir

Ecto es la biblioteca principal de Elixir para interactuar con bases de datos. No es un ORM tradicional sino un toolkit que proporciona esquemas, changesets para validación, un DSL de queries composable y migraciones. Ecto sigue la filosofía explícita de Elixir: nada sucede por arte de magia.

Configuración del Repo

El Repo es el punto de entrada para todas las operaciones con la base de datos:

# lib/mi_app/repo.ex
defmodule MiApp.Repo do
  use Ecto.Repo,
    otp_app: :mi_app,
    adapter: Ecto.Adapters.Postgres
end

# config/dev.exs
config :mi_app, MiApp.Repo,
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  database: "mi_app_dev",
  pool_size: 10

Comandos de base de datos:

# mix ecto.create    — Crear la base de datos
# mix ecto.drop      — Eliminar la base de datos
# mix ecto.migrate   — Ejecutar migraciones
# mix ecto.rollback  — Revertir última migración
# mix ecto.reset     — Drop + create + migrate

Schemas

Los schemas mapean tablas de la base de datos a structs de Elixir:

defmodule MiApp.Cuentas.Usuario do
  use Ecto.Schema
  import Ecto.Changeset

  schema "usuarios" do
    field :nombre, :string
    field :email, :string
    field :edad, :integer
    field :activo, :boolean, default: true
    field :password_hash, :string
    field :password, :string, virtual: true  # No se persiste

    has_many :posts, MiApp.Blog.Post
    belongs_to :empresa, MiApp.Empresas.Empresa
    many_to_many :roles, MiApp.Cuentas.Rol, join_through: "usuarios_roles"

    timestamps()  # Agrega inserted_at y updated_at
  end
end

Changesets

Los changesets validan y transforman datos antes de persistirlos:

defmodule MiApp.Cuentas.Usuario do
  use Ecto.Schema
  import Ecto.Changeset

  schema "usuarios" do
    field :nombre, :string
    field :email, :string
    field :edad, :integer
    field :bio, :string
    timestamps()
  end

  def changeset(usuario, attrs) do
    usuario
    |> cast(attrs, [:nombre, :email, :edad, :bio])
    |> validate_required([:nombre, :email])
    |> validate_format(:email, ~r/@/)
    |> validate_length(:nombre, min: 2, max: 100)
    |> validate_number(:edad, greater_than: 0, less_than: 150)
    |> validate_length(:bio, max: 500)
    |> unique_constraint(:email)
  end

  def changeset_registro(usuario, attrs) do
    usuario
    |> changeset(attrs)
    |> validate_required([:edad])
    |> put_change(:activo, true)
  end
end

# Uso
changeset = Usuario.changeset(%Usuario{}, %{nombre: "Ana", email: "ana@mail.com"})
changeset.valid?  # => true

changeset = Usuario.changeset(%Usuario{}, %{nombre: ""})
changeset.valid?  # => false
changeset.errors  # => [nombre: {"can't be blank", ...}, email: {"can't be blank", ...}]

Queries

Ecto proporciona un DSL de queries composable y seguro:

import Ecto.Query

# Query básica
MiApp.Repo.all(from u in Usuario, where: u.activo == true)

# Query con selección
from u in Usuario,
  where: u.edad >= 18,
  select: {u.nombre, u.email},
  order_by: [asc: u.nombre]

# Queries composables
defmodule MiApp.Cuentas do
  def listar_usuarios(filtros \\ %{}) do
    Usuario
    |> filtrar_activos(filtros)
    |> filtrar_edad(filtros)
    |> ordenar(filtros)
    |> MiApp.Repo.all()
  end

  defp filtrar_activos(query, %{activos: true}) do
    from u in query, where: u.activo == true
  end
  defp filtrar_activos(query, _), do: query

  defp filtrar_edad(query, %{edad_minima: edad}) do
    from u in query, where: u.edad >= ^edad
  end
  defp filtrar_edad(query, _), do: query

  defp ordenar(query, %{orden: campo}) do
    from u in query, order_by: [asc: ^campo]
  end
  defp ordenar(query, _), do: query
end

# Queries con joins y preloads
from u in Usuario,
  join: p in assoc(u, :posts),
  where: p.publicado == true,
  preload: [posts: p]

Operaciones CRUD

defmodule MiApp.Cuentas do
  alias MiApp.{Repo, Cuentas.Usuario}

  def crear_usuario(attrs) do
    %Usuario{}
    |> Usuario.changeset(attrs)
    |> Repo.insert()
  end

  def obtener_usuario(id) do
    Repo.get(Usuario, id)
  end

  def actualizar_usuario(%Usuario{} = usuario, attrs) do
    usuario
    |> Usuario.changeset(attrs)
    |> Repo.update()
  end

  def eliminar_usuario(%Usuario{} = usuario) do
    Repo.delete(usuario)
  end

  # Uso
  # {:ok, usuario} = crear_usuario(%{nombre: "Ana", email: "ana@mail.com"})
  # {:error, changeset} = crear_usuario(%{nombre: ""})
end

Migraciones

Las migraciones definen cambios en la estructura de la base de datos:

# mix ecto.gen.migration crear_usuarios
defmodule MiApp.Repo.Migrations.CrearUsuarios do
  use Ecto.Migration

  def change do
    create table(:usuarios) do
      add :nombre, :string, null: false
      add :email, :string, null: false
      add :edad, :integer
      add :activo, :boolean, default: true
      add :empresa_id, references(:empresas, on_delete: :nilify_all)

      timestamps()
    end

    create unique_index(:usuarios, [:email])
    create index(:usuarios, [:empresa_id])
  end
end

Asociaciones y Preloads

# Cargar asociaciones
usuario = Repo.get(Usuario, 1) |> Repo.preload(:posts)
usuario = Repo.get(Usuario, 1) |> Repo.preload(posts: :comentarios)

# Preload en query
from u in Usuario,
  preload: [:posts, :roles]

# Insertar con asociación
Ecto.build_assoc(usuario, :posts, %{titulo: "Mi Post"})
|> Repo.insert()

Resumen

Ecto es un toolkit completo para trabajar con bases de datos en Elixir. Los schemas definen la estructura, los changesets validan y transforman datos de forma explícita, el DSL de queries permite construir consultas composables y seguras, y las migraciones gestionan la evolución del esquema. A diferencia de los ORMs tradicionales, Ecto favorece la claridad sobre la conveniencia, lo que resulta en código predecible y mantenible.

🔒

Ejercicio práctico disponible

Changesets y queries Ecto

Desbloquear ejercicios
// Changesets y queries Ecto
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes