Braveheart

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 ;)

Implementando Login

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:

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:

  1. O usuário entra com o seu login e senha;
  2. O sistema aceita ou rejeita essa combinação;
  3. Em caso positivo, associamos esse usuário à sessão corrente;
  4. 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.

Nada a ver

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.

Paginando com DBIx::Class e Paginator::Lite

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.

7

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

[ 1 ] 2 3 Next