Introdução ao Ecto
Tags: elixir, programming, database, ecto
Published: 2022-04-23 14:40:20

# Introdução

Sejam bem-vindos à introdução ao Ecto (um projeto onde realizei uma apresentação na **Idopter Labs**).

Esse projeto tem como objetivo auxiliar todos os desenvolvedores iniciantes na programação funcional.

Fico imensamente feliz com seu interesse em aprender um pouco sobre o Ecto. Espero conseguir fazer você entender todo o básico necessário para que você possa sair daqui já com conhecimento para desenvolver pequenas funcionalidades utilizando Ecto.

# O que é o Ecto?

Ecto é basicamente uma lib para você interagir com seu banco. Então, podemos dizer que ele é uma camada que vai ficar entre seu banco de dados e sua aplicação. 

Vale ressaltar, que o Ecto não é um "**ORM**" _(Object Relation Mapping)_. Pois, ORM é sobre objetos e Elixir é uma linguagem funcional e não temos objetos em elixir.

# Arquitetura do Ecto

A biblioteca Ecto é composta por quatro módulos principais:

- **Repo**: Este módulo permite conexões com o banco. E é com ele que podemos criar/atualizar/deletar recursos e executar queries. Dito isso, é necessário um adapter específico para o SGBD _(sistema de gerenciamento de banco de dados)_. Para conectarmos a banco de dados relacionais **SQL** usamos a biblioteca `Ecto.SQL`.
- **Schema**: Mapeamento de dados entre código Elixir e estruturas SQL (tabelas, colunas, etc.)
- **Query**: Módulo que permite fazer queries no banco de dados através de uma DSL em Elixir de uma forma mais fácil e segura, evitando `SQL Injection`.
- **Changeset**: É um módulo que permite normalizar e validar dados da aplicação.


# Operações Básicas

### Cast e Validações

O Changeset recebe uma struct e com isso ele consegue tanto fazer cast de dados para inserir nessa struct como também fazer validações e modificações.

A função cast pega os parâmetros e tenta fazer o cast nos campos da struct e como segundo argumento temos que definir uma lista de campos para cast.

```elixir
defmodule Ecto4noobs.User do
  use Ecto.Schema
  import Ecto.Changeset

  @required_params [:age, :email, :name]

  schema "users" do
  ...
  end

  def changeset(struct \\ %__MODULE__{}, params) do
    struct
    |> cast(params, @required_params)
  end
end
```

Agora vamos no nosso iex criar um map:

```bash
iex> user_params = %{name: "Rômulo", email: "romulo@tomate.com", age: 23}
%{age: 23, email: "romulo@tomate.com", name: "Rômulo"}
```

E em seguida, vamos criar nosso Changeset:

```bash
iex> alias Ecto4noobs.User
Ecto4noobs.User
iex> User.changeset(user_params)
#Ecto.Changeset<
  action: nil,
  changes: %{age: 23, email: "romulo@tomate.com", name: "Rômulo"},
  errors: [],
  data: #Ecto4noobs.User<>,
  valid?: true
>
```

Agora temos um Changeset do Ecto que é uma struct especial que valida os nossos dados, faz cast dos dados e vai ser mandada para o banco.

Dito isso, vamos criar as validações pela função `validate_required` que também recebe uma lista.

```elixir
defmodule Ecto4noobs.User do
  use Ecto.Schema
  import Ecto.Changeset

  @required_params [:age, :email, :name]

  schema "users" do
  ...
  end

  def changeset(struct \\ %__MODULE__{}, params) do
    struct
    |> cast(params, @required_params)
    |> validate_required(@required_params)
  end
end
```

Voltando para o nosso iex, vamos remover o name de `user_params` e tentar criar o changeset novamente:

```bash
iex> user_params = %{email: "romulo@tomate.com", age: 23}
%{age: 23, email: "romulo@tomate.com"}
iex> User.changeset(user_params)
#Ecto.Changeset<
  action: nil,
  changes: %{age: 23, email: "romulo@tomate.com"},
  errors: [name: {"can't be blank", [validation: :required]}],
  data: #Ecto4noobs.User<>,
  valid?: false
>
```

Perfeito! Está tudo funcionando, ele nós retorna um `error` dizendo que o campo `name` não pode ser vazio e um `valid?` false!

Com isso, já podemos partir para a inserção de dados.

### Escrita de dados

Para fazermos a escrita de dados, vamos utilizar o `Ecto.Repo` que define um repositório e mapeia os dados que temos no elixir e o nosso repositório físico que é o nosso banco de dados.

Vamos ao iex criar nosso map com todos os campos preenchidos:

```bash
iex> user_params = %{name: "Rômulo", email: "romulo@tomate.com", age: 23}
%{age: 23, email: "romulo@tomate.com", name: "Rômulo"}
```

Agora vamos criar nosso `Changeset` e em seguida vamos inserir no nosso banco de dados utilizando `Repo.insert/1`:

```bash
iex> alias Ecto4noobs.User
Ecto4noobs.User
iex> alias Ecto4noobs.Repo
Ecto4noobs.Repo
iex> user_params |> User.changeset() |> Repo.insert()

15:36:36.977 [debug] QUERY OK db=4.4ms decode=1.1ms queue=1.9ms idle=1294.1ms
INSERT INTO "users" ("age","email","name") VALUES ($1,$2,$3) RETURNING "id" [23, "romulo@tomate.com", "Rômulo"]
{:ok,
 %Ecto4noobs.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   age: 23,
   email: "romulo@tomate.com",
   id: 1,
   name: "Rômulo"
 }}
```

### Leitura de dados

Após ter feito a nossa escrita no banco, agora podemos também fazer a leitura de todos os dados e é bem simples, basta usarmos `Repo.all/1`.

Vamos testar no iex:

```bash
iex> Repo.all(User)

15:41:33.261 [debug] QUERY OK source="users" db=1.0ms queue=1.1ms idle=1589.9ms
SELECT u0."id", u0."name", u0."email", u0."age" FROM "users" AS u0 []
[
  %Ecto4noobs.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    age: 23,
    email: "romulo@tomate.com",
    id: 1,
    name: "Rômulo"
  }
]
```

Ou, podemos fazer a leitura utilizando o `Repo.get/2`:

```bash
iex> Repo.get(User, 1)

15:42:45.240 [debug] QUERY OK source="users" db=1.0ms queue=1.6ms idle=1568.2ms
SELECT u0."id", u0."name", u0."email", u0."age" FROM "users" AS u0 WHERE (u0."id" = $1) [1]
%Ecto4noobs.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  age: 23,
  email: "romulo@tomate.com",
  id: 1,
  name: "Rômulo"
}
```

O que fizemos além de passar `User` foi passar o `ID` do usuário que queremos listar.

Além das listagens `Repo.all/1` e `Repo.get/2` também é possível fazer a leitura utilizando um filtro.

Para isso vamos dizer ao módulo que queremos utilizar suas macros.

```bash
iex> require Ecto.Query
Ecto.Query
```

E agora vamos utilizar o filtro para buscar somente usuários que possui o nome **Floki** _(Eu inseri outro usuário antes de fazer a listagem com filtro)_.

Voltando para o iex:

```bash
iex> Ecto.Query.where(User, name: "Floki") |> Repo.all()

15:51:10.119 [debug] QUERY OK source="users" db=0.7ms queue=1.4ms idle=1447.6ms
SELECT u0."id", u0."name", u0."email", u0."age" FROM "users" AS u0 WHERE (u0."name" = 'Floki') []
[
  %Ecto4noobs.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    age: 3,
    email: "floki@gato.com",
    id: 2,
    name: "Floki"
  }
]
```

### Atualização de dados

Para atualizarmos os dados de algum usuário é muito simples!

Vamos utilizar o `Repo.get/2` passando `User` e o `ID` do usuário que queremos atualizar.

```
Repo.get(User, 2)
```

Em seguida, vamos criar um `Changeset` com o campo que queremos fazer a atualização e enviar essa atualização para o banco utilizando `Repo.update/1`

```bash
iex> Repo.get(User, 2) |> User.changeset(%{email: "floki@gato.com"}) |> Repo.update()

15:57:31.334 [debug] QUERY OK source="users" db=1.9ms idle=1662.5ms
SELECT u0."id", u0."name", u0."email", u0."age" FROM "users" AS u0 WHERE (u0."id" = $1) [2]
{:ok,
 %Ecto4noobs.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   age: 3,
   email: "floki@gato.com",
   id: 2,
   name: "Floki"
 }}
```

Prontinho! Nosso campo `e-mail` foi atualizado com sucesso! :)

### Remoção de dados

Se você achou fácil atualizar os dados, verá que para remover é muito mais simples!

Para remover um usuário, vamos utilizar o `Repo.get/2` passando `User` e o `ID` do usuário que queremos deletar.

```
Repo.get(User, 1)
```

E por fim, vamos enviar nossa remoção para o banco utilizando `Repo.delete/1`

```bash
iex> Repo.get(User, 1) |> Repo.delete()

16:02:34.110 [debug] QUERY OK source="users" db=1.9ms queue=0.1ms idle=1438.4ms
SELECT u0."id", u0."name", u0."email", u0."age" FROM "users" AS u0 WHERE (u0."id" = $1) [1]

16:02:34.114 [debug] QUERY OK db=2.6ms queue=1.1ms idle=1440.6ms
DELETE FROM "users" WHERE "id" = $1 [1]
{:ok,
 %Ecto4noobs.User{
   __meta__: #Ecto.Schema.Metadata<:deleted, "users">,
   age: 23,
   email: "romulo@tomate.com",
   id: 1,
   name: "Rômulo"
 }}
```

Para conferir, vamos fazer a leitura de todos os dados:

```bash
iex> Repo.all(User)

16:03:08.243 [debug] QUERY OK source="users" db=1.2ms idle=1572.6ms
SELECT u0."id", u0."name", u0."email", u0."age" FROM "users" AS u0 []
[
  %Ecto4noobs.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    age: 3,
    email: "floki@gato.com",
    id: 2,
    name: "Floki"
  }
]
```

E é isso! Ficou apenas o usuário **Floki** :)

# Conclusão
Muito obrigado pela leitura até aqui e espero ter ajudado de alguma forma. Tem alguma sugestão ou encontrou algum problema? por favor deixe-me saber. 💜

Código do projeto no meu [github](https://github.com/RomuloHe4rt/Introduction_Ecto)