Magritte: como se virar sem documentação

É como aprender a andar de bike... quase!Há um (bom) tempo atrás, eu havia tentado aprender a utilizar o Magritte, um framework Smalltalk para meta-descrição de objetos, que fornece recursos como geração automática de views, relatórios, validação etc.

Eu cheguei até a prometer um post sobre o assunto só que, para minha infelicidade, eu não tinha conseguido fazer o bicho funcionar de jeito nenhum. Talvez isso tenha acontecido por eu não ser assim tão esperto; no entanto, creio que o fator de maior impedimento neste caso foi a falta de documentação.

Mesmo ficando chateado por não ter conseguido aprender nada a respeito do Magritte, acabei desistindo. E continuou assim até que, graças à excelente palestra do Ronaldo Ferraz (com realização da eGenial), recebi a dose de empolgol que precisava para voltar a brincar com o Squeak/Seaside aqui em casa. Então, lembrando dessa minha promessa furada, resolvi tentar, pela segunda vez, fazer o Magritte funcionar.

Se consegui desta vez? Digamos que sim. :) Estarei escrevendo aqui algumas coisas que aprendi durante esse processo de auto-flagelação aprendizado, e que são importantes para quem está interessado em começar a utilizar o Magritte.

Pré-requisitos

Sei que todos aqueles que costumam acompanhar este blog possuem o conhecimento necessário, mas, ainda assim, preciso lembrá-lo de que é necessário um bom conhecimento em orientação a objetos e design patterns, além de algum conhecimento em Smalltalk.

Instalando o Magritte

Antes de mais nada, caso você ainda não possua o Squeak instalado, eu recomendo de cara o uso da imagem Squeak-dev. Esta imagem, criada por Damien Cassou, já incorpora muitas das customizações necessárias para quem desenvolve aplicações no Squeak (code-completion, gerenciadores de “pacotes” e tudo mais).

Depois de baixar a imagem (e o arquivo que contém os fontes do Squeak 3.9), rode a imagem e faça a atualização:

Atualizando o Squeak

Se o Squeak perguntar se você quer atualizar para a versão 3.10, diga que não. Depois, abra o Package Universe Browser clicando em World - Open Package Universe Browser.

Na janela que aparecer:

  1. Faça a atualização da lista de pacotes (terceiro botão);
  2. Clique no botão que seleciona automaticamente os pacotes que precisam ser atualizados (segundo botão);
  3. Selecione, nas listas, a instalação do Seaside e do Pier. Isso fará com que o Magritte seja instalado automaticamente. Fazendo isso teremos o código do Pier — uma aplicação Seaside completa — disponível para consulta no caso de dúvidas.

Package Universe Browser

Prossiga com a atualização (primeiro botão). Em algum momento, o Squeak pedirá um usuário e senha para o aplicativo de configuração do Seaside. Você terá de informar também algumas configurações para o Pier.

Depois que terminar o processo de atualização, salve a imagem clicando em World - Save.

Testando o Seaside/Magritte

Abra uma instância de Workspace clicando em World - Open - Workspace. Digite:

  1.  
  2. WAKom startOn:8080.
  3.  

Esta linha serve para iniciarmos o servidor HTTP. Então selecione esta linha e pressione Ctrl+D para executar. Tente, em seguida, abrir o utilitário de configuração do Seaside, que deverá estar disponível no endereço http://localhost:8080/seaside/config. Informe o usuário e senha. Você verá uma tela parecida com a seguinte:

Utilitário de configuração do Seaside

Clique no link do Pier e verifique se ele funciona:

Pier em execução

Tente navegar pelas páginas e criar conteúdo. Se tudo estiver funcionando, podemos prosseguir.

O que é o Magritte, mesmo?

Antes de darmos continuidade, vamos tentar entender melhor o porque de se utilizar o Magritte.

Você já usou um framework ORM, tipo Hibernate? Se sim, então certamente você teve de informar dados sobre seus objetos de domínio (tipo de dados, ordem e nomes dos campos, relacionamentos, constraints etc). A grosso modo, esses dados que descrevem outros dados são chamados de… meta-dados.

Continuando com o exemplo, ao escrever o mapeamento de uma classe no Hibernate, você não fez nada mais do que informar ao Hibernate alguns meta-dados a respeito da sua classe. Por isso, podemos dizer que o Magritte pode ser encarado como um framework especializado nessa tarefa de descrição e leitura de meta-dados de objetos.

Apenas para você ter uma idéia, com o Magritte podemos especificar meta-dados em qualquer tipo de objeto e usar esses meta-dados para fazer algo útil. Por exemplo: gerar automaticamente consultas em objetos, relatórios, formulários para entrada de dados (com e sem suporte a validação), gerar instruções SQL para criação de tabelas e bancos de dados (convertendo os meta-dados para tipos SQL correspondentes), entre outras coisas.

Interessante, não?

Descrevendo objetos

De modo a permitir a descrição de diferentes tipos de dados, o Magritte suporta uma grande variedade de descriptions:

Exemplos de Descriptions

Para descrever tipos booleanos podemos utilizar um objeto da classe MABooleanDescription. Para datas (e intervalos, data mínima/máxima) podemos usar um objeto da classe MADateDescription. Aconselho a você abrir a categoria Magritte-Model-Description no Class Browser do Squeak e dar uma olhada em todas as descriptions disponíveis… isso ajuda a ter uma idéia dos dados que podemos expressar com o Magritte.

A idéia por trás disso tudo é usar diversos objetos description para descrever os atributos de um objeto qualquer. Podemos indicar se algum atributo deste objeto é obrigatório, visível, editável etc. Uma olhada nos métodos da classe MADescription também ajuda a nos dar uma idéia.

Mas, como eu faço para indicar essas descriptions?

Daqui em diante, imagine que temos uma aplicação com um objeto Pessoa:

  1.  
  2. Object subclass: #Pessoa
  3.     instanceVariableNames: ‘nome endereco telefone’
  4.     classVariableNames:
  5.     poolDictionaries:
  6.     category: ‘Minha-Aplicacao’
  7.  
  8.     " métodos para obter e setar os atributos "
  9.  
  10.     " outros métodos "
  11.  

Se você já conhece Smalltalk, não há nada de novo até aqui. A partir de agora, o nosso objetivo será tentar descobrir como fazer para utilizar o Magritte em conjunto com o Seaside, de modo que possamos criar um formulário de cadastro no Seaside, com suporte a validação.

Ao instalar o Magritte, mudanças são feitas em diversas outras classes já existentes. No exemplo abaixo, o Magritte adicionou o método #description no objeto Object class:

  1.  
  2. Object class#description
  3.     ^ MADescriptionBuilder for: self
  4.  

Já sabemos que uma description serve para descrever um determinado aspecto de um objeto. Então, aparentemente, o método #description serve para obtermos o objeto description correspondente.

Mas, o que isso faz? Após navegarmos um pouco pelo código, podemos encontrar o código do Magritte que é invocado através do método #description:

  1.  
  2. MANameBuilder#build: anObject
  3.     | selectors container description |
  4.     selectors := anObject class allSelectors select: [ :each | each isDescriptionSelector ].
  5.  
  6.     " mais código…"
  7.  

Interessante! Este código nos mostra que as descriptions são obtidas a partir dos selectors existentes no nível de classe (e que estão de acordo com as regras implementadas no método #isDescriptionSelector). Ah, você não sabe o que é um selector? Talvez seja melhor você dar uma estudada melhor na linguagem do Smalltalk antes de continuar lendo este texto. :P

Continuando, veja o método #isDescriptionSelector:

  1.  
  2. Symbol#isDescriptionSelector
  3.    ^ self ~= #description
  4.       and: [ self beginsWith: #description ]
  5.  

Se você entendeu os trechos de código que acabei de mostrar, certamente você já tem uma boa pista sobre como fazer para descrever um objeto com o Magritte. Se você não entendeu o código, a coisa fica complicada pois não há documentação disponível sobre esse framework. Como veremos a seguir, o melhor (ou o único) jeito de aprender a usar o Magritte é através do Class Browser do Squeak. :(

Não entendi! Como fazer para descrever meus objetos?

Resposta: crie métodos, no nível de classe, cujos nomes comecem com ‘description‘. Os métodos devem retornar objetos MADescription.

Colocando em prática

Vamos então ver se isso funciona mesmo. Vamos adicionar alguns métodos no nível de classe na classe Pessoa. Terá um método para cada description, que representam os atributos do objeto; o nome de cada método deve seguir a convenção #descriptionAtributo:

  1.  
  2. Pessoa class#descriptionNome
  3.     ^ (MAStringDescription new)
  4.         selectorAccessor: #nome;
  5.         label: ‘Nome’;
  6.         priority: 100;
  7.         beRequired;
  8.         yourself
  9.  
  10. Pessoa class#descriptionEndereco
  11.     ^ (MAStringDescription new)
  12.         selectorAccessor: #endereco;
  13.         label: ‘Endereco’;
  14.         priority: 200;
  15.         yourself
  16.  
  17. Pessoa class#descriptionTelefone
  18.     ^ (MAStringDescription new)
  19.         selectorAccessor: #telefone;
  20.         label: ‘Telefone’;
  21.         priority: 300;
  22.         yourself
  23.  

Se você deu uma olhada nos tipos de descriptions — assim como recomendei anteriormente neste post — você já tem uma boa idéia do que foi feito neste código.

Nosso modelo possui três campos do tipo String: nome, endereco e telefone. Em cada um desses descriptions indicamos um symbol que representa o selector relativo ao método get/set, um label e um número que indica a prioridade (para organizar a ordem com que os campos são mostrados).

Obviamente, existe uma infinidade de outros atributos a serem utilizados, mas creio que isso seja suficiente para se ter uma idéia de como podemos descrever objetos com o Magritte.

Será que isso funciona?

Mas, como podemos saber se isso que fizemos está realmente funcionando? Enquanto me matava estudava aqui, eu acabei encontrando uma forma simples de fazer isso: usando código Smalltalk. Um exemplo:

  1.  
  2. Pessoa description do: [ :each |
  3.         Transcript show: ‘label:’ , each label.
  4.         Transcript show: ‘, priority:’ , each priority asString.
  5.         Transcript show: ‘, selector:’ , each accessor asString.
  6.         Transcript show: (each isRequired ifTrue: [‘, required’] ifFalse: []).
  7.         Transcript cr]
  8.  

Aqui, o método description é invocado na classe Pessoa. Este método, como já vimos, percorre os selectors no nível de classe que começam com #description e retorna objetos MADescription numa coleção.

Abra uma instância da janela Transcript (World - Open - Transcript), cole o código mostrado em uma janela Workspace e o execute. Você verá, na janela Transcript, algo semelhante a isto:

label:Nome, priority:100, selector:(MASelectorAccessor read: #nome write: #nome:), required
label:Endereco, priority:200, selector:(MASelectorAccessor read: #endereco write: #endereco:)
label:Telefone, priority:300, selector:(MASelectorAccessor read: #telefone write: #telefone:)

Se tudo aconteceu de acordo com o que foi descrito, então estamos no caminho certo!

Gerando um formulário de cadastro no Seaside

Agora vem a parte fácil. Dentro de um componente Seaside qualquer, basta passarmos o controle (através do método WAComponent#call:) a um componente gerado pelo próprio Magritte com base nas descriptions:

  1.  
  2. cadastrarPessoa
  3.    | pessoa |
  4.    pessoa := self call: (Pessoa new asComponent addValidatedForm; yourself).
  5.    msg
  6.       ifNotNil: [self pessoas add: pessoa]
  7.  

O resultado:

Formulário gerado pelo Magritte Erro de validação mostrado pelo formulário

Tá certo que o componente não é renderizado com essas cores e tal… só está assim porque eu mudei o estilo da página para ficar fácil para nós enxergarmos a relação dos elementos do formulário com as descriptions que criamos.

Alguém pode explicar o que diabos acontece nesse código?

Bom, esse código faz com que o controle seja passado para um componente gerado pelo Magritte, que é um formulário com suporte a validação. Se o usuário confirmar o formulário, e se este estiver preenchido adequadamente, um objeto Pessoa é retornado e adicionado numa Collection qualquer. Se o formulário não estiver preenchido corretamente após confirmado, ele é re-exibido para que o usuário corrija as informações incorretas. Por fim, se o formulário for cancelado, o componente responde nil, que é ignorado pelo nosso método.

Poderia parar por aqui, mas o meu objetivo neste post é dar algumas dicas para que você pegue as manhas de se virar sozinho dentro do código do Magritte. Por esse motivo, é importante entendermos o que aconteceu debaixo dos panos.

Primeiro, note a chamada ao método #asComponent no objeto Pessoa recém-criado. Se você der uma olhada no código da classe Object, perceberá que esse método foi adicionado pelo Magritte. E se você continuar navegando pelas chamadas através do Class Browser, acabará caindo nos seguintes métodos:

  1.  
  2. Object#asComponent
  3.     ^ self description asComponentOn: self
  4.  
  5. " … "
  6.  
  7. MAContainer#asComponentOn: anObject
  8.     ^ self componentClass
  9.         memento: (anObject mementoClass
  10.             model: anObject
  11.             description: self)
  12.  

Não é preciso dizer que é de extrema importância que você tenha uma forte base nos conceitos de orientação a objetos e design patterns. Digo isso pois Visitors, Mementos, Template Methods e vários outros design patterns são usados “a dar com pau”.

Mas continuando… quando o método #asComponent é chamado no objeto Pessoa, é feita uma chamada ao método #asComponentOn: na description do objeto Pessoa, passando como parâmetro o próprio objeto Pessoa. Lembre-se que essa description armazena os meta-dados da nossa classe.

O Magritte então cria um Memento do objeto que representa a description do objeto recebido. Então, quando o formulário é enviado pelo usuário, esse Memento utiliza a description para verificar as constraints (caso o formulário adicionado suporte validação - método #addValidatedForm). Se nenhum erro for encontrado, os dados digitados nas descriptions são setados no objeto Pessoa pelo Memento e este objeto Pessoa é retornado através da chamada ao método WAComponent#answer:

  1.  
  2. MAContainerComponent#save
  3.     self validate; commit; answer: self model
  4.  

Para treinar suas habilidades, tente ver se o que eu disse agora pouco é verdade. :P Navegue pelo código usando as ferramentas do Squeak.

Pelo amor de Deus, chega!

Se você conseguiu seguir este “roteiro” do começo ao fim, você certamente já tem uma boa noção de como o Magritte funciona, e já tem uma idéia de como utilizá-lo em conjunto com o Seaside.

É lógico que o Magritte tem mais a oferecer do que apenas essa geração automática de formulários HTML, mas a idéia aqui foi tentar ajudá-lo a se ambientar e encontrar ajuda dentro do código do próprio Magritte. Quanto melhor você conhecer as ferramentas do Squeak — Class Browser e Method Finder, por exemplo — mais fácil será para você navegar pelo código e, conseqüentemente, entender o que acontece ali dentro.

Claro que é bem chato ter de aprender um framework desse jeito, mas infelizmente isso acaba sendo necessário. O Magritte (assim como vários outros frameworks Smalltalk) possui pouca ou nenhuma documentação que facilite a vida dos novos usuários. Uma pena, pois quem perde com isso é a própria comunidade Smalltalk, que deixa de crescer por conta desses problemas.

Tags: , , , , , , , ,