💧🍔 Projeto Rockelivery: API para Pedidos em um Restaurante com Elixir e Phoenix (Parte 1)
Tags: elixir, phoenix, ecto, braziliandevs
Published: 2021-06-04 23:22:12

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

Eu usei também alguns trechos da documentação oficial do __Ecto__ e algumas explicações do site __Elixir School__. Existe um guia bem bacana sobre __Ecto__ e em português que você pode estudar também: https://elixirschool.com/pt/lessons/ecto/basics/

Para você poder acompanhar a construção deste projeto é preciso ter o conhecimento básico da Linguagem Elixir :)

### Conteúdo:
* [📚 O que vamos aprender?](#o-que-vamos-aprender?)
* [❔ Como vai ser o projeto?](#como-vai-ser-o-projeto?)
* [🔧 Setup inicial do projeto](#setup-inicial-do-projeto)
* [👦 Criando a Migração e a Tabela do Usuário](#criando-a-migracao-e-a-tabela-do-usuario)
 - [Alterando ID inteiro para UUID](#alterando-id-inteiro-para-uuid)
* [🧑🏻 Criando o Schema do User](#criando-o-schema-do-user)
* [✔️ Usando Changesets](#usando-changesets)
 - [Entendendo mais sobre casting (conversão de tipos)](#entendendo-mais-sobre-casting)
 - [Changeset Válido](#changeset-valido)
 - [Encriptando a Senha](#encriptando-a-senha)


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


## 📚 O que vamos aprender? <a name="o-que-vamos-aprender?"></a>
- Instalar dependências
- Criar rotas (CRUD completo)
 - create
 - read
 - update
 - delete
- Criar Plugs
- Entender o Ecto
 - interagir com bancos de dados
 - migration
 - schema
 - changeset
- Testar uma aplicação Phoenix

## ❔ Como vai ser o projeto? <a name="como-vai-ser-o-projeto?"></a>

- Usuário pode se cadastrar e gerenciar a conta dele
- Items do restaurante podem ser cadastrados
- Usuário pode realizar pedidos
 - Para realizar pedidos, o usuário tem que se logar
 - O CEP do usuário deve ser válido, então vamos usar uma API para validar o CEP e aprender como realizar chamadas HTTP
- Semanalmente, um relatório de pedidos por usuário deve ser gerado, para isso vamos usar `GenServers`.

#### As tabelas do banco de dados serão essas:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xrhja5o0k4dwnrybiezf.png) 

## 🔧 Setup inicial do projeto <a name="setup-inicial-do-projeto"></a>

Se você ainda __não__ tem o Elixir, Phoenix e o PostgreSQL instalados, você pode acessar outro artigo meu onde explico sobre a instalação deles: [Instalação das ferramentas de uma pessoa desenvolvedora Elixir](https://dev.to/maiquitome/instalacao-das-ferramentas-de-um-desenvolvedor-elixir-1bjm)

#### Observação antes de criar o projeto:
Se você está usando uma versão do Phoenix __anterior__ a versão 1.6 use `--no-webpack` ao invés de `--no-assets`

#### Comando para criar o projeto (versão 1.6 e posterior):
```bash
$ mix phx.new rockelivery --no-html --no-assets
```
 
#### Comando para criar o projeto (versões anteriores):
```bash
$ mix phx.new rockelivery --no-html --no-webpack
```
Após esse comando, ele nos perguntará se queremos instalar as dependências. Vamos dizer que sim digitando `y` e apertando a tecla `ENTER`.

```bash
Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix deps.compile
...
```
Por que temos que usar as opções `--no-webpack ou --no-assets` e `--no-html`?
 - Porque __não__ vamos fazer front-end (HTML, CSS, JavaScript);
 - E, porque vamos construir apenas uma API JSON.

Se fossemos usar só para front-end usaríamos a opção `--no-ecto`, assim as configurações e arquivos para banco de dados não seriam gerados. Você pode encontrar outras opções aqui: https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html

Entre na pasta do projeto com o comando:
```bash
$ cd rockelivery
```

## 👦 Criando a Migração e a Tabela do Usuário <a name="criando-a-migracao-e-a-tabela-do-usuario"></a>

Para criar e modificar tabelas no banco de dados, utilizamos as migrações do __Ecto__. Cada migração descreve uma série de ações para serem realizadas no nosso banco, como quais tabelas criar ou atualizar.

Você pode estar se perguntando agora, mas o que é esse tal de __Ecto__? Pois bem, vou te responder agora, o __Ecto__ é um projeto oficial do Elixir que fornece uma camada de banco de dados e linguagem integrada para consultas. Com o __Ecto__ podemos criar migrações, definir esquemas, inserir e atualizar registros, e fazer consultas no banco de dados.

A convenção no __Ecto__ é pluralizar o nome das tabelas, portanto, vamos nomear a nossa tabela como `users`. Para criar a migração vamos rodar o comando abaixo:

```bash
$ mix ecto.gen.migration create_users_table
```

Esse comando irá gerar um novo arquivo na pasta `priv/repo/migrations` contendo uma `timestamp` no nome. Se navegarmos para esse diretório e abrirmos a migração, veremos algo assim:

Em `priv/repo/migrations/"número-da-timestamp"_create_users_table.exs`:

```elixir
defmodule Rockelivery.Repo.Migrations.CreateUsersTable do
  use Ecto.Migration

  def change do

  end
end
```
A `timestamp` é importante para o __Ecto__ saber em qual ordem ele precisa criar as tabelas.

Vamos criar a nossa tabela dentro da função `change`. Estar dentro da função `change` é importante para o __Ecto__ criar a tabela quando rodarmos o comando de `create` ou desfazer automaticamente o que criamos caso rodemos o comando de `rollback`. 

Existem também as funções `up/0` e `down/0` onde especificamos com detalhes o que queremos criar no `up/0` e o que queremos desfazer no `down/0`. Mas ter que escrever as funções `up/0` e `down/0` para cada migração é entediante e sujeito a erros. Por curiosidade, vou deixar o link da documentação explicando mais sobre, mas por aqui vamos deixar tudo dentro do `change` mesmo.

`up e down`: https://hexdocs.pm/ecto_sql/Ecto.Migration.html

Vamos criar a nossa tabela __User__ com os campos abaixo:
- ID
- address
- age
- cep
- CPF
- email
- name
- password

Em `priv/repo/migrations/número-da-timestamp_create_users_table.exs`:

```elixir
defmodule Rockelivery.Repo.Migrations.CreateUsersTable do
  use Ecto.Migration

  def change do
    # A convenção no `Ecto` é pluralizar o nome das tabelas,
    # portanto, vamos nomear a nossa tabela como :users.
    create table :users do
      # o id é gerado automaticamente então não precisamos criar
      add :address,        :string
      add :age,            :integer
      add :cep,            :string
      add :cpf,            :string
      add :email,          :string
      add :name,           :string
      add :password_hash,  :string
      # Precisamos do password nomeado como password_hash 
      # pois vamos criptografar a senha do usuário...

      # essa função de timestamps gera automaticamente os campos
      # inserted_at para guardar a hora de criação do registro
      # e updated_at para guardar a hora da atualização do registro
      timestamps()
    end

    # O índice unique não deixará o cpf do usuário se repetir
    create unique_index(:users, [:cpf])
    # E também não deixará o email do usuário se repetir,
    create unique_index(:users, [:email])
    # ou seja, esses dois campos serão únicos
    

    # Por curiosidade, existe também esse formato:
    # create index("users", [:email], unique: true)
  end
end
```

### Alterando ID inteiro para UUID <a name="alterando-id-inteiro-para-uuid">

Em aplicações Phoenix o __ID__ é gerado automaticamente por padrão como inteiro, mas podemos modificar para que seja gerado automaticamente no formato __UUID4__. 

O que é um __UUID__?

Um identificador único universal (do inglês universally unique identifier - UUID) é um número de 128 bits usado para identificar informações em sistemas de computação e dificilmente um __UUID__ se repete.

__Exemplo de UUID4__: `297b4329-2982-4e6d-b2b1-6cf4b0c7a351 `

__Gerador de UUID__: https://www.uuidgenerator.net/

Vamos colocar as configurações no arquivo `config/config.exs`
```elixir
# abaixo desse código:
config :rockelivery,
  ecto_repos: [Rockelivery.Repo]

# vamos colocar esse código:
config :rockelivery, Rockelivery.Repo,
  migration_primary_key: [type: :binary_id],
  migration_foreign_key: [type: :binary_id]
```
- `:rockelivery` é o nome da nossa app
- `Rockelivery.Repo` é o modulo que conversa com o banco de dados
- `migration_primary_key` é a chave primaria
- `migration_foreign_key` é a chave estrangeira
- `:binary_id` é um UUID na versão 4

__DICA:__ Podemos usar a opção `--binary-id` junto do comando que criamos o projeto. Desta forma, ele irá gerar automaticamente o código para configurar os __IDs__ como __UUID__.

#### Agora vamos criar a nossa tabela no banco de dados rodando o comando:
```bash
$ mix ecto.migrate
```
Após, você deverá ver algo semelhante no terminal:
```bash
18:35:08.572 [info]  == Running 20210525185314 Rockelivery.Repo.Migrations.CreateUsersTable.change/0 forward

18:35:08.575 [info]  create table users

18:35:08.583 [info]  create index users_cpf_index

18:35:08.585 [info]  create index users_email_index

18:35:08.589 [info]  == Migrated 20210525185314 in 0.0s
```
Podemos verificar no `pgAdmim` que a tabela foi criada com sucesso:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/90lvh6pg1xc5qcbzmceh.png)

## 🧑🏻 Criando o Schema do User <a name="criando-o-schema-do-user"></a>

Um esquema é um módulo que define um mapeando dos campos de uma tabela.

Enquanto nas tabelas utilizamos o plural, no esquema tipicamente se utiliza o singular. Então, criaremos um esquema `User` para a nossa tabela.

No diretório das regras de negócio `lib/rockelivery`, vamos criar o arquivo `user.ex`, e o código ficará assim:
```elixir
defmodule Rockelivery.User do
  use Ecto.Schema

  # Como alteramos o tipo da chave primária,
  # precisamos configurar ela aqui,
  # usando o atributo de esquema @primary_key
  # podemos informar que temos um campo :id,
  # do tipo UUID (:binary_id)
  # e que deve ser gerado automaticamente.
  @primary_key {:id, :binary_id, autogenerate: true}

  # "users" no plural é nome da tabela
  schema "users" do
    field :address, :string
    field :age, :integer
    field :cep, :string
    field :cpf, :string
    field :email, :string
    field :name, :string
    field :password_hash, :string

    timestamps()
  end
end
```
`@primary_key` - configura a chave primária do esquema. Ele espera uma tupla `{field_name, type, options}` com o nome do campo da chave primária, tipo (normalmente :id ou :binary_id, mas pode ser qualquer tipo) e opções. Leia mais sobre `atributos de esquemas`: https://hexdocs.pm/ecto/Ecto.Schema.html#module-schema-attributes

Agora que já temos o nosso esquema configurado, podemos perceber que um `esquema (schema)` nada mais é do que uma `struct com metadados` e, de maneira similar, podemos atualizar nossos esquemas como poderíamos fazer com qualquer outro `map` ou `struct` em Elixir:

Executando `iex -S mix` no terminal podemos fazer os testes:
```elixir
iex> user = %Rockelivery.User{}
%Rockelivery.User{
  __meta__: #Ecto.Schema.Metadata<:built, "users">,
  address: nil,
  age: nil,
  cep: nil,
  cpf: nil,
  email: nil,
  id: nil,
  inserted_at: nil,
  name: nil,
  password_hash: nil,
  updated_at: nil
}

iex> user = %{user | age: 28}
%Rockelivery.User{
  __meta__: #Ecto.Schema.Metadata<:built, "users">,
  address: nil,
  age: 28,
  cep: nil,
  cpf: nil,
  email: nil,
  id: nil,
  inserted_at: nil,
  name: nil,
  password_hash: nil,
  updated_at: nil
}

iex> user.age
28
```

__DICA:__ Podemos criar o arquivo de migração juntamente com o do esquema executando o gerador do Phoenix: 

```bash
$ mix phx.gen.schema User users address age:integer cep CPF email name password_hash
```
Você pode ver mais detalhes na documentação: https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Schema.html#content

## ✔️ Usando Changesets <a name="usando-changesets"></a>

Existe um maneira melhor para alterarmos os dados em um __schema__. Usando um __Changeset (Conjunto de Mudanças)__ conseguimos alterar os dados fazendo validações. Você pode obter mais detalhes visitando a documentação: https://hexdocs.pm/ecto/Ecto.Changeset.html#module-the-ecto-changeset-struct

Em `lib/rockelivery/user.ex`:

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

  # variáveis de módulo (Module Attributes) começam com @
  # essas variáveis só podem ser usadas dentro do módulo

  # essa variável guarda os campos que são permitidos
  # para serem alterados  
  @fields_that_can_be_changed [
    :address,
    :age,
    :cep,
    :cpf,
    :email,
    :name,
    :password_hash
  ]

  # essa variável guarda os campos obrigatórios
  # que devem ser preenchidos
  @required_fields [
    :address,
    :age,
    :cep,
    :cpf,
    :email,
    :name,
    :password_hash
  ]

  @primary_key {:id, :binary_id, autogenerate: true}

  schema "users" do
    field :address, :string
    field :age, :integer
    field :cep, :string
    field :cpf, :string
    field :email, :string
    field :name, :string
    field :password_hash, :string

    timestamps()
  end

  # Nesta função changeset vamos criar um changeset com a função cast
  # e validar os campos obrigatórios com validate_required
  def changeset(%{} = params) do
    # %__MODULE__{} é igual a %Rockelivery.User{} que é o nosso Schema
    %__MODULE__{}
    # Ecto.Changeset.cast(Schema, %{} = params, [] = lista_de_campos_permitidos_a_serem_alterados)
    # O cast retorna o changeset
    |> cast(params, @fields_that_can_be_changed)
    # Ecto.Changeset.validate_required(changeset, [] = campos_que_devem_ser_preenchidos)
    |> validate_required(@required_fields)
  end
end
```

Enquanto a função `Ecto.Changeset.cast` é usada para trabalhar com dados externos, existe a função `Ecto.Changeset.change` usada para trabalhar com dados internos da aplicação.

Elas são semelhantes, mas a função `Ecto.Changeset.change` é útil para alterar diretamente uma `struct` sem realizar __castings (conversão de tipos)__ ou __validações__. Ao contrário da função `cast`, na `change` não temos a possibilidade de __não permitir__ que certo dado seja alterado.

#### Entendendo mais sobre casting (conversão de tipos): <a name="entendendo-mais-sobre-casting"></a>

Nós definimos que o campo `age` é do tipo `inteiro` mas ao usar a função `change` no lugar do `cast` isso é ignorado:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/67004czkb83ek8c8iab5.png)

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

Usando a função `cast`, ela só aceitará um tipo inteiro:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t9za1gz7mht1osfeb5od.png)

Se colocarmos um __número como string__ ela fará a __conversão (casting)__ para __inteiro__ automaticamente:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jaya75y0hz2hn231eh8m.png) 

Veja mais sobre a função __change__: https://hexdocs.pm/ecto/Ecto.Changeset.html#change/2 

Veja mais sobre a função __cast__:
https://hexdocs.pm/ecto/Ecto.Changeset.html#cast/4

#### Changeset Válido <a name="changeset-valido"></a>

Agora vamos preencher todos os campos para deixar o changeset totalmente válido:

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

#### Validações e Restrições

Os __changesets__ do __Ecto__ fornecem __validações__ e __restrições (constraints)__ que acabam se transformando em erros caso algo dê errado.

A diferença entre elas é que a maioria das __validações__ pode ser executada sem a necessidade de interagir com o banco de dados e, portanto, são sempre executados antes de tentar inserir ou atualizar a entrada no banco de dados. Algumas __validações__ podem acontecer no banco de dados, mas são inerentemente inseguras. Essas __validações__ começam com um prefixo `unsafe_`, como `unsafe_validate_unique/3`.

Por outro lado, as __constraints__ dependem do banco de dados e estão sempre seguras. Como consequência, as __validações__ são sempre verificadas antes das __constraints__. As __constraints__ nem mesmo serão verificadas em caso de falha nas __validações__.

Aqui você pode encontrar várias funções para validação:
https://hexdocs.pm/ecto/Ecto.Changeset.html#module-external-vs-internal-data

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

Vamos adicionar novas __validações__ para o nosso __changeset__.

Em `lib/rockelivery/user.ex`:

```elixir
def changeset(%{} = params) do
    %__MODULE__{}
    |> cast(params, @fields_that_can_be_changed)
    |> validate_required(@required_fields)
    |> validate_length(:password_hash, min: 6)
    |> validate_length(:cep, is: 8)
    |> validate_length(:cpf, is: 11)
    # idade deve ser maior ou igual a 18
    |> validate_number(:age, greater_than_or_equal_to: 18)
    # email deve conter um caractere @
    |> validate_format(:email, ~r/@/)
    |> unique_constraint(:email)
    |> unique_constraint(:cpf)
end
```

Executando `iex -S mix` no terminal, podemos verificar as validações:

```elixir
iex> changeset = Rockelivery.User.changeset(%{age: "1", address: "rua..", cep: "989", cpf: "212", email: "email", name: "teste", password_hash: "123"})
#Ecto.Changeset<
  action: nil,
  changes: %{
    address: "rua..",
    age: 1,
    cep: "989",
    cpf: "212",
    email: "email",
    name: "teste",
    password_hash: "123"
  },
  errors: [
    email: {"has invalid format", [validation: :format]},
    age: {"must be greater than or equal to %{number}",
     [validation: :number, kind: :greater_than_or_equal_to, number: 18]},
    cpf: {"should be %{count} character(s)",
     [count: 11, validation: :length, kind: :is, type: :string]},
    cep: {"should be %{count} character(s)",
     [count: 8, validation: :length, kind: :is, type: :string]},
    password_hash: {"should be at least %{count} character(s)",
     [count: 6, validation: :length, kind: :min, type: :string]}
  ],
  data: #Rockelivery.User<>,
  valid?: false
>
```

#### Encriptando a Senha <a name="encriptando-a-senha"></a>

Primeiramente precisaremos ter a bibliteca externa __Argon2__. Você pode obter informações de como instalar ela aqui: [Instalação das ferramentas de uma pessoa desenvolvedora Elixir](https://dev.to/maiquitome/instalacao-das-ferramentas-de-um-desenvolvedor-elixir-1bjm)

Em `lib/rockelivery/user.ex`:

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

  @fields_that_can_be_changed [
    :address,
    :age,
    :cep,
    :cpf,
    :email,
    :name,
    # altere password_hash para password.
    # o campo password_hash pode ser retirado
    # dos campos permitidos pois será utilizada
    # a função change para alterar o password_hash
    :password
  ]

  @required_fields [
    :address,
    :age,
    :cep,
    :cpf,
    :email,
    :name,
    # altere password_hash para password.
    # o password_hash só será alterado após as validações,
    # por isso deve ser retirado dos campos obrigatórios.
    :password
  ]

  @primary_key {:id, :binary_id, autogenerate: true}

  schema "users" do
    field :address, :string
    field :age, :integer
    field :cep, :string
    field :cpf, :string
    field :email, :string
    field :name, :string
    # adicionando um campo virtual
    # o valor dele não será gravado no banco de dados
    # será apenas para capturar a senha do usuário
    field :password, :string, virtual: true
    field :password_hash, :string

    timestamps()
  end

  def changeset(%{} = params) do
    %__MODULE__{}
    |> cast(params, @fields_that_can_be_changed)
    |> validate_required(@required_fields)
    |> validate_length(:password_hash, min: 6)
    |> validate_length(:cep, is: 8)
    |> validate_length(:cpf, is: 11)
    |> validate_number(:age, greater_than_or_equal_to: 18)
    |> validate_format(:email, ~r/@/)
    |> unique_constraint(:email)
    |> unique_constraint(:cpf)
    # adicione a função para encriptar a senha
    |> put_pass_hash()
  end

  defp put_pass_hash(%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset) do
    change(changeset, Argon2.add_hash(password))
  end

  # caso o changeset seja 'valid?: false'
  defp put_pass_hash(changeset), do: changeset
end
```

Executando `iex -S mix` no terminal, podemos verificar que a função `Argon2.add_hash` preencheu o campo `password_hash` encriptando o valor de `password`:

```elixir
iex> changeset = Rockelivery.User.changeset(%{age: 28, address: "rua..", cep: "12345678", cpf: "12345678910", email: "maiqui@email.com", name: "Maiqui", password: "123456"})
#Ecto.Changeset<
  action: nil,
  changes: %{
    address: "rua..",
    age: 28,
    cep: "12345678",
    cpf: "12345678910",
    email: "maiqui@email.com",
    name: "Maiqui",
    password: "123456",
    password_hash: "$argon2id$v=19$m=131072,t=8,p=4$ifRuVbsF29358ZZTbdYxGg$iVGPxDdiG5bDVW1yVjfibaiwNSqKHsBOAmvzzfrYd7A"
  },
  errors: [],
  data: #Rockelivery.User<>,
  valid?: true
>
```

Agora você já pode partir para a [parte 2 do projeto](https://dev.to/maiquitome/projeto-rockelivery-api-para-pedidos-em-um-restaurante-com-elixir-e-phoenix-parte-2-3j63) :)