💧🍔 Projeto Rockelivery: API para Pedidos em um Restaurante com Elixir e Phoenix (Parte 2)

maiquitome

Dev Maiqui 🇧🇷

Posted on June 7, 2021

💧🍔 Projeto Rockelivery: API para Pedidos em um Restaurante com Elixir e Phoenix (Parte 2)

Essa é a segunda parte do projeto Rockelivery. Esse projeto faz parte do Bootcamp da Rocketseat, ministrado pelo professor Rafael Camarda.

Conteúdo:

A Jornada do Autodidata em Inglês

🕵🏻‍♂️ Conhecendo o Módulo Ecto.Repo

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:

defmodule Rockelivery.Repo do
  use Ecto.Repo,
    otp_app: :rockelivery,
    adapter: Ecto.Adapters.Postgres
end
Enter fullscreen mode Exit fullscreen mode

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

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]
Enter fullscreen mode Exit fullscreen mode

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

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

🧍 Inserindo o Usuário no Banco de Dados

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:

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"
}
Enter fullscreen mode Exit fullscreen mode

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:

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

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:

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]
 }}
Enter fullscreen mode Exit fullscreen mode

🧍‍♀️ Buscando o Usuário no Banco de Dados

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

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]
  }
]
Enter fullscreen mode Exit fullscreen mode

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

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]
}
Enter fullscreen mode Exit fullscreen mode

🧍‍♂️ Módulo Users.Create

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:

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
Enter fullscreen mode Exit fullscreen mode

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.

iex> Rockelivery.Users.Create.call([])
"Enter the data in a map format"
Enter fullscreen mode Exit fullscreen mode

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
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:

iex> user_params = %{address: "Rua...", age: 28, cep: "12345678", cpf: "12345678911", email: "mikeshinoda@teste.com"
, name: "Mike Shinoda", password: "123456"}
Enter fullscreen mode Exit fullscreen mode

Agora você pode inserir um novo usuário usando a função que criamos:

iex> Rockelivery.Users.Create.call(user_params)
Enter fullscreen mode Exit fullscreen mode

E, pode também, pesquisar os usuários para confirmar a nova inserção:

iex> Rockelivery.Repo.all(Rockelivery.User)
Enter fullscreen mode Exit fullscreen mode

🌐 Criando a Parte Web

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

🛣️ Rotas

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

Em lib/rockelivery_web/router.ex:

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
Enter fullscreen mode Exit fullscreen mode

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

  scope "/api", RockeliveryWeb do
    pipe_through :api

    resources "/users", UsersController
  end
Enter fullscreen mode Exit fullscreen mode

Para podermos entender o resources precisamos ter o controller criado.

Crie lib/rockelivery_web/controllers/users_controller.ex:

defmodule RockeliveryWeb.UsersController do
  use RockeliveryWeb, :controller
end
Enter fullscreen mode Exit fullscreen mode

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

$ mix phx.routes
Enter fullscreen mode Exit fullscreen mode

Podemos ver que o resources criou todas as rotas para nós.
image

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:

  scope "/api", RockeliveryWeb do
    pipe_through :api

    resources "/users", UsersController, except: [:edit, :new]
  end
Enter fullscreen mode Exit fullscreen mode

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

🕹️ UsersController

Vamos criar agora a action do controller.

Em lib/rockelivery_web/controllers/users_controller.ex:

defmodule RockeliveryWeb.UsersController do
  use RockeliveryWeb, :controller

  alias Rockelivery.Users.Create

  def create(_conn, params) do
    Create.call(params)
  end
end
Enter fullscreen mode Exit fullscreen mode

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

O nosso arquivo de fachada será lib/rockelivery:

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

  defdelegate create_user(params), to: UserCreate, as: :call
end
Enter fullscreen mode Exit fullscreen mode

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:

defmodule RockeliveryWeb.UsersController do
  use RockeliveryWeb, :controller

  def create(_conn, params) do
    Rockelivery.create_user(params)
  end
end
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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 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

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

Vamos criar agora o arquivo da view.

Em rockelivery_web/views/users_view.ex:

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
Enter fullscreen mode Exit fullscreen mode

🟣 Usando o Insomnia

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.
imageimage

Podemos também criar uma variável para a base de todas as nossas requests:
imageimage

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.

// 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"
}
Enter fullscreen mode Exit fullscreen mode

imageimage

Vamos refatorar a nossa view. Desta vez, vamos usar a lib que já vem com o Phoenix chamada jason.
image

Em rockelivery_web/views/users_view:

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
Enter fullscreen mode Exit fullscreen mode

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:

@derive {Jason.Encoder, only: [:id, :name, :email]}
Enter fullscreen mode Exit fullscreen mode

E mais um Mayk é adicionado com sucesso :)
image

❌ FallbackController

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

Se tentássemos criar o mesmo usuário de antes, o erro sem o FallbackController seria esse:
image

Após implementarmos o FallbackController, o erro aparece assim:
image

Alterando a ErrorView

Em lib/rockelivery_web/views/error_view.ex:

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
Enter fullscreen mode Exit fullscreen mode

Vamos agora ver a nova mensagem:
image

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

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
Enter fullscreen mode Exit fullscreen mode

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

Você se lembra desse exemplo da documentação que vimos quando estávamos falando sobre 🧍 Inserindo o Usuário no Banco de Dados?

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

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

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

Entendendo o traverse_errors

image
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
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

O EMAIL já existe:
image

Validações antes de enviar os dados para o banco, antes das Constraints:
image

Múltiplos Status de Erro

Em lib/rockelivery/users/create.ex:

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
Enter fullscreen mode Exit fullscreen mode

Em lib/rockelivery_web/controllers/fallback_controller.ex:

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
Enter fullscreen mode Exit fullscreen mode

Em lib/rockelivery_web/views/error_view.ex:

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
Enter fullscreen mode Exit fullscreen mode

Testando As Mudanças

Vamos trocar :bad_request para :internal_server_error.

Em lib/rockelivery/users/create.ex:

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
Enter fullscreen mode Exit fullscreen mode

image
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 :)

{
    "address": "Rua dos Lutadores",
    "age": 55,
    "cep": "01020301",
    "cpf": "12345678920",
    "email": "mike_tyson@pugilista.com",
    "name": "Mike Tyson",
    "password": "987654321"
}
Enter fullscreen mode Exit fullscreen mode

image

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

💖 💪 🙅 🚩
maiquitome
Dev Maiqui 🇧🇷

Posted on June 7, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related