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?
