Requisitos
- Elixir 1.14.2
- Erlang/otp 25
Introdução
O Ecto é uma biblioteca feita em elixir que nos possibilita ter uma Api, assim como uma abstração para fazer operações em diferentes tipos de bancos de dados. Por meio dele podemos fazer uso de drives de banco de dados como Mysql(Myxql) e Postgresql(Postgrex).
Esta biblioteca se utiliza de um padrão chamado Repository, no qual todo e qualquer acesso ao banco de dados se dará por meio de funções de um determinado repositório criado. O Ecto é dividido em 4 componentes principais:
- Ecto.Repo
- Responsável por ser o Repositório de acesso ao banco, ou seja, operações de busca(Query), Inserção, atualização e remoção de dados do banco de dados será por meio de funções dele.
- Ecto.Query
- Responsável por ter funções de criação de Query, ou seja, ler do banco de dados alguma informação.
- Ecto.Schema
- Responsável por mapear determinados dados externos para estruturas elixir.
- Ecto.Changeset
- Responsável por validar e filtrar determinados dados recebidos, afim de acessar o banco de dados.
Situação problema
Nada melhor do que entender uma nova tecnologia do que em uma situação problema que você vai aplica-la.
Imagine que um determinado proprietário de uma pizzaria tenha apenas o interesse de manter os dados dos clientes, assim como as pizzas que ele poderá fazer e os pedidos desses clientes para determinadas pizzas existentes.
Podemos mapear um modelo relacional dessa maneira:
Mão na massa
Criando o projeto
Primeiramente vamos criar o nosso projeto usando a ferramenta Mix, então digite no terminal:
mix new pizzaria_ecto --sup
Caso você nunca criou um projeto com o mix, aqui estamos apenas gerando uma estrutura inicial e adicionando um supervisor na nosso projeto, precisamos desse supervisor, pois a nossa aplicação com ecto será um processo e caso ocorra algum erro nele, por alguma razão, então o nosso supervisor irá reiniciar o nosso processo do inicio(let it crash 🥳).
Instalando as dependências
Antes de a gente trabalharmos com o ecto temos que ter essa biblioteca no nosso projeto, então vamos adicionar essas duas dependências na função deps dentro do arquivo mix.exs:
defp deps do
[
{:postgrex, "~> 0.16.5"},
{:ecto_sql, "~> 3.9"}
]
end
Aqui estamos usando o drive do postgresql e estamos fazendo uso de uma biblioteca ecto_sql que tem como dependência o ecto, mas adiciona outras funcionalidades para trabalharmos com banco de dados relacionais. Para instalar e compilar as aplicações devemos rodar o seguinte comando no terminal:
mix do deps.get, compile
Criando o nosso próprio repositório
Dentro do diretório pizzaria_ecto que foi criado poderemos criar um arquivo chamado repo.ex, com seguinte código:
defmodule PizzariaEcto.Repo do
use Ecto.Repo, otp_app: :pizzaria_ecto, adapter: Ecto.Adapters.Postgres
end
Aqui criamos um módulo que chamamos de PizzariaEcto.Repo no qual ele faz uso do Ecto.Repo, eu gosto de pensar que quando usamos o “use” fazemos com que o nosso módulo seja um módulo Ecto.Repo . Além disso temos a keyword :otp_app que nos diz quais configurações vamos usar dentro de um arquivo chamado config.exs(Vamos criar ainda), e por último termos a keyword :adapter que dizemos para o ecto que vamos usar o banco de dados Postgresql. Agora vamos criar um diretório chamado config(Na raiz do projeto) e dentro dele vamos criar um arquivo config.exs com seguinte código:
import Mix.Config
config :pizzaria_ecto, ecto_repos: [PizzariaEcto.Repo]
config :pizzaria_ecto, PizzariaEcto.Repo,
database: "pizzaria_ecto",
username: "postgres", # Username "postgres" is stardard user in pgdb
password: "postgres", # Password "postgres" is our password when created pg container
hostname: "localhost" # We expose our port when created our pg container
Na código acima estamos fazendo algumas configurações para o nosso querido ecto, no qual temos o uso da macro config que setamos qual o nome da configuração, veja que usamos o mesmo atom usado na keyword :opt_app no módulo PizzariaEcto.Repo, além disso temos uma keyword dizendo quais serão os repositórios no nosso projeto, poderiamos ter mais de um acessando diferentes banco de dados.
Na segunda chamada a macro config temos as informações necessárias para o adapter acessar o banco de dados rodando. Opps, ainda não subimos um container postgresql, então vamos fazer isso.
Subindo container postgresql
No diretório home do projeto vamos criar um arquivo docker-compose.yml com seguinte código:
version: '3.9'
services:
db:
container_name: "pg_pizzaria_ecto"
image: postgres:latest
environment:
POSTGRES_PASSWORD: postgres
volumes:
- postgres-db:/var/lib/postgresql/data
ports:
- '5432:5432'
volumes:
postgres-db:
Posteriormente vamos subir o container com seguinte comando no terminal:
docker-compose up -d
Criando Schemas
Uma das formas de mapear determinados dados externos para estruturas elixir é usando o Ecto.Schema, com elas é possível fazer operações no banco de dados com retornos em estruturas elixir específicas. Dentro do diretório pizzaria_ecto crie um arquivo chamado client.ex com o seguinte conteúdo:
defmodule PizzariaEcto.Client do
use Ecto.Schema
alias PizzariaEcto.{Order}
schema "clients" do
field :name, :string
field :phone, :string
timestamps() # field :updated_at, :naive_datetime
# field :inserted_at, :naive_datetime
has_many :order, Order
end
end
Aqui temos um módulo que é um Ecto.Schema, assim algumas funções e macros podem ser chamadas dentro do módulo que ele é especificado, um destes macros é a schema que recebe como argumento a tabela no banco de dados, assim ele vai mapear determinados campos para uma tabela no banco de dados, porém ainda não será criada as tabelas no banco de dados.
Associação Um para Muitos
Algo curioso que podemos ver no schema de PizzariaEcto.Client é a macro has_many, aqui estamos tentando mapear Um cliente podendo fazer vários pedidos, porém para ter o “casamento” deste relacionamento é necessário criar o segundo schema, então crie um arquivo chamado order.ex com o conteúdo:
defmodule PizzariaEcto.Order do
use Ecto.Schema
alias PizzariaEcto.{Client, Pizza, OrderPizza}
schema "orders" do
field :price, :float
timestamps()
belongs_to :client, Client
many_to_many :pizza, Pizza, join_through: OrderPizza
end
end
No código acima fizemos a mesma coisa do código anterior, mapeamos determinados campos com uma tabela no banco de dados, entretanto temos a outra parte do primeiro relacionamento entre Cliente e Order, em que usamos a macro belongs_to, com esse relacionamento estabelecido é esperado que tenha uma coluna na tabela orders chamada client_id.
Associação Muitos para Muitos
Um outro relacionamento que precisamos mapear é entre Order e Pizza, como é uma associação muito para muitos é necessário a criação de uma terceira tabela que vai fazer o papel de guardar order_id e pizza_id, assim é necessário usar a macro many_to_many em ambos os schemas e usar a keyword join_through mostrando qual o schema será usado. Vamos criar o schema de pizza, então crie um arquivo chamado pizza.ex, com o conteúdo:
defmodule PizzariaEcto.Pizza do
use Ecto.Schema
alias PizzariaEcto.{Order,OrderPizza}
schema "pizzas" do
field :name, :string
timestamps()
many_to_many :order, Order, join_through: OrderPizza
end
end
Vamos também criar o schema OrderPizza, então crie o arquivo order_pizza.ex.
defmodule PizzariaEcto.OrderPizza do
use Ecto.Schema
alias PizzariaEcto.{Pizza, Order}
schema "orders_pizzars" do
timestamps()
belongs_to :order, Order
belongs_to :pizza, Pizza
end
end
Ótimo, com esses schemas criados poderemos utiliza-los para fazer determinadas operações no banco de dados, mas com a criação desses ainda não estão criadas as tabelas no banco de dados, para isso é necessário criar as migrações, então vamos lá.
Criando migrations
Tabela de Cliente
O Ecto se utiliza de um componente chamado Ecto.Migrations para fazer as migrações necessárias, então para cria-la podemos usar um linha de comando no terminal, então digite:
mix ecto.gen.migrations add_clients_table
O código acima vai criar um arquivo dentro do seguinte path priv/SEU_REPO/migrations/ID_ALEATÓRIO_add_clients_table , como eu já mencionei podemos ter vários repositórios, então este SEU_REPO significa qual repositório será criado a migração, porém como existe apenas um, então será ele. O conteúdo do arquivo criado, será o seguinte:
defmodule PizzariaEcto.Repo.Migrations.AddClientsTable do
use Ecto.Migration
def change do
end
end
Esta função change será a chamada pelo Ecto para fazer a migração, então vamos adicionar a nossa tabela:
defmodule PizzariaEcto.Repo.Migrations.AddClientsTable do
use Ecto.Migration
def change do
create table("clients") do
add :name, :string, null: false
add :phone, :string, null: false
timestamps()
end
create index("clients", :name)
end
end
Temos aqui a macro create que recebe o que será criado no banco e um bloco de código, então estamos criando a tabela clients com algumas colunas, podemos perceber que o nome aqui é o mesmo que colocamos no schema PizzariaEcto.Client. Além da tabela poderemos criar index para maximizar as buscas por uma coluna no banco. Podemos criar agora a tabela de Order.
Tabela de Order
mix ecto.gen.migrations add_orders_table
Criado o arquivo priv/SEU_REPO/migrations/ID_ALEATÓRIO_add_orders_table
defmodule PizzariaEcto.Repo.Migrations.AddOrdersTable do
use Ecto.Migration
def change do
create table("orders") do
add :price, :float, null: false
add :client_id, references("clients", on_delete: :nothing)
timestamps null: true
end
end
end
Veja que pelo fato de termos uma associação de 1 para muitos entre Cliente e Order , então é necessário que orders tenha a foreign_key, então podemos adicionar usando o tipo references.
Tabela de Pizza
mix ecto.gen.migrations add_pizzas_table
defmodule PizzariaEcto.Repo.Migrations.AddPizzasTable do
use Ecto.Migration
def change do
create table("pizzas") do
add :name, :string, null: false
timestamps null: true
end
end
end
No código acima criamos a migration de pizza, mas podemos voltar no schema de Pizza e Order, e ver que eles tem uma relação de muitos para muitos entre eles, e criamos um schema OrderPizza para receber as foreign_keys, então vamos criar a tabela orders_pizzas que mapeamos no schema OrderPizza.
mix ecto.gen.migrations add_orders_pizzas_table
defmodule PizzariaEcto.Repo.Migrations.AddOrdersPizzasTable do
use Ecto.Migration
def change do
create table("orders_pizzas") do
add :order_id, references("orders")
add :pizza_id, references("pizzas")
timestamps null: true
end
create index(:orders_pizzas, :order_id)
create index(:orders_pizzas, :pizza_id)
end
end
OK, todas as migrations foram criadas até agora, mas é necessário executar a migração, então vamos digitar no terminal:
mix ecto.migrate
E done!
Vamos para parte 2:
Segunda Parte do Artigo
Referências
Programming Ecto - Build Database Apps in Elixir for Scalability and Performance
Top comments (0)