case
O case/2
nos ajuda a condicionar um valor de acordo com diferentes valores de entrada. Ele cairá no caso especificado pelo valor esperado. Normalmente utilizando pattern matching.
Um exemplo real:
case HTTPoison.get("http://.../user/1") do
{:ok, %HTTPoison.Response{body: body, status: 200}} -> Jason.decode(body)
{:ok, %HTTPoison.Response{body: body, status: 404}} -> {:error, :not_found}
{:ok, %HTTPoison.Response{body: body, status: 401}} -> {:error, :unauthorized}
{:error, reason} -> {:error, reason}
end
HTTPoison.get/1
HTTPoison é um biblioteca escrita em elixir para realizar requisições HTTP externas.
Caso queira, você pode ver mais sobre ela na página de biblioteca
https://github.com/edgurgel/httpoison
Temos no exemplo uma chamada a um endpoint para resgatar um usuário com o id 1. No mundo real temos vários casos de resposta utilizando pattern matching para cada caso.
status: 200 -> Deu tudo certo e o usuário foi retornado no boddy
status: 404 -> O usuário com id 1 não foi encontrado
status: 401 -> Você não possui autorização para acessar esse endpoint
:error -> Algo deu errado na chamada do endpoint mas não possuimos tratamento para isso, logo, vou apenas retornar a razão do erro junto com a tupla de error.
Case se torna um bom recurso para conseguirmos lidar com todos os casos dessa requisição e facilmente trata-las a fim de avisar o usuário ou tentar uma ação corretiva. No nosso caso, apenas alertamos que algo não saiu como o esperado.
Criando nosso código
Vamos a um código mais simples para aplicarmos o conceito.
Utilizaremos o código de autorização feita no estudo de cond/1 . Precisamos obter os dados de um recurso, que vamos chamar aqui de articles.
Pra isso o usuário deve estar autorizado a receber a informação. Qualquer nível de funcionário conseguirá realizar essa operação.
defmodule ArticlesTest do
use ExUnit
test "success: user has the authorization to get articles" do
user = %User{id: 156, name: "Iago", level: 1}
assert {:ok, _articles} = Articles.all()
end
end
mix test test/articles_test.exs
warning: Articles.all/1 is undefined (module Articles is not available or is yet to be defined)
test/articles_test.exs:7: ArticlesTest."test success: user has authorization to get articles"/1
1) test success: user has the authorization to get articles (ArticlesTest)
test/articles_test.exs:4
** (UndefinedFunctionError) function Articles.all/1 is undefined (module Articles is not available)
code: assert {:ok, _articles} = Articles.all(user)
stacktrace:
Articles.all(%User{id: 156, name: "Iago", level: 2})
test/articles_test.exs:7: (test)
Finished in 0.03 seconds (0.00s async, 0.03s sync)
1 test, 1 failure
Teste falhou por que não temos ainda o módulo Articles.
Vamos cria-lo e já adicionar a logica utilizando o Authorizatin.access/1
. Vamos fazer esse teste passar o mais simples possível.
defmodule Articles do
def all(_user) do
articles = [%{title: "This is an example"}]
{:ok, articles}
end
end
mix test test/articles_test.exs
Compiling 1 file (.ex)
.
Finished in 0.01 seconds (0.00s async, 0.01s sync)
1 test, 0 failures
Primeira etapa finalizada. Vamos para o segundo problema. Caso você não tenha um nível de acesso acima de 1, você não pode acessar os dados.
defmodule ArticlesTest do
use ExUnit.Case
# ...
test "fail: user has not authorization to get articles" do
user = %User{id: 156, name: "Iago", level: 1}
assert {:error, _reason} = Articles.all(user)
end
end
Rodando o teste, teremos um relatório de erro.
mix test test/articles_test.exs
Compiling 1 file (.ex)
1) test fail: user has not authorization to get articles (ArticlesTest)
test/articles_test.exs:10
match (=) failed
code: assert {:error, _reason} = Articles.all(user)
left: {:error, _reason}
right: {:ok, [%{title: "This is an example"}]}
stacktrace:
test/articles_test.exs:13: (test)
.
Finished in 0.02 seconds (0.00s async, 0.02s sync)
2 tests, 1 failure
Temos agora um cenário novo. Quando o seu nível de acesso é baixo, ele não deveria deixar passar. Mas como esta agora voce consegue acessar os artigos normalmente.
Vamos adicionar a checagem de autorização. A autorização responde de duas formas:
{:ok, data} => quando algo tem sucesso
{:error, reason} => quando algo está errado.
Já que possuimos esses dois cenários e ambos podem ser pegos por pattern matching podemos utilizar case/2 para resolver o problema.
Utilizaremos case/2 com o primeiro argumento a chamada a função Authorization.access/1
Utilizaremos o bloco do para realizar os matchs dos cenários
Retornaremos o valor dependendo do caso.
defmodule Articles do
def all(user) do
articles = [%{title: "This is an example"}]
case Authorization.access(user)
{:ok, articles} -> {:ok, articles}
{:error, reason} -> {:error, reason}
end
end
end
Dentro do segundo argumento do case/2
temos a chamada do escopo do
onde isola o contexto de case
. Cada linha dentro do escopo do
é um possível cenário a ser coberto. Sendo o lado esquerdo a resposta de Authorization.access
(o que foi chamado no primeiro argumento do case/2
). Para acessar um caso especifico o pattern matching deve ser válido. Caso não seja ele passa para o proximo match. Caso não encontra nenhum que seja valido ele dispara uma exceção.
Vamos rodar o teste novamente
mix test test/articles_test.exs
..
Finished in 0.02 seconds (0.00s async, 0.02s sync)
2 tests, 0 failures
Tudo funcionando novamente. Com isso você pode criar diversos casos dentro desse case em articles. Como por exemplo:
Somente usuários com permissões de level 2 conseguem pegar determinados artigos
Quando tivermos level 0 pode ser apenas dito que nada foi encontrado.
Assim você consegue expandir os casos de uma funcionalidade.
Conclusão
Case é uma poderosa ferramenta para ter em sua caixa de ferramentas. Várias bibliotecas a utilizam. Também conseguimos tirar maior proveitos da conveção de tuplas, sendo assim, mais fácil a sua utilização.
Last updated
Was this helpful?