💧🍔 Projeto Rockelivery: API para Pedidos em um Restaurante com Elixir e Phoenix (Parte 2)
Tags: elixir, phoenix, ecto, braziliandevs
Published: 2021-06-07 00:55:20

Essa é a segunda parte do projeto __Rockelivery__. Esse projeto faz parte do Bootcamp da [Rocketseat](https://rocketseat.com.br/), ministrado pelo professor [Rafael Camarda](https://www.instagram.com/rafacamarda/).

## Conteúdo:
* [🕵🏻‍♂️ Conhecendo o módulo Ecto.Repo](#ecto-repo)
 - [🧍 Inserindo o Usuário no Banco de Dados](#inserindo-o-usuario-no-banco-de-dados)
 - [🧍‍♀️ Buscando o Usuário no Banco de Dados](#buscando-o-usuario-no-banco-de-dados)
 - [🧍‍♂️ Módulo Users.Create](#modulo-users-create)
* [🌐 Criando a Parte Web](#criando-a-parte-web)
 - [🛣️ Rotas](#rotas)
 - [🕹️ UsersController](#users-controller)
 - [😎 UsersView](#users-view)
 - [🟣 Usando o Insomnia](#usando-o-insomnia)
 - [❌ FallbackController](#fallback-controller)

[![A Jornada do Autodidata em Inglês](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkoqkmh9hic17tnddjf6.png)](https://go.hotmart.com/R93865620U)

## 🕵🏻‍♂️ Conhecendo o Módulo Ecto.Repo <a name="ecto-repo"></a>

Para podermos inserir um usuário no banco de dados precisamos conhecer antes o módulo __Ecto.Repo__. O módulo __Ecto.Repo__ define um repositório. No nosso caso será definido como `Rockelivery.Repo`. Ele nos fornecerá a 'interface' com o nosso banco de dados, nos permitindo fazer a criação, alteração, deleção e consulta dos dados no banco.

Nosso repositório está definido em `lib/rockelivery/repo.ex`. Nesse arquivo podemos ver também que foi usado o adapter do PostgreSQL:

```elixir
defmodule Rockelivery.Repo do
  use Ecto.Repo,
    otp_app: :rockelivery,
    adapter: Ecto.Adapters.Postgres
end
```

A configuração do __Repo__ deve estar em seu ambiente de aplicativo, geralmente definido no arquivo `config/config.exs`:

```elixir
use Mix.Config

# configuração do Repo
config :rockelivery,
  ecto_repos: [Rockelivery.Repo]

# você deve lembrar que fizemos uma configuração
# para definirmos o ID como UUID também nesse arquivo
config :rockelivery, Rockelivery.Repo,
  migration_primary_key: [type: :binary_id],
  migration_foreign_key: [type: :binary_id]
```
Essas definições você encontra na documentação oficial: https://hexdocs.pm/ecto/Ecto.html

__Podemos ter mais de um repositório__, o que significa que podemos nos conectar a mais de um banco de dados. Você pode conferir mais sobre isso nesse artigo: [Meet Ecto, No-compromise Database Wrapper for Concurrent Elixir Apps](https://www.toptal.com/elixir/meet-ecto-database-wrapper-for-elixir)

Este artigo em português também pode te ajudar a ter mais clareza sobre o repositório: https://elixirschool.com/pt/lessons/ecto/basics/

Um adaptador (adapter) é necessário para se comunicar com o banco de dados. O __Ecto__ suporta diferentes banco de dados através do uso dos adaptadores. Alguns exemplos de adaptadores são:

* PostgreSQL
* MySQL
* SQLite

Acessando a documentação você pode encontrar várias funções chamadas de __Callbacks__, sendo funções implementadas pelo adaptador: https://hexdocs.pm/ecto/Ecto.Repo.html#callbacks

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rkti0fjc2bp7fkziozf.png) 

## 🧍 Inserindo o Usuário no Banco de Dados <a name="inserindo-o-usuario-no-banco-de-dados"></a>

Vamos agora inserir um usuário no banco de dados utilizando o Modo Interativo do Elixir chamado __Interactive Elixir (iex)__. Execute o comando `iex -S mix` no terminal.

#### Definindo os parâmetros do usuário:

```elixir
iex> user_params = %{address: "Rua...", age: 28, cep: "12345678", cpf: "12345678910", email: "maiqui@teste.com", name: "Maiqui", password: "123456"}
%{
  address: "Rua...",
  age: 28,
  cep: "12345678",
  cpf: "12345678910",
  email: "maiqui@teste.com",
  name: "Maiqui",
  password: "123456"
}
```

#### Inserindo o usuário no banco:

Para inserirmos um usuário no banco vamos utilizar o callback `Rockelivery.Repo.insert/1`. Podemos ver um exemplo do que a função recebe como parâmetro e dos possíveis retornos:

```elixir
case MyRepo.insert %Post{title: "Ecto is great"} do
  {:ok, struct}       -> # Inserted with success
  {:error, changeset} -> # Something went wrong
end
```
Perceba que se der `:ok` o retorno é uma `struct`, senão o retorno é um `changeset`.

Este exemplo e mais detalhes você pode conferir na documentação:
https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert/2

Agora podemos então inserir o usuário no banco:

```elixir
iex> user_params |> Rockelivery.User.changeset() |> Rockelivery.Repo.insert()
[debug] QUERY OK db=69.1ms decode=8.7ms queue=22.3ms idle=1924.0ms
INSERT INTO "users" ("address","age","cep","cpf","email","name","password_hash","inserted_at","updated_at","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ["Rua...", 28, "12345678", "12345678910", "maiqui@teste.com", "Maiqui", "$argon2id$v=19$m=131072,t=8,p=4$L3mKYnhn5ooDZRsyd7maaA$92xStD9oCV06HtJYnoYhGY4HBJK69bUvb6HpTv+IlPc", ~N[2021-06-05 14:45:28], ~N[2021-06-05 14:45:28], <<72, 47, 149, 167, 180, 71, 66, 233, 174, 103, 174, 247, 41, 84, 195, 240>>]
{:ok,
 %Rockelivery.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   address: "Rua...",
   age: 28,
   cep: "12345678",
   cpf: "12345678910",
   email: "maiqui@teste.com",
   id: "482f95a7-b447-42e9-ae67-aef72954c3f0",
   inserted_at: ~N[2021-06-05 14:45:28],
   name: "Maiqui",
   password: "123456",
   password_hash: "$argon2id$v=19$m=131072,t=8,p=4$L3mKYnhn5ooDZRsyd7maaA$92xStD9oCV06HtJYnoYhGY4HBJK69bUvb6HpTv+IlPc",
   updated_at: ~N[2021-06-05 14:45:28]
 }}
```

## 🧍‍♀️ Buscando o Usuário no Banco de Dados <a name="buscando-o-usuario-no-banco-de-dados"></a>

Para pesquisar todos os usuários no banco, vamos utilizar o callback `Rockelivery.Repo.all/1`. A documentação desta função você encontra aqui: https://hexdocs.pm/ecto/Ecto.Repo.html#c:all/2

```elixir
iex> Rockelivery.Repo.all(Rockelivery.User)
[debug] QUERY OK source="users" db=3.6ms queue=1.7ms idle=895.2ms
SELECT u0."id", u0."address", u0."age", u0."cep", u0."cpf", u0."email", u0."name", u0."password_hash", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
[
  %Rockelivery.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    address: "Rua...",
    age: 28,
    cep: "12345678",
    cpf: "12345678910",
    email: "maiqui@teste.com",
    id: "482f95a7-b447-42e9-ae67-aef72954c3f0",
    inserted_at: ~N[2021-06-05 14:45:28],
    name: "Maiqui",
    password: nil,
    password_hash: "$argon2id$v=19$m=131072,t=8,p=4$L3mKYnhn5ooDZRsyd7maaA$92xStD9oCV06HtJYnoYhGY4HBJK69bUvb6HpTv+IlPc",
    updated_at: ~N[2021-06-05 14:45:28]
  }
]
```
#### Pesquisando no banco um usuário pelo seu ID:

Para pesquisar no banco o usuário pelo ID, vamos utilizar o callback `Rockelivery.Repo.get/2`. Vamos usar o ID da última pesquisa. A documentação desta função você encontra aqui: https://hexdocs.pm/ecto/Ecto.Repo.html#c:get/3

```elixir
iex> Rockelivery.Repo.get(Rockelivery.User, "482f95a7-b447-42e9-ae67-aef72954c3f0")
[debug] QUERY OK source="users" db=5.8ms queue=3.0ms idle=1422.9ms
SELECT u0."id", u0."address", u0."age", u0."cep", u0."cpf", u0."email", u0."name", u0."password_hash", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [<<72, 47, 149, 167, 180, 71, 66, 233, 174, 103, 174, 247, 41, 84, 195, 240>>]
%Rockelivery.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  address: "Rua...",
  age: 28,
  cep: "12345678",
  cpf: "12345678910",
  email: "maiqui@teste.com",
  id: "482f95a7-b447-42e9-ae67-aef72954c3f0",
  inserted_at: ~N[2021-06-05 14:45:28],
  name: "Maiqui",
  password: nil,
  password_hash: "$argon2id$v=19$m=131072,t=8,p=4$L3mKYnhn5ooDZRsyd7maaA$92xStD9oCV06HtJYnoYhGY4HBJK69bUvb6HpTv+IlPc",
  updated_at: ~N[2021-06-05 14:45:28]
}
```

## 🧍‍♂️ Módulo Users.Create <a name="modulo-users-create"></a>

Agora precisamos inserir o usuário de um jeito mais fácil, até porque quem estiver usando a nossa API irá passar apenas os parâmetros esperando a inserção deles. Vamos criar um módulo onde terá uma função que receberá esses parâmetros, realizará as validações e depois, se todos os dados estiverem ok, fará a inserção no banco de dados.

Vamos criar o arquivo `lib/rockelivery/users/create.ex` e o seu conteúdo terá o seguinte código:

```elixir
defmodule Rockelivery.Users.Create do
  alias Rockelivery.{User, Repo}
  # O alias acima é a mesma coisa que:
  # alias Rockelivery.User
  # alias Rockelivery.Repo

  def call(%{} = params) do
    params
    |> User.changeset()
    |> Repo.insert()
  end

  def call(_anything), do: "Enter the data in a map format"
end
```

Como parâmetro estamos usando esse Pattern Matching `%{} = params` estamos definindo que os dados devem ter o formato de `map`. Caso a função receba qualquer outra coisa, será devolvido uma mensagem que está no segundo escopo da função.

```elixir
iex> Rockelivery.Users.Create.call([])
"Enter the data in a map format"
```

A função sendo nomeada como __call__ faz parte do __Command Pattern__. É um padrão que nesse caso significa que estamos chamando (call) a ação de criação (Create). 

Vamos testar usando o __iex__. Se você não saiu do __iex__ na última sessão não esqueça de usar o comando `recompile` para a função existir para você. Vamos tentar criar um usuário com a mesma variável `user_params`:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cll5udhdkxxq3ihlej47.png)
Estamos vendo que o usuário não foi inserido devido a um erro acusado por uma __constraint__. Simplismente o __CPF__ já existe no banco. Mas, se alterarmos apenas o __CPF__, a __constraint do email__ também irá reclamar que o email já existe. Aqui conseguimos perceber melhor como uma __constraint__ funciona. Ela faz as validações no banco, diferente das __validations__ que validam as informações antes de ir para o banco de dados.

Então, precisamos alterar pelo menos as informações do __CPF__ e do __EMAIL__:

```elixir
iex> user_params = %{address: "Rua...", age: 28, cep: "12345678", cpf: "12345678911", email: "mikeshinoda@teste.com"
, name: "Mike Shinoda", password: "123456"}
```
Agora você pode inserir um novo usuário usando a função que criamos:

```elixir
iex> Rockelivery.Users.Create.call(user_params)
``` 

E, pode também, pesquisar os usuários para confirmar a nova inserção:
```elixir
iex> Rockelivery.Repo.all(Rockelivery.User)
```

## 🌐 Criando a Parte Web <a name="criando-a-parte-web"></a>

Agora podemos criar as nossas rotas, controllers e views. Criei um artigo explicando detalhadamente sobre isso e gostaria muito que você lesse antes de prosseguir nesse projeto: [O Ciclo de Vida do Request no Phoenix](https://dev.to/maiquitome/o-ciclo-de-vida-do-request-no-phoenix-53e7)

## 🛣️ Rotas <a name="rotas"></a>

Vamos precisar do CRUD completo (Create, Read, Update, Delete), então vamos ao arquivo de rotas:

Em `lib/rockelivery_web/router.ex`:

```elixir
scope "/api", RockeliveryWeb do
    pipe_through :api
    
    get "/users/", UsersController, :index
    post "/users/", UsersController, :create
    put "/users/:id", UsersController, :update
    get "/users/:id", UsersController, :show
    delete "/users/:id", UsersController, :delete
end
```

Podemos criar o código acima de uma maneira mais fácil, usando o comando `resources`:

```elixir
  scope "/api", RockeliveryWeb do
    pipe_through :api

    resources "/users", UsersController
  end
```

Para podermos entender o `resources` precisamos ter o controller criado.

Crie `lib/rockelivery_web/controllers/users_controller.ex`:

```elixir
defmodule RockeliveryWeb.UsersController do
  use RockeliveryWeb, :controller
end
```

Vamos agora verificar todas rodas disponíveis da nossa aplicação digitando o comando no terminal:

```bash
$ mix phx.routes
```

Podemos ver que o `resources` criou todas as rotas para nós.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1eyjl7mq8n9vlgi98nz0.png) 

Mas como não vamos fazer a criação e nem a alteração via front-end, não precisaremos das actions `:edit` e `:new`. Então podemos retirálas alterando o `resources`:

```elixir
  scope "/api", RockeliveryWeb do
    pipe_through :api

    resources "/users", UsersController, except: [:edit, :new]
  end
```

Você pode obter mais detalhes na documentação do Phoenix: https://hexdocs.pm/phoenix/routing.html#resources

## 🕹️ UsersController <a name="users-controller"></a>

Vamos criar agora a action do controller.

Em `lib/rockelivery_web/controllers/users_controller.ex`:

```elixir
defmodule RockeliveryWeb.UsersController do
  use RockeliveryWeb, :controller

  alias Rockelivery.Users.Create

  def create(_conn, params) do
    Create.call(params)
  end
end
```

### Facade Pattern

Existe uma maneira melhor para chamarmos a função `Create.call/1`. Existe um padrão chamado __Facade Pattern (Padrão da Fachada)__. O objetivo deste padrão é esconder a complexidade. Um exemplo disso, é o volante de um carro. Para virar à equerda você não precisa dizer: "gire a barra de ligação 20 graus no sentido anti-horário". Você apenas diz: "vire à esquerda aqui!" Há um artigo que fala mais sobre isso: [Saner apps with the Facade Pattern](https://dev.to/justgage/saner-apps-with-the-facade-pattern-4e29)

O nosso arquivo de fachada será `lib/rockelivery`:

```elixir
defmodule Rockelivery do
  alias Rockelivery.Users.Create, as: UserCreate

  defdelegate create_user(params), to: UserCreate, as: :call
end
```

Agora sempre que quisermos usar `Rockelivery.Users.Create.call/1` devemos usar na verdade `Rockelivery.create_user/1`. Então, precisamos refatorar o código do controller em `lib/rockelivery_web/controllers/users_controller.ex`:

```elixir
defmodule RockeliveryWeb.UsersController do
  use RockeliveryWeb, :controller

  def create(_conn, params) do
    Rockelivery.create_user(params)
  end
end
```

### Estrutura de Controle With

Agora precisamos tratar o caso de sucesso e o caso de erro da nossa action. Para isso, podemos utilizar a estrutura de controle __with__. Ela é uma ótima opção para quando temos vários casos de sucesso, um após o outro. 

Você pode encontrar exemplos do `with` aqui: https://elixirschool.com/pt/lessons/basics/control-structures/#with

Ou visite a documentação: https://hexdocs.pm/elixir/master/Kernel.SpecialForms.html#with/1

Vamos agora alterar o nosso código em `lib/rockelivery_web/controllers/users_controller.ex`:

```elixir
defmodule RockeliveryWeb.UsersController do
  use RockeliveryWeb, :controller

  alias Rockelivery.User

  def create(conn, params) do
    with {:ok, %User{} = user} <- Rockelivery.create_user(params) do
      conn
      # Plug.Conn.put_status(conn, HTTP Status Codes) retorna conn
      |> put_status(:created)
      # CASO O NOME DA VIEW SEJA DIFERENTE DO CONTROLLER,
      # VOCÊ DEVE USAR: Phoenix.Controller.put_view(NomeView)

      # Phoenix.Controller.render(conn, template, assigns) retorna conn
      |> render("create.json", user: user)
    end
  end
end
```

O `assigns` está sendo passado como __Keyword Argument__. Em geral, quando um __keyword list__ é o último argumento de uma função, os colchetes são opcionais.

Lembrando que o nome da view deve ter o mesmo nome do controller __users__, caso contrário, deveríamos ter que especificar qual a view seria usada para [renderização](https://www.dicio.com.br/renderizar/) usando a função `Phoenix.Controller.put_view/2` e passar como argumento o nome do módulo da view. Você pode ver mais detalhes desta função na documentação: https://hexdocs.pm/phoenix/Phoenix.Controller.html#put_view/2

Se `conn`, `params`, `render`, `template`, `assigns` não está tão claro pra você recomendo novamente que você leia o artigo: [O Ciclo de Vida do Request no Phoenix](https://dev.to/maiquitome/o-ciclo-de-vida-do-request-no-phoenix-53e7)

Confira todos os __HTTP Status Codes__ aqui: https://httpstatuses.com/

Confira todos os atoms que represam os __HTTP Status Codes__ aqui:
https://hexdocs.pm/plug/Plug.Conn.Status.html

## 😎 UsersView <a name="users-view"></a>

Vamos criar agora o arquivo da view.

Em `rockelivery_web/views/users_view.ex`:

```elixir
defmodule RockeliveryWeb.UsersView do
  use RockeliveryWeb, :view

  alias Rockelivery.User

  def render("create.json", %{user: %User{} = user}) do
    %{
      message: "User created!",
      user: %{
        id: user.id,
        name: user.name
      }
    }
  end
end
```

## 🟣 Usando o Insomnia <a name="usando-o-insomnia"></a>

Como no navegador só conseguimos fazer requisição GET, para podermos testar a requisição POST precisamos utilizar um destes softwares: __Insomnia__, __Postman__, __HTTPie__ e __Curl__. Aqui usaremos o __Insomnia__.

Por questão de organização, podemos criar uma __Request Collection__, assim todas as requests do projeto __Rockelivery__ ficaram guardadas em um local separado de outros projetos.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmf8s9r5cwtytoqowc6p.png)![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n2nhdelvr1noe997zibz.png)
 
Podemos também criar uma variável para a base de todas as nossas requests:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wbrja40dj2dreq98zdfo.png)![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4b5vkghyz00swspf5kr8.png) 

Vamos agora testar a criação de um usuário. Lembre de executar o servidor da aplicação com o comando `mix phx.server` no terminal.
```json
// crie um usuário com o formato json
{
	"address": "Rua dos Monstros S.A.",
	"age": 20,
	"cep": "01020304",
	"cpf": "12345678912",
	"email": "mike_wazowski@monsterssa.com",
	"name": "Mike Wazowski",
	"password": "987654321"
}
```
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19rhtq4o7a2j1drd82yc.png)![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyhz7n98cd5bmuuk6b7u.png) 
 
Vamos refatorar a nossa view. Desta vez, vamos usar a lib que já vem com o Phoenix chamada `jason`.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pjcc2yvwsifhpcuyg5m3.png)

Em `rockelivery_web/views/users_view`:

```elixir
defmodule RockeliveryWeb.UsersView do
  use RockeliveryWeb, :view

  alias Rockelivery.User

  def render("create.json", %{user: %User{} = user}) do
    %{
      message: "User created!",
      # altere o código abaixo
      user: user
    }
  end
end
```

Para que a struct `%User{}` seja convertida em __JSON__ precisamos alterar o nosso arquivo de schema.

Em `lib/rockelivery/user.ex` adicione a linha abaixo:

```elixir
@derive {Jason.Encoder, only: [:id, :name, :email]}
```

E mais um Mayk é adicionado com sucesso :)
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uygpbkzmenjmggn7nsoj.png) 

## ❌ FallbackController <a name="fallback-controller"></a>

Quando um erro acontece em uma action e ele não é tratado por ninguém, esse erro passa a ser tratado pela `action_fallback`. Você pode obter mais detalhes na documentação: https://hexdocs.pm/phoenix/Phoenix.Controller.html#action_fallback/1

#### Adicionando o FallbackController ao UsersController.

Em `lib/rockelivery_web/controllers/users_controller.ex`:

```elixir
defmodule RockeliveryWeb.UsersController do
  use RockeliveryWeb, :controller

  alias Rockelivery.User
  # adicione
  alias RockeliveryWeb.FallbackController

  # adicione
  action_fallback FallbackController

  def create(conn, params) do
    with {:ok, %User{} = user} <- Rockelivery.create_user(params) do
      conn
      |> put_status(:created)
      |> render("create.json", user: user)
    end
  end
end
```

#### Criando o FallbackController

O `FallbackController` sempre irá executar uma função chamada `call/2`, então é ela que vamos precisar criar agora.

Crie o arquivo em `lib/rockelivery_web/controllers/fallback_controller.ex`:

```elixir
defmodule RockeliveryWeb.FallbackController do
  use RockeliveryWeb, :controller

  alias RockeliveryWeb.ErrorView

  def call(conn, {:error, changeset}) do
    conn
    |> put_status(:bad_request)
    |> put_view(ErrorView)
    |> render("400.json", result: changeset)
  end
end
```

Se tentássemos criar o mesmo usuário de antes, o erro __sem__ o `FallbackController` seria esse:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f16ga4jc9j7sq3e9jr3p.png)

Após implementarmos o `FallbackController`, o erro aparece assim:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/99i0d28ip7aq2mfo3ydy.png)

#### Alterando a ErrorView

Em `lib/rockelivery_web/views/error_view.ex`:

```elixir
defmodule RockeliveryWeb.ErrorView do
  use RockeliveryWeb, :view

  def template_not_found(template, _assigns) do
    %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
  end

  # adicione
  def render("400.json", _assigns) do
    %{errors: %{message: "Error :("}}
  end
end
```
Vamos agora ver a nova mensagem:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gfsa27ysb5jfgyveroa.png)

O que queremos mesmo é enviar os erros do `changeset`. Então vamos alterar novamente o `ErrorView`:

```elixir
defmodule RockeliveryWeb.ErrorView do
  use RockeliveryWeb, :view

  import Ecto.Changeset, only: [traverse_errors: 2]

  alias Ecto.Changeset

  def template_not_found(template, _assigns) do
    %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
  end

  def render("400.json", %{result: %Changeset{} = changeset}) do
    %{message: translate_errors(changeset)}
  end

  def translate_errors(%Changeset{} = changeset) do
    traverse_errors(changeset, fn {msg, opts} ->
      Enum.reduce(opts, msg, fn {key, value}, acc ->
        String.replace(acc, "%{#{key}}", to_string(value))
      end)
    end)
  end
end
```
#### Entendendo de onde está vindo o changeset

Vamos enteder todo esse código. Primeiramente, você pode estar se perguntando da onde está vindo o `changeset`. Se formos ao terminal no momento do erro, podemos ver o changeset:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh7n7jefinyag4mlhoau.png)

Você se lembra desse exemplo da documentação que vimos quando estávamos falando sobre [🧍 Inserindo o Usuário no Banco de Dados](#inserindo-o-usuario-no-banco-de-dados)?

```elixir
case MyRepo.insert %Post{title: "Ecto is great"} do
  {:ok, struct}       -> # Inserted with success
  {:error, changeset} -> # Something went wrong
end
```

Então, o `Changeset` é retornado em uma `tupla` quando acontece algum erro. Como esse erro não é tratado por ninguém, ele passa a ser responsabilidade do `FallbackController`. Dentro do módulo de `FallbackController` o `changeset` é colocado dentro da váriavel `result` em um __Keyword Argument__ que é passado para o módulo `ErrorView`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tslzxlkq9ocfnbspa2pv.png)

Lembrando que quando um __keyword list__ é o último argumento de uma função, os colchetes são opcionais.

#### Entendendo o traverse_errors

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ckyho6pflmmrvks0sz78.png)
Primeiramente, você não precisa decorar esse código, você encontra ele na documentação: https://hexdocs.pm/ecto/Ecto.Changeset.html#traverse_errors/2

A `traverse_errors/2` serve para capturar as mensagens de erro de um `changeset`. 

Se comentarmos a linha do `import` vamos ver um erro no nosso código:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pykh5bj0xbxtye15826s.png) 
Nessa mesma linha do `import`, estamos dizendo com o `only` que queremos importat apenas  a função `traverse_errors` que tem `aridade 2`, ou seja, a função `traverse_errors` espera 2 parâmetros. E é obrigatório informar a `aridade`.

#### Testando as mensagens de erro do changeset

O __CPF__ já existe:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8fi6yodpy6rua4gczhze.png) 

O __EMAIL__ já existe:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5uod4mb4p9hs06ivorzi.png)

__Validações__ antes de enviar os dados para o banco, antes das __Constraints__:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2mia2ef6vp83ajpjfs0h.png)

### Múltiplos Status de Erro

Em `lib/rockelivery/users/create.ex`:

```elixir
defmodule Rockelivery.Users.Create do
  alias Rockelivery.{User, Repo}

  def call(%{} = params) do
    params
    |> User.changeset()
    |> Repo.insert()
    # adicione
    |> handle_insert()
  end

  def call(_anything), do: "Enter the data in a map format"

  # adicione
  defp handle_insert({:ok, %User{}} = result), do: result

  # adicione
  defp handle_insert({:error, changeset}) do
    {:error, %{status: :bad_request, result: changeset}}
  end
end
``` 

Em `lib/rockelivery_web/controllers/fallback_controller.ex`:

```elixir
defmodule RockeliveryWeb.FallbackController do
  use RockeliveryWeb, :controller

  alias RockeliveryWeb.ErrorView

  # altere {:error, changeset} para {:error, %{status: status, result: changeset}}
  def call(conn, {:error, %{status: status, result: changeset}}) do
    conn
    # altere :bad_request para status
    |> put_status(status)
    |> put_view(ErrorView)
    # altere "400.json" para "error.json"
    |> render("error.json", result: changeset)
  end
end
```

Em `lib/rockelivery_web/views/error_view.ex`:

```elixir
defmodule RockeliveryWeb.ErrorView do
  use RockeliveryWeb, :view

  import Ecto.Changeset, only: [traverse_errors: 2]

  alias Ecto.Changeset

  def template_not_found(template, _assigns) do
    %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
  end

  # altere "400.json" para "error.json"
  def render("error.json", %{result: %Changeset{} = changeset}) do
    %{message: translate_errors(changeset)}
  end

  def translate_errors(%Changeset{} = changeset) do
    traverse_errors(changeset, fn {msg, opts} ->
      Enum.reduce(opts, msg, fn {key, value}, acc ->
        String.replace(acc, "%{#{key}}", to_string(value))
      end)
    end)
  end
end
```
### Testando As Mudanças

Vamos trocar __:bad_request__ para __:internal_server_error__.

Em `lib/rockelivery/users/create.ex`:

```elixir
defmodule Rockelivery.Users.Create do
  alias Rockelivery.{User, Repo}

  def call(%{} = params) do
    params
    |> User.changeset()
    |> Repo.insert()
    # adicione
    |> handle_insert()
  end

  def call(_anything), do: "Enter the data in a map format"

  # adicione
  defp handle_insert({:ok, %User{}} = result), do: result

  # adicione
  defp handle_insert({:error, changeset}) do
    {:error, %{status: :internal_server_error, result: changeset}}
  end
end
``` 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t4vek5p5lgxzhrsw1qun.png)
Agora você pode colocar devolta o __:bad_request__ :)

Vamos tentar adicionar novamente um usuário para verificar se tudo está funcionando. Se você quiser pode usar os dados abaixo para adicionar outro Mike heheh :)

```json
{
	"address": "Rua dos Lutadores",
	"age": 55,
	"cep": "01020301",
	"cpf": "12345678920",
	"email": "mike_tyson@pugilista.com",
	"name": "Mike Tyson",
	"password": "987654321"
}
```
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dc50gyfifb177g2b5im3.png)

Agora você já pode ir para a parte 3 :)