Ok, eu estou devendo alguns posts mas é por uma boa causa.
A novidade da vez é o "lançamento" da primeira versão alpha do Poste, codinome braveheart, que como o nome diz é somente para os puros de coração!
Como já está usável por mim, considero que já vale um "alpha". O que não quer dizer que ele está instalável, estável ou funcional em qualquer nível, ou seja, ainda não vale nem um "beta". Você foi avisado!
Mas se você é do tempo que os homens de verdade escreviam seus device drivers, aproveite o commit, teste bastante e deixe suas considerações, dicas ou patches nos comentários ;)
Este projeto é muito mais uma diversão do que uma tentativa de criar um
novo app-killer, então, avançamos devagar. Algumas coisas eu já poderia
ter implementado de cara mas preferi fazer um pedaço de cada vez também
para poder mostar como fazer, os prós, os contras, os erros e acertos e
principalmente as decisões envolvidas em cada etapa.
Até o momento, foram uns vinte commits implementando a exibição de posts,
com um ou outro refactoring. Por enquanto a gente apenas exibe posts já
presentes em um banco de dados, o que está longe atender às necessidades
da primeira versão alpha.
O próximo passo é adicionar a capacidade de se inserir posts usando o
próprio blog, que eu divido em duas etapas:
- implementar um mecanismo de inserção de posts;
- implementar uma forma de protegê-lo de acessos indevidos;
Adicionando um sistema de login
Para proteger certas áreas do site, que vou chamar de
áreas privadas, vamos utilizar um mecanismo de sessões
acessado via login. Assim, qualquer usuário do site pode ver as
áreas públicas enquanto que usários específicos, após
se autenticarem através de um login, tem acesso a outros recursos.
Por sorte o Mojolicious já implementa um sistema de sessões, portanto,
só precisamos autenticar os usuários, associando-os à sessão em questão.
Ou seja, vamos criar um mecanismo onde:
- O usuário entra com o seu login e senha;
- O sistema aceita ou rejeita essa combinação;
- Em caso positivo, associamos esse usuário à sessão corrente;
-
Nas áreas privadas, verificamos a presença ou não dos dados
autenticados para permitir ou não o acesso;
Tablea de usuários
Para logar, precisamos claro de usuários, com um login e senha.
Vamos criar então uma tabela users com as
colunas user_id, username,
password, email e full_name.
Ná prática, para o login precisamos apenas de usuário e senha, mas como
eu já sei que vamos precisar de mais informações adiante, já implemento
logo na tabela.
Tanto username como password são necessários por
razões óbvias. O full_name é por uma razão estética. Quero
mostrar o nome do autor do post. O email eu vou utilizar em
conjunto com o mecanismo de senha.
Por último, o user_id. Eu vou utilizá-lo para ligar as
tabelas posts e users. Neste ponto cabe a
discussão de por que não utilizar o username ou o próprio email como
identificadores únicos. A minha justificativa atual, é que é menos
custoso um join sobre uma chave numérica do que um join sobre uma chave
string. Mas isso não quer dizer que minha opinião não vá mudar no futuro.
Lidando com senhas
O armazenamento e manipulação de senhas é um capítulo à parte em todo
manual de segurança, sendo algo muito, muito sensível.
Todas as decisões relacionadas, desde a escolha do algoritmo de
encriptação, até a forma de lidar com isso pode(rá) ser conferida
nesse artigo do equinócio
.
Mecanismo básico de login
O controller
Access.pm
É o responsável pela autenticação.
Como de costume a action startup cria as rotas necessárias.
A rota /login, acessada somente via método
GET (ou seja quando acessamos diretamente a url pelo
browser) ativa a action index que exibe o form de login.
Já a rota /login acessada via método POST
(quando enviamos os dados do form de login) ativa a action
login que valida os dados e associa ou não o usuário à
sessão corrente.
A rota /logout ativa a action logout que
encerra e destrói a sessão do usuário.
Se ficou confuso duas rotas terem o mesmo nome, o Mojolicious separa-as
também pelo método HTTP utilizado. Confira esse texto sobre o
Protocolo HTTP
.
Barra Menu
Agora só falta acrescentar algo na interface para encontrarmos o form de
login e a rota para o logout.
Vamos adicionar então uma
navigation bar
, com links para o home do site e login ou logout, conforme o caso.
O Mojo oferece um helper chamado session, que pode ser usado
para verificar se há um usuário associado (ou logado) ou se o site está
sendo acessado por um usuário comum.
Dessa forma, por enquanto, um usuário comum vê um menu com Home e
Login, enquanto que um usuário logado vê um menu com
Home e Logout.
Existem alguns truques legais que podem ser usados, como por exemplo ao
tentarmos acessar o form de login já estando logados, a action percebe e
ignora, enviando-nos para a página principal do site.
Analogamente, nem é um truque, mas ao acessar o link de logout sem
estarmos logados, nada acontece também.
Conclusões
Bom, esse primeiro passo não trouxe muitas novidades visuais mas agra
podemos implementar um sistema de inserção de posts acessível apenas por
usuários logados.
De todas as alterações até aqui, a do commit
265c7d4
é com certeza a mais nada a ver. Na verdade eu convesso que fiquei com
preguiça de fazer algo decente e to enrolando até agora. Vamos ver se
depois desse post isso passa.
A alteração de hoje é resultado de uma noite insone e não traz muitas
novidades técnicas. Foi algo mais 'just for fun'.
Chegando aos nove posts a barra de rolagem está ficando um pouco comprida,
então resolvi implementar um esquema de paginação.
Isso é feito em três etapas. A primeira naturalmente criar a rota para
paginação. Já a segunda é selecionar uma dada quantidade de posts
no banco, ou seja uma faixa de posts. E por último, precisamos criar na
view os botões para navegação entre as páginas.
Criando a rota
Com a startup publicando as rotas, basta acrescentar uma
mísera linha de código, and next:
$r->route('/posts/page/:page')->to('posts#index');
Paginando no banco com DBIx::Class
Para selecionar os posts no banco, ao invés de escrever queries
mirabolantes, eu conto com o completíssimo DIBx::Class.
Bastou acrescentar os parametros rows e page no
search realizado pelo last_posts.
O rows define quantas linhas devem ser retornadas em cada
página, enquanto que o page diz qual a página que é
retornada.
Já deu pra perceber que eles são uma abstração em cima dos conhecidos
LIMIT e OFFSET do SQL, e uma mão na roda, pois
de acordo com a rota, teremos o número da página atual e não o offset de
registros. Assim ao invés de ficar fazendo continha eu passo diretamente
o número da página para o DBIC, and next.
Paginando com o Paginator::Lite
O Paginator::Lite é um módulo que eu criei para me auxiliar
a criar controles de paginação de forma dinâmica.
No próprio CPAN existem vários módulos que se propõem a fazer isso,
entretanto, na época, nenhum tinha a capacidade de 'paginar o paginador'.
As formas clássicas de fazer paginação são adicionando botões
next/prev e/ou adicionando boões com os números das páginas desejadas:
(prev) 1 2 [3] 4 5 (next)
Isso funciona bem se você tem poucas páginas, mas considere o exemplo
abaixo:
(prev) 1 2 3 4 5 6 7 8 9 10 [11] 12 13 14 15 16 16 18 19 20 21 (next)
Isso começa a ficar horroroso. Você poderia remover os números e deixar
apenas os botões next/prev, mas o usuário que quisesse saltar várias
teria que navegar uma a uma.
O Paginator::Lite implementa então a idéia de frame (
ou janela), que é um subconjunto to total de páginas
que aparecem nos botões numéricos, é a paginação do paginador.
Considere o exemplo abaixo:
(prev) 11 12 [13] 14 15 (next)
Além dos controles next/prev ele possui alguns botões de acesso direto a
páginas que estão em volta da página corrente. Este é o frame.
Para usá-lo basta construir um objeto com os parâmetros desejados e passar
para o template via stash.
No template, é só usar um loop e alguns condicionais que o paginador tá
pronto. Confira o arquivo
paginator.html.ep
.
Assim chegamos a mais um final feliz com poucas linhas de código.
Esta semana conversei bastante com o
@garu_rj a respeito da existência
de duas rotas para se chegar ao mesmo post, uma por ID e outra por
permalink.
A escolha de criar uma rota por ID, pra mim foi natural. Do ponto de vista
de um desenvolvedor, se você quer extrair do banco um elemento único,
busque por sua chave primária. Entretanto, com a utilização de permalinks,
além de ser redundante, a busca por ID presenteou-me com um bug.
Como '.html' é o formato default, a url
http://app/posts/7.html não casa com
o permalink para o post de título '7', mas sim com o post de ID 7 :'(
Minha rota estava errada, e para corrigi-la, eu precisei introduzir código
não-elegante, forçando undef para o formato na rota numérica.
Oras, simples de corrigir, mas se a presença de uma 'feature' redundante é
responsável por um bug, por que não removê-la?
Então essa foi a minha escolha. Ao invés de adicionar uma correção
não-elegante, preferi remover o código não-necessário, causador do bug.
Chegamos assim ao nono post e ao commit
d40a7f4