Seaside na prática: autenticação de usuários
Foi complicado, mas consegui subir mais um degrau no meu aprendizado do Seaside. Depois de gastar algumas horas com pesquisas e experimentações, eis que finalmente consegui desenvolver uma aplicaçãozinha de Login no Seaside. Ela não é nada sofisticada, é verdade, mas aprendi muita coisa durante o desenvolvimento desta aplicação. A aplicação está disponível para download caso você tenha interesse em dar uma olhada. Os procedimentos necessários para carregar o código na sua imagem Squeak são mostrados na screencast que acompanha o arquivo.
Estarei comentando aqui algumas coisas que aprendi durante o desenvolvimento dessa aplicação.
Estendendo a Session
Como tipicamente acontece em aplicações como esta, eu precisava manter alguns dados do usuário logado na sessão. Para fazer isso, eu criei uma subclasse de WASession que contém um atributo de instância, que serve justamente para guardar esses dados.
Como já imaginava, a coisa foi bastante tranqüila, pois tive apenas que criar tal classe e modificar as configurações da aplicação de modo que tal objeto fosse utilizado no lugar da WASession padrão.
Customizando um formulário gerado pelo Magritte
Outra coisa interessante que aprendi foi a respeito do Magritte. Para evitar a fadiga, eu decidi usá-lo para não precisar escrever o formulário de login na mão. Se você já viu um formulário gerado pelo Magritte, então sabe como ele se parece:
Pode parecer meio idiota, mas a única coisa que eu queria fazer era mudar o título do botão Save para Login. Achei que existia algum método para setar tal título, mas, pelo que vi na lista de discussão, a solução é adicionar um método #login no próprio componente do Magritte e indicar tal método ao gerar o formulário:
-
-
MAContainerComponent#login
-
self save. " Just like the Save button "
-
-
MAContainerComponent#cancel
-
" Some code here "
-
-
" … "
-
-
MyClass#generateForm
-
Login new asComponent addValidatedForm: #(login cancel); yourself.
-
" Some code here "
-
Veja que indiquei no método #addValidatedForm: os seletores para os métodos presentes no componente do Magritte. O Magritte então, ao renderizar o formulário, pega essa lista que recebeu no parâmetro do método e adiciona um botão para cada elemento. Neste caso o formulário será mostrado com os botões Login e Cancel.
Aprendendo a usar o método #onAnswer:
Essa parte foi bem interessante, pois posso dizer que realmente aprendi qual o papel dos métodos #call: e #answer: e, principalmente, onde se pode usá-los.
Pelo que eu pude perceber, o ciclo de vida de uma requisição no Seaside é composto basicamente por duas fases distintas: renderização e execução dos callbacks. (ao contrário de certos frameworks, que tem uma fase para cada letra do alfabeto).
Para ilustrar a situação, imagine que temos três componentes: A, B e C. O componente A chama (#call:) o B. O componente B, por sua vez, é pai do componente C. O componente B não faz nada de especial; sua única função é renderizar o componente C (#render:), que é o responsável por fazer a tarefa necessária (qualquer que seja ela).
Quando o componente A chama o B, uma chamada tipo B answer faz com que o controle volte ao componente A. Mas, e se você quiser que o componente C — que não foi chamado por nenhum outro, apenas renderizado pelo seu componente-pai — seja responsável por responder à chamada feita pelo componente A?
O problema é que, se você chama #answer: em um componente que não foi chamado por nenhum outro, nada acontece. Então, como fazer com que uma chamada C answer responda ao componente A?
A resposta está no método #onAnswer:! Todo componente Seaside possui este método, que recebe um bloco como parâmetro. Por exemplo:
-
-
SomeClass#someMethod
-
C onAnswer: [ :value | " Do something! " ]
-
Se o método #onAnswer: for chamado para o componente C, como mostrado, o bloco passado no parâmetro é executado sempre que o método #answer: for chamado nesse componente.
Então, o que fazer para resolvermos esse problema? Assim:
-
-
B#initialize
-
C := C new.
-
C onAnswer: [ :value | self answer: value ]
-
Desse jeito, sempre que o método #answer: for chamado no componente C, a chamada self answer: value (que pelo contexto também significa B answer: value) será executada, fazendo com que o componente B devolva o controle ao componente A!
Ativando a autenticação para “páginas administrativas”
Uma coisa que às vezes nos esquecemos enquanto trabalhamos com o Seaside é que ele é um framework que nos permite desenvolver aplicações orientadas a objetos. Isso acontece pois é muito comum hoje termos de trabalhar com frameworks que nos deixam fazer de tudo, menos programar aplicações orientadas a objetos. Por que estou dizendo isso? Porque basta tirar o conhecimendo sobre orientação a objetos que (muitas vezes) costumamos deixar na gaveta e usá-lo.
A solução que encontrei para fazer essa separação entre páginas públicas e “administrativas”, embora simples, resolve o problema.
Toda página administrativa estende SLPrivatePage. SLPrivatePage, assim como todas as outras páginas da aplicação, estendem SLPage, que é onde essa lógica de controle de acesso está implementada. A grosso modo, o que essa classe SLPage faz é modificar o funcionamento do método #call: para que este acione o esquema de autenticação caso a página seja uma instância de SLPrivatePage.
Configuração automática do entry-point
Até então, todas as vezes em que eu mexi com o Seaside, eu configurei minhas aplicações diretamente no aplicativo de configurações do Seaside. Toda vez eu ia lá, adicionava um entry-point, selecionava o componente-raíz e tudo mais.
No entanto, não é legal distribuir uma aplicação e fazer o usuário ter de configurá-la na mão. Por isso, tentei descobrir um jeito de fazer com que a aplicação se configurasse automaticamente assim que o código fosse carregado na imagem.
Se você já mexeu algumas vezes no utilitário de configuração do seaside, então você deve ter percebido a existência de alguns campos, por exemplo:
- O campo Library serve para que possamos adicionar bibliotecas e outros recursos a uma aplicação. Alguns exemplos recursos: estilos CSS, JavaScripts, imagens etc.
- O campo Ancestry serve para definirmos as classes de configuração usadas na configuração da aplicação. Uma aplicação padrão costuma vir com este campo setado como
WARenderLoopConfiguration, que fornece parâmetros para configuração da session, informações sobre o servidor, entre outros.
Então, para que eu conseguisse fornecer uma configuração customizada para minha aplicação, bastou estender WASystemConfiguration — que é a classe pai de todas as outras classes de configuração — sobrescrevendo nela alguns métodos para que retornem valores adequados a minha aplicação.
Então, para que o entry-point fosse adicionado automaticamente após o carregamento do código na imagem, bastou sobrescrever o método #initialize — no nível de classe — do componente raíz:
-
-
MyRootComponent class#initialize
-
| app |
-
super initialize.
-
app := self registerAsApplication: #entrypoint. " entry-point name "
-
app configuration addAncestor: MyConfiguration new " using our ancestor here "
-
Feito! Assim que esse método #initialize for chamado (e isso ocorre durante a inicialização da classe, de forma automática) o entry-point é registrado e configurado. Rodar a aplicação é só uma questão de iniciar o servidor web e acessá-la pelo browser.
Re-lembrando
Digo e repito: o código dessa aplicação está disponível para download! Ninguém vai te mandar um boleto de cobrança se você fazer o download.
E antes que eu me esqueça…
… Seaside rulezzzzzzzz.
Tags: autenticação, componentes, continuation, login, magritte, seaside, smalltalk, squeak, web
