Introdução ao Ecto com Elixir: Configuração e Operações Básicas
Durante minha jornada de aprendizado em Elixir, descobri o Ecto, uma poderosa camada de abstração para bancos de dados e gerador de consultas que permite interagir com bancos SQL de forma intuitiva. Embora frequentemente comparado a ORMs como o Entity Framework Core ou o ActiveRecord do Ruby on Rails, o Ecto se diferencia por não rastrear automaticamente estados de entidades, exigindo que os desenvolvedores gerenciem mudanças de forma explícita. Este artigo explora conceitos fundamentais do Ecto, incluindo repositórios, schemas, migrações e operações CRUD. Para recursos avançados, consulte a documentação oficial do Ecto.
O que é o Ecto?
O Ecto é uma camada de abstração para bancos de dados projetada para trabalhar com bancos relacionais como PostgreSQL e MySQL. Ele oferece:
- Mapeamento de tabelas para structs (via schemas).
- Geração de consultas com sintaxe segura usando a sintaxe do Elixir.
- Validação de dados antes da persistência (via changesets).
- Gerenciamento de migrações com controle de versão.
Por que o Ecto não é um ORM tradicional?
O Ecto evita armadilhas comuns de ORMs tradicionais:
- Sem rastreamento automático de estado: Diferente de ORMs, o Ecto não rastreia estados de entidades (ex: campos modificados).
- Fluxo de dados explícito: Desenvolvedores devem passar dados manualmente por changesets antes da persistência.
- Alinhamento com o paradigma funcional: O Ecto segue o modelo de programação funcional do Elixir, evitando o "impedance mismatch" de mapeamento objeto-relacional.
Requisitos
- Elixir 1.18+ (ajuste para versões anteriores, se necessário)
- Adicione as dependências em
mix.exs
:
{:ecto_sql, "~> 3.0"}, # Ecto
{:postgrex, ">= 0.0.0"} # Driver para PostgreSQL
Configuração do Repositório
Um repositório (ou repo) é a interface do Ecto com o banco de dados, semelhante ao DbContext
do Entity Framework.
Configuração Manual
- Defina um módulo de repositório:
defmodule Friends.Repo do
use Ecto.Repo,
otp_app: :friend,
adapter: Ecto.Adapters.Postgres
end
- Configure em
config/config.exs
:
config :friends, Friends.Repo,
database: "friends",
username: "user",
password: "pass",
hostname: "localhost"
Configuração Automática via Mix
Execute:
mix ecto.gen.repo -r Friends.Repo
Executando o Ecto no Início
Adicione o repositório ao supervisor da aplicação em lib/<app_name>/application.ex
:
def start(_type, _args) do
children = [Friends.Repo]
...
end
Atualize config/config.exs
:
config :friends, ecto_repos: [Friends.Repo]
Criando um Schema
Um schema mapeia uma tabela do banco para uma struct do Elixir. Exemplo de schema Person
:
defmodule Friends.Person do
use Ecto.Schema
import Ecto.Changeset
schema "people" do
field :first_name, :string
field :last_name, :string
field :age, :integer
end
def changeset(person, params \\ %{}) do
person
|> cast(params, [:first_name, :last_name, :age])
|> validate_required([:first_name, :last_name])
end
end
Migrações
Migrações definem alterações incrementais no esquema do banco.
Migração Manual
Crie um arquivo em priv/repo/migrations/<datetime>_create_people.exs
:
defmodule Friends.Repo.Migrations.CreatePeople do
use Ecto.Migration
def change do
create table(:people) do
add :first_name, :string
add :last_name, :string
add :age, :integer
end
end
end
Migração Automática via Mix
Execute:
mix ecto.gen.migration create_people
Este comando cria um arquivo vazio para edição.
Executando Migrações
mix ecto.create # Cria o banco de dados
mix ecto.migrate # Executa as migrações
Operações CRUD
Create (Criar)
Insira um novo registro:
person = %Friends.Person{first_name: "Alice", last_name: "Smith", age: 30}
{:ok, inserted_person} = Friends.Repo.insert(person)
Com validação:
changeset = Friends.Person.changeset(%Friends.Person{}, %{first_name: "Alice"})
case Friends.Repo.insert(changeset) do
{:ok, person} -> # Sucesso
{:error, changeset} -> # Trate erros
end
Read (Ler)
Busque registros:
# Por ID
Friends.Repo.get(Friends.Person, 1)
# Primeiro registro
Friends.Repo.one(from p in Friends.Person, order_by: [asc: p.id], limit: 1)
# Todos os registros que atendem a uma condição
Friends.Repo.all(from p in Friends.Person, where: like(p.first_name, "A%"))
Update (Atualizar)
Atualize um registro existente:
person = Friends.Repo.get!(Friends.Person, 1)
changeset = Friends.Person.changeset(person, %{age: 31})
Friends.Repo.update(changeset)
Delete (Excluir)
Exclua um registro:
person = Friends.Repo.get!(Friends.Person, 1)
Friends.Repo.delete(person)
Conclusão
O Ecto equilibra abstração e controle, oferecendo:
- Consultas com segurança de tipos: Prevenção de erros em tempo de compilação.
- Fluxos explícitos: Changesets garantem validação antes da persistência.
- Suporte da comunidade: Amplamente adotado no ecossistema Elixir.
Embora não seja um ORM tradicional, o design funcional do Ecto evita abstrações problemáticas comuns em mapeadores objeto-relacionais. Para padrões avançados (ex: associações), explore suporte a has_many
, belongs_to
e many_to_many
.