Quando acordamos de manhã cedo e pensamos em fazer nossa mais nova ideia milionária, sempre vem na cabeça (pelo menos na minha) em colocar esse produto em uma aplicação web, para facilitar o acesso.
No mundo Elixir uma das melhores ferramentas para construir isso é o Phoenix Framework. Ele possui todas as funcionalidades que precisamos e muito mais.
Convenhamos que ideias vem o tempo todo e queremos faze-las e valida-las o mais rápido possível antes que percamos o foco (ou a vontade). Mas, como podemos acelerar o processo de criação?
Phoenix possui uma gama de generators que podem te ajudam a gerar código de problemas comuns, que basicamente se repetem ao longo da história e é sobre os generators que eu falarei aqui.
O que veremos?
- Gerando uma nova aplicação web com
mix phx.new ...
- Gerando contexto, controller, views e html com
mix phx.gen.html
- Gerando autenticação com
mix phx.gen.auth
- Protegendo rotas com autenticação
Para iniciar, essa é a ideia milionária:
Desafio de hoje: Na nossa mais nova ideia, precisamos criar um painel administrativo onde possamos cadastrar filmes. Os campos necessários são title
e description
. Nossos usuários precisarão estar logado para interagir com o sistema.
Requisitos tecnológicos
Criando nossa aplicação
Nossa aplicação terá a necessidade de um banco de dados (utilizaremos o postgres), também o suporte a HTML.
O primeiro gerador que utilizaremos dará conta dissoPodemos passar comandos simples para o cli do phoenix configurar isso para nós:
mix phx.new my_movies --database postgres --no-live
...
* creating my_movies/lib/my_movies/mailer.ex
* creating my_movies/lib/my_movies_web/gettext.ex
* creating my_movies/priv/gettext/en/LC_MESSAGES/errors.po
* creating my_movies/priv/gettext/errors.pot
* creating my_movies/assets/css/phoenix.css
* creating my_movies/assets/css/app.css
* creating my_movies/assets/js/app.js
* creating my_movies/priv/static/robots.txt
* creating my_movies/priv/static/images/phoenix.png
* creating my_movies/priv/static/favicon.ico
Fetch and install dependencies? [Yn]
O --no-live
diz para nosso gerador que não utilizaremos live view. O --database
seta para nós o banco que utilizaremos (potgres é o default, então não precisaria dele ali, mas para deixar mais claro, adicionei. Também da suporte a mysql
a mssql
, sqlite3
e outros.
No fim da operação o terminal ira perguntar se quer instalar as dependências. Pode ser feito depois, mas eu ja apertei Y
aqui enquanto tomava um gole de café.
Fetch and install dependencies? [Yn] Y
* running mix deps.get
* running mix deps.compile
We are almost there! The following steps are missing:
$ cd my_movies
Then configure your database in config/dev.exs and run:
$ mix ecto.create
Start your Phoenix app with:
$ mix phx.server
You can also run your app inside IEx (Interactive Elixir) as:
$ iex -S mix phx.server
Finalizando o processo devemos entrar na pasta do projeto.
cd my_movies
Agora falta so configurar o banco de dados em config/dev.exs
. Eu uso para fins de estudo o default, então meu está igual ao gerado.
config :my_movies, MyMovies.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "my_movies_dev",
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10
De volta ao terminal, precisamos rodar o setup da aplicação para conseguir executar ela. Utilizarei o comando mix setup
onde ele ja vai fazer para mim o mix deps.get
e mix ecto.setup
(que vai criar o banco para nós e rodar a migration caso tenha)
mix setup
Compiling 14 files (.ex)
Generated my_movies app
The database for MyMovies.Repo has already been created
14:25:38.948 [info] Migrations already up
Não se preocupe com o warning do gettext
ele não morde. Porém se voce se incomodar com o warning
pode ir no mix.exs e remover ele do compilers.
Pronto, tudo configurado e bom para ir. Para garantir vitória so executar o comando de inicializar o nosso server:
mix phx.server
[info] Running MyMoviesWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.29.tgz
[info] Access MyMoviesWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes..
Podemos então abrir nossa aplicação no browser http://localhost:4000. Vai, clica ai, confia.
Proximo passo: Criar nossa página para cadastrar, ler, editar e deletar um filme. (famoso CRUD)
e ai que entra nosso primeiro generate.
Segura ai, ainda não iremos tocar no código.
Na documentação do Phoenix temos outros generators que podem nos ser útil. Em nosso CRUD precisamos do pacote completo:
- schema: A estrutura de nosso Filme
- migration: O comando para criar a sua tabela no banco de dados
- context: um lugar para ele viver
- controller: um lugar para acessar ele
- view: uma forma de alterar sua exibição
Dois generator atendem a esse requisito:
- phx.gen.json
- phx.gen.html
Agora vai de você escolher como voce vai expor seu programa. Nós aqui iremos utilizar HTML, logo phx.gen.html será utilizado.
Nossa regra para isso será:
- Temos um catalogo
- No catalogo podemos adicionar, ver, listar e deletar filmes
- os filmes terão titulo e descrição apenas
- os filmes serão salvos na tabela
movies
no banco de dados
Bora para o terminal:
mix phx.gen.html Catalog Movie movies title:string description:string
* creating lib/my_movies_web/controllers/movie_controller.ex
* creating lib/my_movies_web/templates/movie/edit.html.heex
* creating lib/my_movies_web/templates/movie/form.html.heex
* creating lib/my_movies_web/templates/movie/index.html.heex
* creating lib/my_movies_web/templates/movie/new.html.heex
* creating lib/my_movies_web/templates/movie/show.html.heex
* creating lib/my_movies_web/views/movie_view.ex
* creating test/my_movies_web/controllers/movie_controller_test.exs
* creating lib/my_movies/catalog/movie.ex
* creating priv/repo/migrations/20221027161657_create_movies.exs
* creating lib/my_movies/catalog.ex
* injecting lib/my_movies/catalog.ex
* creating test/my_movies/catalog_test.exs
* injecting test/my_movies/catalog_test.exs
* creating test/support/fixtures/catalog_fixtures.ex
* injecting test/support/fixtures/catalog_fixtures.ex
Add the resource to your browser scope in lib/my_movies_web/router.ex:
resources "/movies", MovieController
Remember to update your repository by running migrations:
$ mix ecto.migrate
No final do comando ele pede para fazer algumas ações.
- Adicionar o
resources "/movies", MovieController
em nossas rotaslib/my_movies_web/router.ex
. Iremos colocar em acesso publico (sem restrição por não estar logado)
# ...
scope "/", MyMoviesWeb do
pipe_through :browser
get "/", PageController, :index
resources "/movies", MovieController # adicione essa linha
end
# ...
- rodar a migração que o gerador criou para nós.
mix ecto.migrate
13:26:53.578 [info] == Running 20221027161657 MyMovies.Repo.Migrations.CreateMovies.change/0 forward
13:26:53.581 [info] create table movies
13:26:53.597 [info] == Migrated 20221027161657 in 0.0s
O gerador fez bastante coisa né? Sugiro dar uma olhada no que foi adicionado e criado.
Uma nota: nem sempre vem como queremos, tendo muitas vezes que fazer mudanças. Os generators são uma base para adiantarmos nosso trabalho, ele não vai te entregar exatamente o que você precisa.
A não ser que você queira um CRUD simples e esse é o nosso caso! Rode a aplicação para ver o que aconteceu.
mix phx.server
E vamos entrar na nova rota criada: http://localhost:4000/books, aperta ai ;)
A segunda etapa do nosso sistema esta pronto. Podemos criar, listar. editar e deletar filmes. 🎉
Mas está meio estranho esse admin ter acesso por todos sem um login e senha não? 🤔
Sim e vamos resolver agora!
Gerando autenticação
Podemos também gerar essa parte do sistema, adiantando e muito nosso sistema. Utilizaremos o phx.gen.auth para isso.
A regra para isso sera:
- Precisamos que nosso usuário tenha uma conta com email e senha
- Precisamos validar via link se o e-mail existe de fato.
- Precisamos realizar o loggin do usuario
- Precisamos dar a possibilidade do logout
- Precisamos proteger nossa rota de filmes.
Let's go
O phx.gen.auth funciona muito parecido com o phx.gen.html que usamos. Primeiro passamos o contexto, depois o schema e por fim a tabela.
mix phx.gen.auth Account user users
...
* creating lib/my_movies_web/controllers/user_settings_controller.ex
* creating test/my_movies_web/controllers/user_settings_controller_test.exs
* creating lib/my_movies/accounts.ex
* injecting lib/my_movies/accounts.ex
* creating test/my_movies/accounts_test.exs
* injecting test/my_movies/accounts_test.exs
* creating test/support/fixtures/accounts_fixtures.ex
* injecting test/support/fixtures/accounts_fixtures.ex
* injecting test/support/conn_case.ex
* injecting config/test.exs
* injecting mix.exs
* injecting lib/my_movies_web/router.ex
* injecting lib/my_movies_web/router.ex - imports
* injecting lib/my_movies_web/router.ex - plug
* injecting lib/my_movies_web/templates/layout/root.html.heex
Please re-fetch your dependencies with the following command:
$ mix deps.get
Remember to update your repository by running migrations:
$ mix ecto.migrate
Once you are ready, visit "/users/register"
to create your account and then access "/dev/mailbox" to
see the account confirmation email.
Duas ações são necessárias
- gerir dependencias
mix deps.get
- rodar migrations
mix ecto.migrate
e voce esta bem para ir.
Mas vamos analisar a parte final que é interessante:
...
Once you are ready, visit "/users/register"
to create your account and then access "/dev/mailbox" to
see the account confirmation email.
O generate ja trás para nossas as funções de register
, login
e também confirmation
para saber que o e-mail está correto e assim ativar sua conta.
Existe diversas configurações que podem ser feitas aqui, recomendo a leitura da documentação e procurar na internet outras formas de customizar isso, é bem interessante e prático.
continuando...
Rode novamente o servidor phoenix mix phx.server
e voce verá algumas mudanças no cabeçalho.
Agora você pode ver no lado direito o register
e o Log in
e server para exatamente isso =D
Faça o register para ver o que acontece. Uma vez cadastrado as opções la em cima mudam:
Você já possui um sistema de autenticação para usar. Os arquivos gerados estão na sua codebase e você pode alterar a vontade, para suprir suas necessidades.
Um detalhe interessante, quando se faz o cadastro é enviado um e-mail, mas já que estamos local ele não vai para o mundo externo. Em vez disso ele é pego no meio do caminho e você pode ver ele aqui http://localhost:4000/dev/mailbox e fazer a confirmação sem precisar mandar para um provedor de e-mail. Muito util e rápido para testar, não?
Booa, bastante coisa pronta não? Mas faltou uma ultima etapa. Se voce acessar o /movies
sem estar logado ainda poderá realizar as operações, vamos resolver isso.
Protegendo rotas privadas
Ao ter gerado a autenticação o seu arquivo router.ex
mudou e agora você tem la alguns plugs e escopos novos.
Para os recursos de filmes estarem configurados de forma a não deixar pessoas não autenticadas acessar, você precisa usar o plug :require_authenticated_user
no pipe do escopo.
Depois de gerar a autenticação, foi configurado novas rotas automaticamente (que ja acessamos, como register e Log in). Basta agora mover o resources "/movies", MovieController
para o escopo que já está utilizando o plug ou criar um novo escopo para você. Irei optar por isso:
# lib/my_movies_web/router.ex
# ...
scope "/admin", MyMoviesWeb do
pipe_through [:browser, :require_authenticated_user]
resources "/movies", MovieController
end
# ...
No código acima, colocamos o recurso de filmes abaixo do escopo admin
, então o acesso na URL mudou sendo agora http://localhost:4000/admin/movies
Caso esteja logado você conseguirá ver a página normalmente, mas caso não esteja, você será redirecionado para a página de login e uma mensagem será exibida:
Basta fazer o login e você será enviado novamente para a listagem de filmes.
Agora sim! Tudo pronto 🎉🎉
Conclusão
Geradores podem causar grandes discussões no mundo do desenvolvimento e por uma boa razão.
Quando os utilizamos só por utilizar, sem uma consciência do que esta acontecendo, tende a deixar as coisas mais confusas e criar funcionalidades que nunca serão utilizadas. Isso é ruim a longo prazo, onde se torna difícil manter.
Mas se você está fazendo as escolhas consciente, analisando os impactos que isso possa causar e alterando o código gerado para se adequar ao seu produto, não a mau em utiliza-lo, eles te pouparão um grande tempo.
Espero que tenham gostado ;)
Já que você chegou até aqui, se inscreva no Café com Elixir uma newsletter semanal de Elixir focado em conteúdos da comunidade brasileira.
Me sigam também no twitter @iagoEffting lá sou mais ativo =D
E se inscrevam no canal no youtube
Top comments (0)