Diferente de case/2 que verificamos o valor e escolhemos o caso especifico para ela, cond/1verificamos a condição, como feito no if/2 com o adicional de termos várias condições alinhadas onde retornamos a primeira que for verdadeira.
Quando usuário tiver acesso 1, exibir mensagem "Bem vindo {nome}, sinta-se à vontade em nossas estruturas!"
Quando usuário tiver acesso 2, mostrar ultima pendência no setor da saúde junto com o nome do usuário "Bem vindo João, o Ibuprofeno x42 esta nas fases finais"
Quando o usuário tiver acesso 3, mostrar ultimo status do nemesis. "Sr. Iago, já mandamos nemesis para Raccoon city"
Vamos começar criando nosso teste:
tests/authorization_test.exs
defmoduleAuthorizationTestdouseExUnit.Case describe "access/1"do test "User is a guest"do person = %{name: "Paulo", level: 1} result =Authorization.access(person) assert result =="Bem vindo #{person.name}, sinta-se à vontade em nossas estruturas!"endendend
mixtesttest/authorization_test.exswarning:Authorization.access/1isundefined (module Authorizationisnotavailableorisyettobedefined)test/authorization_test.exs:7:AuthorizationTest."test access/1 User is a guest"/11) test access/1 User is a guest (AuthorizationTest)test/authorization_test.exs:5** (UndefinedFunctionError) functionAuthorization.access/1 is undefined (moduleAuthorizationisnotavailable)code:result=Authorization.access(person)stacktrace:Authorization.access(%{level:1,name:"Iago"})test/authorization_test.exs:7: (test)Finishedin0.03seconds (0.00s async,0.03ssync)1test,1failure
Recebemos um relatório dizendo que Authorization.access não está disponÃvel. Claramente, por que não possuÃmos esse modulo. Criaremos ele
mix test test/authorization_test.exsCompiling1file (.ex)Generated hello_world app1) test access/1User is a guest (AuthorizationTest) test/authorization_test.exs:5Assertionwith== failed code: assert result =="Bem vindo #{person.name}, sinta-se à vontade em nossas estruturas!" left: nil right: "Bem vindo Paulo, sinta-se à vontade em nossas estruturas!" stacktrace: test/authorization_test.exs:9: (test)Finishedin0.02seconds (0.00s async,0.02s sync)1 test,1 failure
Recebemos agora outro erro, dizendo que precisamos de um texto, mas o que recebemos foi um nil. Isso por que não estamos retornando nada da função chamada. Vamos fazer o mÃnimo para passar esse teste, retornaremos da função, o texto completo.
lib/authorization.ex
defmoduleAuthorizationdodefaccess(_user) do"Bem vindo Paulo, sinta-se à vontade em nossas estruturas!"endend
Agora possuÃmos apenas mais um ponto para ver. O que acontece caso mandamos um map incorreto para dentro da função access? Por exemplo Authorization.access("lalala").
Vamos validar isso no terminal iterativo do elixir. Para isso queremos que esse projeto que estamos mexendo compile junto para que possamos usar o modulo Authorization. Para isso, basta executar na pasta raiz de seu projeto iex -S mix e o terminal iterativo será aberto:
Vamos executar a função Authorization.access/1 utilizando como parâmetro algo incorreto, como "lalala"
iex(1)> Authorization.access("lalala")** (KeyError) key :name not found in: "lalala". If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
(hello_world0.1.0) lib/authorization.ex:3:Authorization.access/1
Criamos um teste simples para garantir o comportamento. Se você analisar o código, verá que agora adicionamos a convenção de :ok/:error para conseguirmos facilmente identificar quando algo da certo e quando algo da errado.
mixtesttest/authorization_test.exs1) test access/1 invalid params (AuthorizationTest)test/authorization_test.exs:5 ** (KeyError) key :name not found in: "lalala". If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
code:{:error,reason}=Authorization.access(invalid_params)stacktrace: (hello_world0.1.0) lib/authorization.ex:3:Authorization.access/1test/authorization_test.exs:7: (test).Finishedin0.03seconds (0.00s async,0.03ssync)2tests,1failure
Agora precisamos lidar com ele. Para isso, utilizaremos a validação de tipo usando uma estrutura que ja possuÃmos no projeto. A estrutura User. Nela já possuÃmos o name.
Se rodar novamente o erro, teremos um relatório diferente
mixtesttest/authorization_test.exsCompiling1file (.ex)1) test access/1 User is a guest (AuthorizationTest)test/authorization_test.exs:12** (FunctionClauseError) no functionclause matching in Authorization.access/1ThefollowingargumentsweregiventoAuthorization.access/1:# 1%{level:1,name:"Paulo"}Attemptedfunctionclauses (showing 1outof1):defaccess(user=%User{})code:result1=Authorization.access(person1)stacktrace: (hello_world0.1.0) lib/authorization.ex:2:Authorization.access/1test/authorization_test.exs:16: (test)2) test access/1 invalid params (AuthorizationTest)test/authorization_test.exs:5** (FunctionClauseError) no functionclause matching in Authorization.access/1ThefollowingargumentsweregiventoAuthorization.access/1:# 1"lalala"Attemptedfunctionclauses (showing 1outof1):defaccess(user=%User{})code:{:error,reason}=Authorization.access(invalid_params)stacktrace: (hello_world0.1.0) lib/authorization.ex:2:Authorization.access/1test/authorization_test.exs:7: (test)Finishedin0.02seconds (0.00s async,0.02ssync)2tests,2failuresshel
Recebemos dois erros, vamos focar no primeiro agora. Já que nossa função access permite apenas receber a estrutura User agora, precisamos alterar nossos dados de testes para enviar o User para access.
mixtesttest/authorization_test.exs==Compilationerrorinfiletest/authorization_test.exs==** (KeyError) key :level not found (hello_world0.1.0) expandingstruct:User.__struct__/1test/authorization_test.exs:13:AuthorizationTest."test access/1 User is a guest"/1
Recebemos um outro erro, dizendo que a key :level not found. Quando utilizamos structure, precisamos utilizar apenas a estrutura de dados criada nele. Em user não possuÃmos um campo :level e isso faz com que a compilação falhe.
Se você analisar, estamos usando pattern matching no parâmetro da função e devido não ter sido correspondido ele não entrou na primeira função access. Podemos então criar uma nova função de mesmo nome, fazer dar matching e então retornar a mensagem de erro. Vamos lá
lib/authorization.ex
defmoduleAuthorizationdodefaccess(user = %User{}) do"Bem vindo #{user.name}, sinta-se à vontade em nossas estruturas!"enddefaccess(_user) do {:error,"invalid_params"}endend
Conseguimos finalizar o primeiro ponto, vamos seguir para o ponto dois.
Quando usuário tiver acesso 1, exibir mensagem "Bem vindo {nome}, sinta-se à vontade em nossas estruturas!"
Quando usuário tiver acesso 2, mostrar ultima pendência no setor da saúde junto com o nome do usuário "Bem vindo João, o Ibuprofeno x42 esta nas fases finais"
Quando o usuário tiver acesso 3, mostrar ultimo status do nemesis. "Sr. Iago, já mandamos nemesis para Raccoon city"
Vamos agora para o nÃvel 2, a autorização de um funcionário. Vamos agora criar um teste para lidar com esse cenário.
test/authorization_teste.exs
defmoduleAuthorizationTestdouseExUnit.Case describe "access/1"do# ... test "User is an employee"do person = %User{name: "Carlos", level: 2} {:ok, result} =Authorization.access(person) assert result =="Bem vindo #{person.name}, o Ibuprofeno x42 esta nas fases finais"endendend
O teste agora quer garantir que usuários com nÃvel 2 de acesso, tenham informação sobre seu dia de trabalho, pois nÃvel dois se da a funcionários. Se rodarmos esse teste, teremos um relatório de erro.
mixtesttest/authorization_test.exs1) test access/1 User is an employee (AuthorizationTest)test/authorization_test.exs:5Assertionwith==failedcode:assertresult=="Bem vindo #{person.name}, o Ibuprofeno x42 esta nas fases finais"left:"Bem vindo Carlos, sinta-se à vontade em nossas estruturas!"right:"Bem vindo Carlos, o Ibuprofeno x42 esta nas fases finais"stacktrace:test/authorization_test.exs:11: (test)..Finishedin0.04seconds (0.00s async,0.04ssync)3tests,1failure
A mensagem que esperavamos não foi entregue. Isso devido a função access so esta implementado para devolver uma mensagem de primeiro nÃvel. Vamos alterar utilizando cond/0 para tomar a decisão de fluxo que queremos.
Relembrando que
* nivel 1 -> mensagem para visitantes
* nivel 2 -> mensagem de funcionarios base
* nivel 3 -> mensagem de postos altos
Vamos alterar nosso código
lib/authorization.ex
defmoduleAuthorizationdodefaccess(user = %User{}) doconddo user.level ==1-> {:ok,"Bem vindo #{user.name}, sinta-se à vontade em nossas estruturas!"} user.level ==2-> {:ok,"Bem vindo #{user.name}, o Ibuprofeno x42 esta nas fases finais"}endenddefaccess(_user) do {:error,"invalid_params"}endend
Adicionamos a condicional cond/0 onde ela lida com o level de acesso do usuário. Percebe como fica simples adicionar novos desvios de fluxo?
Quando usuário tiver acesso 1, exibir mensagem "Bem vindo {nome}, sinta-se à vontade em nossas estruturas!"
Quando usuário tiver acesso 2, mostrar ultima pendência no setor da saúde junto com o nome do usuário "Bem vindo João, o Ibuprofeno x42 esta nas fases finais"
Quando o usuário tiver acesso 3, mostrar ultimo status do nemesis. "Sr. Iago, já mandamos nemesis para Raccoon city"
Desafio
Tente adicionar o level 3 para dar matching na frase: Sr. {nome}, já mandamos Nemesis para Raccoon city.
Você deve:1. Escrever o teste em authorizatin_test.exs
2. Implementar a solução em authorization.ex
Existe apenas mais uma coisa a se fazer. Vamos dizer que eu coloque o nÃvel de acesso 10. O que aconteceria? Vamos abrir novamente nosso terminal iterativo iex -S mix e executaremos a chamada da função usando o level 10.
iex(1)> Authorization.access(%User{name:"iago",level:10})** (CondClauseError) no cond clause evaluated to a truthy value (hello_world0.1.0) lib/authorization.ex:7:Authorization.access/1
defmoduleAuthorizationTestdouseExUnit.Case describe "access/1"do test "You are supposed to not be here"do person = %User{name: "Eduardo", level: 10} {:error, reason} =Authorization.access(person) assert reason =="#{person.name}, você não deveria estar aqui. Peço que se retire"end# ...endend
Execute esse código e verá que conseguimos reproduzir o problema que enfrentamos no terminal iterativo.
mixtesttest/authorization_test.exsCompiling1file (.ex)...1) test access/1 You are supposed to not be here (AuthorizationTest)test/authorization_test.exs:5** (CondClauseError) no cond clause evaluated to a truthy valuecode:{:error,reason}=Authorization.access(person)stacktrace: (hello_world0.1.0) lib/authorization.ex:7:Authorization.access/1test/authorization_test.exs:8: (test)
Precisamos cuidar disso agora, mas seremos defensivos. Ao invez de fazer alguma regra, deixaremos uma clausura true no final de cond, onde, se nenhuma clausura for saciada, a ultima sera e uma mensagem de erro aparecerá.
lib/authorization.ex
defmoduleAuthorizationdodefaccess(user = %User{}) doconddo user.level ==1-> {:ok,"Bem vindo #{user.name}, sinta-se à vontade em nossas estruturas!"} user.level ==2-> {:ok,"Bem vindo #{user.name}, o Ibuprofeno x42 esta nas fases finais"}true-> {:error,"#{user.name}, você não deveria estar aqui. Peço que se retire"}endenddefaccess(_user) do {:error,"invalid_params"}endend
Adiconamos uma condição que será sempre true, caso nenhuma condição acima seja true, ele caira nessa última e a mensagem de erro vai ser gerada. Uma solução simples, que funciona bem para nós aqui.