Catucando no I18n do rails

Publicado por joaolins, Fri Aug 08 18:48:00 UTC 2008

Depois de dar uma olhada no artigo do Carlos Brando e do Diego Carrion, resolvi dar uma catucada no I18n. Comecei com um exemplo simples citado nos blogs dos mesmos. Resolvi ir um pouco mais longe, usar o ActiveRecord e tentar internacionalizar mensagens pré-customizadas do rails e atributos das tabelas.

Bom, o resultado do meu arquivo de internacionalização foi esse:

Depois com seu arquivo de internacionalização pronto você pode criar um scaffold (scaffold é ótimo para exemplos/testes):
script/generate scaffold post title:string description:text author:string
Adicione algumas validações no model post:
class Post < ActiveRecord::Base
  validates_presence_of :title
  validates_length_of   :author, :within => 3..10
end

Depois de rodar os migrations acesse a URL de posts http://localhost:3000/posts/new tente criar um post sem digitar nada nos campos e veja todas as mensagens devidamente traduzidas.

Para traduzir os atributos da sua tabela/model você deverá usar algo como:
:human_attribute_names => { 
      :post => { 
        :title => "Título" 
      } 
}
Se você quizer ser mais especifico para uma determinada validação de um atributo você pode fazer isso da seguinte forma:
:active_record => {
  :error_messages => {
     :custom => { 
        :post => { 
          :title => { 
            :blank => "erro especifico para título em branco no post" 
        }
      }
    }
 }
}
#observe que isso tem que ser feito dentro do 
#contexto do active_record.error_messages

Nota: Para minha surpresa acabei descobrindo um bug que acontece quando você não especifica mensagens default para o ActiveRecord no seu arquivo de localização. O que acontece é que o método full_messages do ActiveRecord procura por mensagens padrões para os validations e quando não encontra a chave, ele retorna um Array ao invés de uma String, por consequência o error_messages_for usa o full_messages esperando dele uma mensagem string, mas recebe esse bendito Array que não deveria ter sido retornado e ao concatenar a string com o Array dá pau. O erro na minha opinião é bem bobo mais é uma coisa que deve ser tratada…

Mais informações sobre I18n: http://www.artweb-design.de/2008/7/18/the-ruby-on-rails-i18n-core-api

3 comentários | Tags: catucando Rails rails

Escreveu mas não leu

Publicado por thiago.arrais, Wed Jul 23 12:09:00 UTC 2008

Uma boa forma de aprender é simplesmente observar quem já sabe. Muita gente sabe disso e sabe tomar proveito do trabalho de quem veio antes. Bons pintores gostam de admirar boas telas, bons músicos geralmente são apaixonados por simplesmente ouvir música. Escritores, arquitetos, engenheiros e marceneiros todos aprendem através dos trabalhos dos mestres do passado. Ora, quem quiser ser médico é até obrigado a trabalhar algum tempo observando quem já sabe.

É certo que muitas vezes os aprendizes de médico são simplesmente usados no lugar de profissionais experientes como mão de obra mais barata e que isso não é exclusivade da área deles, mas isto é assunto para outro texto. Em outro blog.

Isto certamente acontece em qualquer área do conhecimento, mas é flagrante como programadores não gostam de ler código dos outros. Nós não fomos treinados para isto. É bem possível passar quatro ou cinco anos num curso de graduação na área de computação e não ler sequer um programa escrito pelos professores. Não falo de exemplos didáticos, como a definição do QuickSort em duas linhas de Haskell, mas de programas vivos de verdade que andam e respiram. Algo como o compilador GHC.

A triste verdade é que a maioria de nós não lê código dos outros, a não ser quando somos obrigados a mantê-lo. Mais ou menos como a grande parte das pessoas não lê mais nenhum livro depois que conclui a escola e não precisa mais ler clássicos de Machado de Assis ou de José de Alencar para passar nas provas. Dada a imensa quantidade de código livre de que dispomos hoje em dia, isso é de espantar. Não é escassez de código para leitura, quer dizer que nós simplesmente não nos damos ao trabalho de ler. Ao invés disso, preferimos espernear e xingar quando precisamos ler o código de alguém, só porque a pessoa não usou a linguagem que preferimos, os padrões que preferimos, o estilo de endentação que preferimos. Só porque não escreveu como nós escreveríamos. Enfim, só porque não fomos nós que escrevemos.

Adivinha só! O código provavelmente não está tão ruim quanto pensamos, nós é que não estamos acostumados a ler código dos outros. Então, pelo bem de todos os programadores, vamos levantar nossos traseiros e começar a ler um pouco mais de código. De quebra ainda vamos aprender mais.

1 comentários | Tags: estudo leitura de codigo

2º ESOL - Palestra: Ruby on Rails colocando a web nos trilhos

Publicado por joaolins, Sat Jul 05 13:56:00 UTC 2008

Essa semana participei do II ESOL que aconteceu no CEFET-PE. Foi a minha primeira paticipação em um evento de software livre. Tentei passar um pouco do mundo Ruby e Rails com o intuito de consquistar alguns adeptos e fortalecer a nossa comunidade local. A palestra foi nos moldes do DHH. Fiz uma breve introdução ao Ruby e um hands on com Rails para mostrar as facilidades de utilizar o framework.

O evento estava muito bom e só tenho a agradecer ao pessoal da organização pelo apoio e atenção que me deram durante todo tempo que estive por lá.

O código da aplicação para quem quizer brincar.

0 comentários | Tags: Palestra Rails rails

encontrando uma sarna para se coçar

Publicado por joaolins, Tue Dec 18 19:36:00 UTC 2007

Muita gente se beneficia de software open source e ferramentas livres. Quando isso acaba se tornando constante pode acabar batendo aquele sentimento de que é necessário dar alguma coisa em troca, contribuir. É ruim pensar que existem pessoas trabalhando por trás daquilo e você está simplesmente mamando. Isso certamente não passa pela cabeça de todo mundo, mas provavelmente da maioria dos mineradores.

Bom, eu vou contar a história que aconteceu comigo.

Tudo começou quando tive um pequeno problema com um sistema que estou fazendo com Rails. Lá é preciso fazer upload de um arquivo XML em um dado momento. Até ai tudo bem:

result = xml_parser.parser(params[:file_upload])

Isso funcionou bem. Todos os testes passaram e eu nem me preocupava com o tipo de objeto que vinha dentro do params[:file_upload], tinha em mente que sempre seria um TempFile.

Qual não foi minha surpresa quanto foi feito o deploy em produção: BOMBA! Acabei descobrindo que, nas versões de Ruby anteriores à 1.8.6, somos obrigados a chamar open no TempFile.

result = xml_parser.parser(params[:file_upload].open)

Aparentemente é uma modificação bem pequena. Desta vez a surpresa foi um pouco menor, já que aconteceu em ambiente de testes. Mas não pude deixar de me assustar quando um erro saltou na minha cara:

NoMethodError: private method `open' called for #<StringIO:0xb7c35020>

Depois da mão na cabeça, veio naturalmente um pouco de depuração e inspeção. Notei que o params[:file_upload] estava retornando um StringIO e que a chamada ao método open, que na documentação do ruby core aparece como publico e na verdade é privado, dava o erro acima. Resolvi tirar a história a limpo. Dei uma lida no código do Rails e acabei descobrindo que se o upload for de arquivos menores do que 10KB um StringIO é retornado e não um TempFile como eu esperava.

Neste ponto eu já sabia a razão de todos os meus problemas e só faltava resolver, o que fiz foi o seguinte:

if params[:upload_file].instance_of?(StringIO)
    result = xml_parser.parser(params[:upload_file])
else
    result = xml_parser.parser(params[:upload_file].open)
end

Isso funciona para todas as versões de Ruby e tamanhos de arquivo, mas eu não fiquei satisfeito com a solução porque não a achava limpa o bastante e porque esse comportamento do Rails não era documentado.

Meu código estava funcionando, mas eu não estava satisfeito. Lembra da história de contribuir e parar de mamar? Pensei: “é hora de largar a teta!” Eu estava disposto e há algum tempo já vinha procurando sarna para me coçar, foi só procurar um pouco na internet para saber o que eu precisava fazer para contribuir com o Rails. Encontrei os slides encorajadores de Josh Susser e arregacei as mangas para por as mãos à obra.

Depois de ter lido o código, eu sabia exatamente como resolver o problema. Escrevi um email para a rails-core falando do problema e fazendo uma proposta para que o Rails retornasse apenas TempFile, ao invés de tentar fazer uma otimização que pode fazer muita gente ter que coçar a cabeça para resolver . Alguns emails rolaram na lista e o primeiro a me responder foi o Michael Koziarski que escreve para o The Rails Way.

Koz achou a minha proposta interessante, mas ele não conseguiu entender que tipo de bug aquela otimização poderia resolver. Enviei uma resposta e tentei explicar tudo de forma mais detalhada em outro email para a lista. Foi quando Jonathan Yurek me respondeu que todos os meus problemas poderiam ser solucionados de uma forma bem simples usando o poder libertador do duck typing:

result = xml_parser.parser(params[:upload_file].read)

A solução proposta pelo Yurek funciona perfeitamente. Eu não tinha prestado atenção, mas o método read que funciona no StringIO, também pode ser usado para o TempFile, pois ele é um Delegate de File que herda de IO e possui o método read.

Depois disso Koz respondeu que ainda acharia interessante que isso fosse documentado em algum lugar da API do Rails e que ele poderia aplicar a modificação caso alguém fizesse o patch. Hongli Lai também escreveu para a lista informando sobre um post que fez em seu blog ao ter um problema bem parecido com o que eu tive e pareceu solidário a minha proposta.

Ainda não mandei o patch para deixar claro o comportamento do Rails ao fazer uploads. Explicar esse comportamento não é difícil. Na verdade o maior problema é saber exatamente o melhor local para colocar a informação. Alguma sugestão?

1 comentários | Tags: Dicas dicas Rails rails

Objetos especializados com eigenclass

Publicado por thiago.arrais, Thu Dec 13 00:27:00 UTC 2007

Todo o código incluído neste texto foi projetado para ser executado interativamente no interpretador irb. Você pode copiar os trechos de código daqui para o interpretador na ordem em que aparecem e tudo deve funcionar como esperado. Mas digo logo que eu não garanto que isso aconteça, então use por sua conta e risco.

Todo mundo que programa em Ruby já deve ter definido métodos em classes singleton ou, para usar um termo mais na moda, em eigenclasses. Mesmo quem não sabe o que diabos é isso muito provavelmente já as usou sem saber. Até há bem pouco tempo atrás eu não tinha a mínima idéia da existência dessas coisas, mas um dia precisei usá-las. Mais ou menos como todo mundo que programa em Haskell acaba um dia escrevendo um tutorial sobre Monads, todo mundo que tenta um pouco de metaprogramação em Ruby acaba escrevendo seus próprios artigos sobre eigenclasses. Essa é a minha tentativa de tornar o assunto um pouco menos obscuro para mim mesmo e para os demais. Depois eu conto o que estava querendo fazer, vamos primeiro tentar entender essas classes diferentes.

Primeiramente, as classes singleton não têm muito a ver com o padrão Singleton. O termo provavelmente foi escolhido pela alusão à unicidade, mas as similaridades páram por aí. Classes singleton são classes que, como no padrão, estão associadas a um único objeto. Porém, diferente da implementação comum do padrão, o objeto não é instância dela, mas de outra classe. O maior problema com o termo "classe singleton" é a confusão com o nome do padrão, por isso muita gente tem procurado usar outros termos, como o diferentíssimo eigenclass (que até hoje eu não sei se deu ou roubou o nome do blog do Mauricio Fernandez).

Em Ruby todo objeto possui uma eigenclass associada (para ser realmente preciso, alguns objetos não possuem, mas vamos assumir que não sabemos disso por enquanto). Os métodos definidos nesta classe especial ficam disponíveis somente para o objeto associado e para nenhuma outra instância da mesma classe. Para obter acesso a ela, pode-se usar a expressão "class << obj; self; end" (onde obj é o objeto do qual se quer a eigenclass). Quem pretende usar este tipo de metaprogramação com freqüência pode fazer como o why e guardar esta expressão num método em um arquivo qualquer para referência futura:

class Object
  def eigenclass
    class << self; self; end
  end
end

A eigenclass é única para cada objeto e diferente da classe a partir da qual o objeto foi criado.

a = Object.new
b = Object.new
a.class == Object # => true
a.eigenclass == a.class # => false
a.eigenclass == b.eigenclass # => false
a.eigenclass # => #<Class:#<Object:0xb7ced878>>

Isso quer dizer que métodos adicionados e/ou modificados a estas classes estarão disponíveis para um objeto apenas. Isso é usado com alguma freqüência para definir métodos de classe, algo que todo mundo já fez uma vez na vida. Classes são objetos também, instâncias da classe Class, e possuem uma eigenclass associada como a maioria dos objetos em Ruby. Por isso podemos definir métodos de classe:

MyClass = Class.new

MyClass.eigenclass.send :define_method, :instance_count do
  @count || 0
end

MyClass.eigenclass.send :define_method, :instance_count_inc do
  @count = instance_count + 1
end

MyClass.send :define_method, :initialize do
  self.class.instance_count_inc
end

a, b = MyClass.new, MyClass.new
MyClass.instance_count # => 2

Muito para engolir de uma vez só? Eu também achei no início, mas descobri que fica mais fácil quando se tenta entender uma coisa de cada vez. A primeira é o método define_method da classe Module (lembre que a classe Class herda de Module, portanto este método também está disponível para classes). Este método pode ser usado no lugar da palavra-chave def para (re)definir métodos de instância dinamicamente. Ele recebe um nome e um bloco para ser usado como corpo do método. Este é um método privativo, ou seja, só pode ser chamado sobre um objeto por ele mesmo. Para contornarmos esta limitação usamos o send (por isso ele aparece ali em cima).

Uma sutileza do define_method é que ele define métodos acessíveis a partir das instâncias da classe, não da classe em si. A classe MyClass é instância de outra classe diferente (a classe Class, como está explícito na primeira linha do bloco de código anterior) e se quisermos definir um método para ela, podemos chamar o define_method em Class:

Class.send :define_method, :instance_count do
  @count || 0
end

O problema dessa abordagem é que define o método para todas as instâncias da classe Class.

MyClass.instance_count # => 2
Object.instance_count # => 0
Module.instance_count # => 0

Obviamente existem instâncias da classe Object (posso contar pelo menos sete só nos dois trechos de código acima), mas o contador de instâncias não funciona direito porque precisa ser incrementado durante a inicialização dos objetos. Isso pode causar um pouco de confusão e polui o código das classes que não precisam do recurso de contagem. Uma coisa que realmente não queremos é poluir os programas dos outros sem necessidade, então é melhor darmos um jeito de definir o método somente para as classes em que vamos usá-lo.

O problema é que se chamarmos define_method nas classes em si, estaremos definindo métodos para as instâncias delas e o contador não vai ter muita utilidade. Por outro lado, se usarmos o define_method de Class, vamos injetar código em classes demais. Para nos tirar dessa sinuca de bico, eis que surge a magnânima eigenclass. Antes de entender o que está acontecendo, vamos limpar a sujeira:

Class.send :remove_method, :instance_count
Object.instance_count # NoMethodError
MyClass.instance_count # => 2

Como pode ver, isso remove o método instance_count de todas as classes, exceto da MyClass. Ela é poupada porque tem o método definido na sua eigenclass, uma classe intermediária que não aparece na hierarquia de classes, mas que é consultada antes da classe real e das superclasses quando uma chamada de método é recebida. Por isso que no trecho de código original em que definimos o instance_count as duas primeiras definições são solicitadas à eigenclass, não à classe. Por outro lado, a última definição é feita para a própria classe MyClass, para redefinir o método de inicialização de suas instâncias de modo a aumentar o contador.

Tudo isto poderia ter sido feito com o uso da palavra-chave def e do atalho def objeto.nome_do_metodo, mas resolvi usar define_method porque ilustra como gerar métodos dinamicamente (e também porque é mais divertido, ora bolas). A palavra-chave def pode ser usada para definir métodos em qualquer objeto (não só classes) e o define_method também, mas por que alguém em sã consciência iria querer fazer isso?

Bom, há várias razões, mas vou descrever apenas o uso que citei no início do texto. No Mediacloth há uma classe responsável por resolver nomes de página wiki para URLs que geralmente é usada somente para ter um dos métodos sobre-escritos. Sem a magia negra de eigenclass os clientes vão normalmente estender esta classe e definir um trecho de código personalizado para este método:

class MyLinkHandler < MediaWikiLinkHandler
  def link_attributes_for(page)
    { :href => "http://example.com/wiki/#{page}", :class => "internal" }
  end
end

generator = MediaWikiHTMLGenerator.new
generator.link_handler = MyLinkHandler.new

Não seria bom se o resolvedor de referências pudesse ser definido com uma chamada de construtor apenas?

generator = MediaWikiHTMLGenerator.new
generator.link_handler = MediaWikiLinkHandler.new do |page|
  { :href => "http://example.com/wiki/#{page}", :class => "internal" }
end

Esse efeito pode ser obtido com uma alteração simples no initialize da classe original. O código fica parecido com o que está aí embaixo:

class MediaWikiLinkHandler
  def initialize(&block)
    eigenclass.send(:define_method, :link_attributes_for, block) if block
  end
end

Com Ruby 1.9 tudo isso vai ficar muito mais fácil, já que o método define_singleton_method (que já é usado por muita gente) será incluído na distribuição padrão, como o Taq já notou. De qualquer modo, este exercício ainda continua válido como forma de aprendizado. É para isto que estamos aqui, não?

1 comentários | Tags: cutucando metaprogramação

Vamos minerar...

Publicado por thiago.arrais, Wed Nov 14 12:42:00 UTC 2007

Você tem garimpado ultimamente?

Não, apesar de não ser uma idéia ruim para garantir uma graninha extra, esse texto não é sobre gente que procura ouro nas impurezas no leito de um rio nem sobre minas de diamante. O garimpo de que estamos falando é em busca de algo que é tão valioso quanto ouro e é muito mais fácil de conseguir, mas que é muito mais difícil de guardar: conhecimento.

Conhecimento não pode ser depositado num cofre de banco nem esquecido no fundo de uma gaveta junto com alguns papéis velhos. Assim que se tenta fazer isso, ele começa a se desmanchar. Ele não pode ser guardado e recuperado facilmente depois de um tempo, precisa estar em constante evolução. Precisamos sempre ter certeza de que nosso conhecimento está evoluindo, porque se não estiver, estará diminuindo. Funciona mais ou menos como uma motocicleta: quando está indo em frente, está tudo bem, mas começa a cair no instante que pára. Conhecimento que não é usado e exercitado também começa a se desmanchar, desaparecer. Você lembra das aulas de biologia de quando tinha 13 anos?

Não? Então acho que deu para entender…

Porém, diferentemente dos bens materiais, o conhecimento não precisa ser fisicamente transferido para ser repassado. Se você der um pouco do seu ouro para um amigo, no final terá necessariamente menos ouro do que antes. Mas se você ensinar algo a ele, não deixa de saber nada do que já sabia. Na verdade é bem possível que passe a entender melhor o assunto depois de ter ensinado outra pessoa.

Por isso uma das melhores formas de garantir que o conhecimento não fique parado minguando é distribuí-lo. Quando você ensina outra pessoa, acaba aprendendo mais. Não porque acontece alguma mágica inexplicável, mas porque mais pessoas geram mais idéias e o conhecimento se alimenta de idéias. Quanto maior e mais diversa for a comunidade em torno de um assunto, mais conhecimento é gerado. Isso acaba formando um ciclo de crescimento exponencial: mais pessoas geram mais idéias e mais idéias atraem mais pessoas. Muita gente já aprendeu isso e inventa todo tipo de esquema milaborante para aproveitar este efeito.

O Minerama (este blog que você está lendo agora, se ainda não notou) é uma tentativa egoísta e despudorada de tirar proveito deste ciclo de aprendizado por parte de João Paulo Lins e Thiago Arrais, dois programadores que se encontraram por meio do grupo recife.rb. Este blog é apenas mais um esquema para multiplicar conhecimento. Como diria o famoso médico Dráuzio Varella, “podemos [...] afirmar que escrever é uma forma salutar de ampliar nosso banco de dados.” Por meio da escrita tentamos registrar um pouco do nosso conhecimento para que possamos esquecê-lo temporariamente e permitirmos que um conjunto de informações dê lugar a outro. Além disso, a escrita tem um efeito colateral de facilitar o compartilhamento. Com o tipo de comunicação escrita que vai ser usada aqui não precisamos estar reunidos no mesmo lugar para ensinarmos uns aos outros.

Esta iniciativa não é nada altruísta. Solidariedade não é o objetivo principal aqui, mas simplesmente sabemos que é mais fácil e mais rápido aprender em conjunto do que tentar sozinho. Apesar do blog ter sido criado para proveito próprio, talvez este pequeno púlpito seja interessante para outras pessoas também. Nossa idéia é que compartilhemos aqui um pouco do que aprendermos e que possamos aprender com quem se dispuser a oferecer um pouco de sabedoria. Assim todo mundo vai poder se fartar nesse grande banquete de conhecimento que estamos tentando criar.

O assunto de todos os dias por aqui vai ser Ruby e coisas rúbicas em geral. Para quem não conhece Ruby, poderíamos dizer que é uma linguagem de programação dinâmica, com duck typing e continuar falando por horas. Mas isto não seria mais interessante do que experimentar você mesmo.

1 comentários | Tags: blogs