Organizando flash messages no Phoenix
Comecei a fazer um sistema pra entender melhor como funciona o Elixir e aprender sobre o Phoenix.
O Phoenix é um framework pra Elixir, assim como o Rails é um framework pro Ruby. Ele tem como missão ser produtivo, sem comprometer a velocidade ou a manutenção.
Sem mais delongas, decidi criar um CRUD simples em Elixir pra registrar os livros que já li. Usei os seguintes comandos:
# Cria a aplicação.
$ mix phx.new booklistx
# Entra no projeto criado
cd booklistsx
# Gerador do CRUD (estilo scaffold do Rails)
mix phx.gen.html Books Book books title:string
# Cria o banco e cria a tabela de books
mix ecto.create
mix ecto.migrate
Defini como root da aplicação a listagem de livros.
# lib/booklistx_web/router.ex
defmodule BooklistxWeb.Router do
use BooklistxWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", BooklistxWeb do
pipe_through :browser
# get "/", PageController, :index # <- Comentei essa linha!
resources "/", BooksController # <- Adicionei essa linha!
end
# Other scopes may use custom stacks.
# scope "/api", BooklistxWeb do
# pipe_through :api
# end
end
Executei o comando pra iniciar a aplicação:
$ mix phx.server

Pronto, já podia adicionar e remover livros. Foi aí que, ao criar um livro, vi a flash message aparecer.

Usei o inspetor do browser pra ver como era o html.

Vi que o html sempre vinha com as tags de flash message:
<p class="alert alert-info" role="alert">Book updated successfully.</p>
<p class="alert alert-danger" role="alert"></p>
Tem só um truque simples de css pra não mostrar nada caso a tag esteja vazia:
/* assets/css/phoenix.css */
.alert:empty {
display: none;
}
Por padrão o arquivo vem assim, carregando as tags de alert mesmo quando não tem nenhuma flash message:
# lib/booklistx_web/layout/app.html.exx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Booklistx · Phoenix Framework</title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<%= csrf_meta_tag() %>
</head>
<body>
<header>
<section class="container">
<nav role="navigation">
<ul>
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
</ul>
</nav>
<a href="https://phoenixframework.org/" class="phx-logo">
<img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
</a>
</section>
</header>
<main role="main" class="container">
#-> <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
#-> <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<%= render @view_module, @view_template, assigns %>
</main>
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>
Isso me incomodava. Pesquisei como funciona e encontrei numa issue sugerindo usar dessa forma:
# lib/booklistx_web/layout/app.html.exx
...
<%= if info = get_flash(@conn, :info) do %>
<p class="alert alert-info" role="alert"><%= info %></p>
<% end %>
<%= if error = get_flash(@conn, :error) do %>
<p class="alert alert-danger" role="alert"><%= error %></p>
<% end %>
...
Agora ele só mostra caso tenha alguma flash message. Mas essas variáveis no meio do código (info e error) não ficaram legais.
Decidi fazer algo parecido com o que já fiz no Rails.
Devem existir várias outras formas de resolver isso, talvez melhores, mas essa foi a que mais gostei porque é simples e usa os conceitos que venho estudando.
Criei os seguintes arquivos:
# Cria o arquivo shared_view
$ touch lib/booklistx_web/shared_view.ex
# Cria a pasta shared
$ mkdir lib/booklistx_web/templates/shared
# Cria o arquivo _flash_message.html.exx
$ touch lib/booklistx_web/templates/shared/_flash_message.html.eex# lib/booklistx_web/shared_view.ex
defmodule BooklistxWeb.SharedView do
use BooklistxWeb, :view
import BooklistxWeb.Router.Helpers
def show_flash_message(conn) do
conn
|> get_flash
|> flash_message
end
def flash_message(%{"info" => message}) do
render "_flash_message.html", class: "primary", message: message
end
def flash_message(%{"error" => message}) do
render "_flash_message.html", class: "danger", message: message
end
def flash_message(_), do: nil
end
Aqui estou usando coisas que aprendi como pipe e pipeline no método show_flash_message, e pattern matching no flash_message.
A partial ficou assim:
# lib/booklistx_web/templates/shared/_flash_message.html.eex
<p class="alert alert-<%= @class %>" role="alert">
<%= @message %>
</p>
E o layout ficou assim:
# lib/booklistx_web/layout/app.html.exx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Booklistx · Phoenix Framework</title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<%= csrf_meta_tag() %>
</head>
<body>
<header>
<section class="container">
<nav role="navigation">
<ul>
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
</ul>
</nav>
<a href="https://phoenixframework.org/" class="phx-logo">
<img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
</a>
</section>
</header>
<main role="main" class="container">
<%= BooklistxWeb.SharedView.show_flash_message(@conn) %>
<%= render @view_module, @view_template, assigns %>
</main>
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>Conclusão
No meu ponto de vista, ficou bem melhor do que usar as variáveis (info e error) e aqueles IF direto no layout. Acredito que devem ter soluções melhores, mas essa foi a que consegui fazer e mais me agradou. Consegui colocar em prática algumas coisas que aprendi como pipe, pipeline e pattern matching.
Vou deixar o link do código que fiz no github:
https://github.com/guilhermeyo/booklistx
Fique à vontade pra deixar feedback e melhorias que posso fazer.
Referências
#23: Partial Templates with Phoenix Elixir forum - Check for error and info alert in Phoenix Issue phoenixframework - Add has_flash? functions. #1757
Comentários