Posts de ‘Tiago Motta’

[Tiago Motta] Padrões de URLs com expressões regulares no Fakeweb

Monday, May 11th, 2009

Ao usarmos o Fakeweb aqui onde trabalho encontramos a necessidade de registrar grupos de URLs de acordo com um padrão. Inspirado no Anselmo Alves, colega de minha equipe que corrigiu um bug nesta mesma gem, alterei-a de forma a permitir o registro de URLs utilizando regex.

Com essa alteração, se por exemplo você quiser que todas as chamadas a um determinado host sejam respondidas com uma determinada string, basta registrar como o mostrado abaixo:

FakeWeb.register_uri(:get, /programandosemcafeina\.blogspot\.com/, :string => "Meu blog")

O principal desafio no desenvolvimento dessa nova funcionalidade foi não quebrar compatibilidade com o uso anterior. Dessa forma, os registros de URLs absolutas passaram a ter uma prioridade maior independente da ordem em que forem registradas. Ou seja, os retornos de acordo com as expressões regulares só são executados se a URL absoluta solicitada não estiver registrada. Veja o exemplo abaixo:

FakeWeb.register_uri(:get, "http://programandosemcafeina.blogspot.com/search/label/rails", :string => "Label Rails")FakeWeb.register_uri(:get, /programandosemcafeina\.blogspot\.com/, :string => "Meu blog")FakeWeb.register_uri(:get, "http://programandosemcafeina.blogspot.com/search/label/ruby", :string => "Label Ruby")

Net::HTTP.get(URI.parse("http://programandosemcafeina.blogspot.com/search/label/rails"))=> "Label Rails"

Net::HTTP.get(URI.parse("http://programandosemcafeina.blogspot.com/search/label/ruby"))=> "Label Ruby"

Net::HTTP.get(URI.parse("http://programandosemcafeina.blogspot.com/search/label/rmagick"))=> "Meu blog"

Já solicitei ao Chris Kampmeier a integração do meu fork, mas enquanto ela não é feita, você pode utilizar a gem gerada diretamente do meu repositório no Github. Basta intalar da seguinte forma:

gem sources -a http://gems.github.comsudo gem install timotta-fakeweb

[Tiago Motta] Undefined method request_uri com HTTParty

Monday, May 4th, 2009

Dica rápida para quem está utilizando HTTParty: Se por um acaso você se deparar com o seguinde erro:

undefined method `request_uri' for #<URI::Generic:0xb03919c>/mnt/apps/filmes/vendor/gems/httparty-0.3.1/lib/httparty/request.rb:56:in `setup_raw_request'/mnt/apps/filmes/vendor/gems/httparty-0.3.1/lib/httparty/request.rb:39:in `perform'/mnt/apps/filmes/vendor/gems/httparty-0.3.1/lib/httparty.rb:153:in `perform_request'/mnt/apps/filmes/vendor/gems/httparty-0.3.1/lib/httparty.rb:119:in `get'/mnt/apps/filmes/app/models/Filme.rb:25:in 'com_tags'

Não se desespere, URI:Generic realmente não possui o método request_uri. O problema é na incialização do HTTParty. Se base_uri estiver nulo qualquer requisição utilizando a classe obterá esse erro. Teste você mesmo algo como:

class A  include HTTParty  base_uri nil  format :xmlend

A.get('/search/label/ruby')

Ao corrigir a inicialização de base_uri o erro deixa de acontecer:

class A  include HTTParty  base_uri 'http://programandosemcafeina.blogspot.com'  format :xmlend

A.get('/search/label/ruby')

O problema é passível de ocorrer principalmente se base_uri for configurável de acordo com o ambiente em que a aplicação estiver rodando.

[Tiago Motta] Esperando o resultado de uma chamada assíncrona no Watir

Thursday, April 16th, 2009

Em alguns casos quando uma determinada requisição assíncrona demora para retornar, o teste de aceitação implementado com Watir pode falhar. Principalmente quando os testes estão rodando em alguma ferramenta de integração contínua.

Para evitar essa falha irritante, o usual é colocar alguns sleeps após as ações que disparam as chamadas ajax. Contudo, isso não garante que o teste não vá falhar, especialmente se o sleep for baixo. Em contrapartida se forem colocado muitos sleeps altos poderá haver uma demora muito grande para rodar todos os testes.

Uma solução possível é esperar que um determinado elemento html seja inserido ou removido da página para então continuar executando os testes. Para isso implementei um método genérico que recebe um bloco de verificação. Veja o exemplo de uso dele:

espera { @browser.text.include? 'Ajax retornado' }

O código do método é bem simples e está descrito abaixo. Ele recebe um bloco assumindo que quando este retornar verdadeiro significa que a espera deve terminar, caso contrário ele continuará esperando e verificando, com um timeout de 30 segundos.

def espera  Timeout::timeout(30) do    while not yield      sleep 1     end  endend

[Tiago Motta] Nomes de métodos e variáveis devem ser no idioma do cliente

Monday, March 16th, 2009

Recentemente Carlos Brando escreveu um excelente artigo em seu blog sobre nomes de métodos e variáveis. Dentre as diversas dicas, uma eu não concordei, comentei lá sobre isso, e o argumento de resposta não me foi convincente.

O ponto de discordância dizia respeito a necessidade de evitar código bilingue. Segundo ele, já que a maioria das linguagens de programação são em inglês, nada mais correto do que escrever o nome das classes e metódos também em inglês. Para começar vou contar uma pequena historinha:

Experiência própria: Laboratório geológico

Há um bom tempo atrás trabalhei em uma empresa que desenvolvia um sistema para um laboratório geológico. Um dia, um dos desenvolvedores do time ligou para tirar duvidas com o cliente e fez a seguinte pergunta “Quando um sample alcança um result no segundo stage em determinado job, ele pode parar os próximos stages ou deve seguir até a conclusão de todos”.

O rapaz na outra ponta ficou completamente confuso. Era novo no laboratório, conhecia toda cadeia de análises de amostras mas não conhecia os estranhos termos que a equipe de desenvolvimento vira e mexe falava. Sample, result e stage não eram palavras de domínio do laboratório, e sim os seus similares em português. Mas como o sistema estava sendo desenvolvido com os termos em inglês, constantemente os membros do time de desenvolvimento, eu incluso, os citava, atrapalhando a comunicação e o entendimento dos problemas.

Eis então a pergunta: Será que um código que mistura termos em inglês e português causa mais problemas que os ruídos na comunicação com o cliente? Pode-se alegar que o código nem será mostrado ao usuário, mas na hora em que o desenvolvedor precisa se comunicar com o cliente, na mente dele ele não está trabalhando com amostras, ele está trabalhando com samples.

Mas o mais interessante é que dentre o domínio do laboratório geológico, um item era em inglês, o Job. Eles falavam amostras, estágio, resultados, mas falavam Job. Isso então nos leva a uma conclusão.

O idioma como ferramenta

A grande questão é que o idioma é apenas uma ferramenta para a comunicação. O primeiro item do manifesto ágil fala “Indivíduos e interação entre eles mais que processos e ferramentas”. No caso, acredito que a interação entre os individuos, no caso a comunicação, é mais importante do que a ferramenta, que no caso é o idioma. Portanto eu prefiro priorizar a comunicação independente do idioma. Se meu cliente possuir termos em seu negócio em francês e outros em alemão, eu prefiro utilizá-los no meu sistema para que ao conversar com ele eu não me confunda com os termos usados na programação.

Um contra-argumento muito usado é dizer que utilizar termos em português em uma linguagem de programação em inglês torna o código sujo. Mas será que isso é realmente verdade?

Código limpo depende do idioma?

Antes de discutir é preciso definir o que é um código limpo. Em minha concepção código limpo é aquele fácil de entender. As técnicas apresentadas pelo Carlos Brando são realmente importantes. Mas será que o seguinte código é complicado de entender?

amostras = Amostra.obter_amostras_com_resultado_positivoamostras.each do |amostra|  puts amostra.produto.siglaend

Será que o fato de utilizarmos inglês misturado com o português tornou mais dificil o seu entendimento? Eu não só creio que não, como acredito que por conta das expressões utilizadas pelo cliente serem idênticas ao do programa, o entendimento fica mais fácil ainda.

Dizer o contrário é o mesmo que defender o uso de arcabouço e chamada de retorno ao invés de framework e callback, em trabalhos acadêmicos somente por uma questão de purismo idiomático. É nesse público que políticos como o deputado Aldo Rebelo se mira, tentando aprovar projetos de lei perigosos.

Mas e o mercado internacional?

Outro ponto levantado para defender o uso de termos diferentes dos do domínio do cliente é a possibilidade de venda do software ao exterior em algum remoto dia. Ao meu entender isso é um pouco de síndrome do mapa de calor. Um problema comum em empresas de desenvolvimento que não seguem o manifesto ágil. Este tipo de argumento futurista era muito utilizado para defender que aplicações fossem escritas em Java. Coisas do tipo “E se um dia quisermos colocar esse software numa geladeira”.

O interessante é que na resposta que recebi, foi citado que para não haver confusão nas conversas com o cliente poderíamos fazer as especificações em português. Mas oras, quando um software é vendido, as especificações vão juntas. Se for vendido para o exterior teríamos o mesmo problema. E pior ainda, as especificações, que dizem exatamente o que o sistema faz estariam em uma lingua diferente da do cliente.

O fato é que a possibilidade de se vender um sistema ao exterior em algum futuro remoto não deve se tornar um empecilho na comunicação com o cliente local, que é quem vai gerar receita mais brevemente. Se um dia realmente a venda for uma possibilidade real faz-se um refactory.

Só para constar, a desculpa para desenvolver o sistema exemplificado no ínicio do post em inglês foi exatamente esta. Hoje, 6 anos depois o foco da empresa mudou totalmente, nenhum outro sistema de laboratório foi vendido ou criado. Somente este continua em manutenção. Dos outros que desenvolvi em Java nenhum precisou trocar de sistema operacional ou mesmo ser instalado em geladeiras.

Enfim a conclusão

O que defendo não é programar em um idioma e não em outro. Defendo que deve-se programar o mais próximo da realidade. Se o seu cliente tiver todos os termos do seu negócio em inglês, ótimo, programe em inglês. Fora isso, vejo apenas dois outros motivos para basear sua programação no idioma britânico: O software que você está desenvolvendo é uma biblioteca ou framework open-source; Ou a maioria dos membros da sua equipe só fala inglês.

[Tiago Motta] Alias e functions para seu dia-a-dia com git

Thursday, March 12th, 2009

Embora o Git seja uma poderosa ferramenta de controle de versão, a quantidade de passos que é necessário para executar alguns procedimentos básicos pode dificultar um pouco o dia-a-dia do desenvolvedor. Principalmente se o projeto em questão não requer todas as funcionalidades que o git oferece para compartilhar código entre grandes equipes espalhadas.

Felizmente é possível criar alguns alias e functions para facilitar o seu uso. Dessa forma podemos utilizar o git de maneira mais simples, e quando houver a necessidade de usar um dos seus recursos avançados, basta não usar esses atalhos. Segue abaixo os que tenho utilizado:

gitshazam

Adiciona todos os arquivos criados, modificados, deletados e depois comita para o repositório local com a mensagem informada.

alias gitshazam='git add -u && git add . && git commit -m'

Exemplo de uso:

gitshazam 'Minha mensagem de commit'git push

gitcoleradodragao

Sincroniza com o repositório remoto as informações dos branchs existentes e os exibe, mostrando também em que branch você está desenvolvendo atualmente.

alias gitcoleradodragao='git fetch && git branch -a -v'

gitpodediamante

Cria um branch local com o nome informado e o associa ao branch de mesmo nome no repositório remoto.

function gitpodediamante() { git checkout --track -b $1 origin/$1; }

Exemplo de uso:

gitpodediamante 'historia-22'

Agradecimentos especiais à galera da minha equipe que tem me ajudado bastante com o git e aguentado minhas reclamações.

[Tiago Motta] Duck typing e os testes de aceitação automáticos

Thursday, February 26th, 2009

Uma discussão frequente em minha equipe é sobre maneiras de garantir que nossos sistemas estejam funcionando adequadamente por meio de testes automáticos. Em nossos ultimos projetos temos conseguido alcançar 100% de cobertura de testes unitários, e nos sistemas legados temos aumentado a cobertura aos poucos.

No entanto, estamos cientes de que nem 100% de cobertura de testes unitários garantem a ausência de bugs. Isso nos remete aos testes de aceitação automáticos, não só por causa do funcionamento das telas e fluxos de navegação, mas também dos possíveis problemas que um refactoring com um pouco menos de atenção pode causar em uma linguagem baseada em duck typing.

Darei um exemplo em ruby para ilustrar um bug que pode ser introduzido sem que seus testes unitários percebam. Digamos que você tenha as seguintes classes:

class Filme < ActiveRecord::Base do  def alugar    self.estoque.remover self  endendclass Estoque < ActiveRecord::Base do  def remover(filme)    # ...  endend

No seu spec que verifica o método alugar da classe Filme você colocaria um mock mais ou menos como mostrado abaixo:

@estoque = mock_model(Estoque)@estoque.should_receive(:remover).with(@filme).once

Um dia você percebe que o nome do método remover não está explicando muito bem o significado, e resolve fazer um refactoring renomeando-o para remover_fita_do_filme.

Você altera os testes da classe Estoque, renomeia o método e logo depois recebe uma ligação de uma empresa de telefonia te oferecendo serviços excepcionais que você nunca precisou. Puto da vida você desliga o telefone, roda os testes e todos passam! Serviço feito!

Perceberam o problema? Sem um teste de aceitação automático ou no mínimo um teste manual, você não perceberia que a classe Filme continua referenciando o método antigo, remover, da classe Estoque. Esse tipo de problema não passaria em uma linguagem que não segue duck typing, pois a etapa de verificação de código da compilação serviria como uma espécie de teste unitário de tipos e nomes.

Um problema mais evidente seria um método que recebe um objeto que precisa ter determinado método, ou seja implementar determinada interface. Veja o exemplo abaixo, que mostra que ao alugar um item, no caso um Filme, adiciona-se todas as tags dele às tags preferenciais do cliente:

class Cliente < ActiveRecord::Base do  def alugar(item)    #...    adiciona_tags_preferenciais item.tags  endendclass Filme < ActiveRecord::Base do  def tags    # ...  endend

Se mudarmos o nome do método tags em Filme e também corrigirmos o uso de dele no método alugar da classe Cliente tudo funcionará bem. Mas, como usamos duck typing não temos como garantir que outros objetos, digamos alugáveis, tenham sido alterados. Se por exemplo esta locadora também alugas livros, teríamos um erro evidente:

class Cliente < ActiveRecord::Base do  def alugar(item)    #...    adiciona_tags_preferenciais item.tags_principais  endendclass Filme < ActiveRecord::Base do  def tags_principais    # ...  endendclass Livro < ActiveRecord::Base do  def tags    # ...  endend

Esse tipo de erro só seria pego num teste manual se lembrássemos de testar o aluguel de livros também. Por isso é tão importante o teste de aceitação automático em uma linguagem com duck typing. Em Java por exemplo, que a interface precisa ser explícita, o erro seria pego no que poderíamos considerar o teste unitário que a compilação executa. As classes Filme e Livro implementariam a interface Alugavel por exemplo:

public interface Alugavel {  List tagsPrincipais();}

Mas é claro que o uso de uma liguagem menos dinâmica não remove a necessidade de testes de aceitação automáticos. O valor desse tipo de testes é independente da linguagem, e sua implementação é importantíssima para garantir fluxos de navegação e integração entre os diversos componentes do sistema.

[Tiago Motta] Plugin do rails para copiar erros de um model para outro

Thursday, February 19th, 2009

Em um projeto pessoal precisei desenvolver uma maneira de copiar os erros de um model para outro. Como é uma funcionalidade que outrora já havia precisado, aproveitei para criar um plugin e disponibilizá-lo para quem mais tiver esse mesmo problema.

O plugin está disponível no GitHub pelo endereço http://github.com/timotta/copy_errors_from/tree/master. Para instalar no seu projeto basta rodar a seguinte linha:

script/plugin install git://github.com/timotta/copy_errors_from.git

Após instalar todos os seus models terão o método copy_errors_from, que pode ser utilizado como mostrado abaixo:

> filme = Filme.new :titulo => 'Corra que a polícia vem aí'> ator = Ator.new> filme.atores.push ator> filme.save #return false> filme.errors.entries #return []> ator.errors.entries #return [['nome','Não pode ser vazio']]> filme.copy_errors_from ator> filme.errors.entries #return [['ator_nome','Não pode ser vazio']]> filme.errors.on(:ator_nome) #return 'Não pode ser vazia'

[Tiago Motta] Cuidado ao cachear named_scope no Rails

Friday, January 23rd, 2009

Deve-se tomar cuidado ao cachear o acesso a um named_scope pois ele tem um carregamento de forma preguiçosa, o famoso lazy load. Assim, o código abaixo gravaria no cache não os atores femininos, mas sim todos os atores.

class Filme    #...  def atores_femininos_cacheado    Rails.cache.fetch("#{self.id}:atores-principais") { self.atores.femininos }  end

class Ator  #...  named_scope :femininos, :conditions => { :sexo => 'F' }

Se você observar o log, verá que o rails executará a query sem as condições definidas na classe Ator para o named_scope :femininos. E que após ser cacheado, será essa a query que deixará de ser executada.

A query para buscar atores femininos do named_scope só será realmente executada quando algum método do objeto retornado pelo método .atores.femininos for chamado. Veja no exemplo abaixo:

@filme = Filme.find_by_id 10filme.atores_femininos_cacheado.last

Ou seja, o cacheamento não está servindo ao propósito definido. Uma solução para isso é cachear diretamente o resultado de um find, como pode ser visto abaixo:

class Filme    #...  def atores_femininos_cacheado    Rails.cache.fetch("#{self.id}:atores-principais") do      Ator.find_all_by_filme_id self, :conditions => { :sexo => 'F' }    end  end

[Tiago Motta] PGError com Rails no Heroku

Wednesday, January 21st, 2009

Essa dica é para quem utiliza o Heroku Garden para desenvolver suas aplicações rails. Ao rodar os specs diretamente da interface web do Heroku pode ser que um dia você se depare com o seguinte erro:

PGError: ERROR: relation "filmes" does not exist: SELECT a.attname, format_type(a.atttypid, a.atttypmod),d.adsrc, a.attnotnullFROM pg_attribute a LEFT JOIN pg_attrdef dON a.attrelid = d.adrelid AND a.attnum = d.adnumWHERE a.attrelid = 'filmes'::regclassAND a.attnum > 0 AND NOT a.attisdroppedORDER BY a.attnum

O erro acontece porque o link para rodar as migrations, que aparece na interface do heroku, não as executa no ambiente de testes. Portanto, é preciso abrir a tela do rake e executar as seguintes linhas antes de rodar seus specs.

db:migratedb:test:prepare

UPDATE

Descobri outro problema do Heroku. O schema.rb é criado e alterado no diretório tmp. Dessa forma para que o PGError não dê novamente em outra atualização do banco, você precisa copiá-lo para o diretório db e então no console do rake rodar:

db:test:clone

[Tiago Motta] Imagem de fundo no captcha gerado pelo simple_captcha

Thursday, October 9th, 2008

O simple_captcha, plugin de geração de captcha para rails, não possui opção para colocar um imagem de fundo na imagem gerada. Mas fazer isso não é nenhum bicho de sete cabeças, basta alterar algumas partes chaves do plugin utilizando um pouco de rmagick. Vou mostrar aqui mais ou menos como fiz isso em um recente projeto.

No arquivo simple_captcha_image.rb basta alterar o método generate_simple_captcha_image para que na criação da imagem seja colocado o fundo desejado. Veja o exemplo de código abaixo:

def generate_simple_captcha_image(options={})  #:nodoc

  fundo = Magick::Image.read(url_da_imagem_de_fundo).first  preenchimento = Magick::TextureFill.new(fundo)

  @image = Magick::Image.new(197, 45, preenchimento) do     self.format = 'JPG'  end

  # ...

Repare que existe ali uma chamada ao método url_da_imagem_de_fundo que no caso é um método que pega o path de uma imagem aleatória. Dessa forma o captcha poderá ter vários fundos diferentes. Veja um exemplo de como pode ser a implementação desse método:

  def url_da_imagem_de_fundo    "#{RAILS_ROOT}/vendor/plugins/simple_captcha/assets/imgs/picture_" + (rand(9)+1).to_s + ".jpg"  end

Foi preciso também comentar a chamada ao método que aplica estilo e distorção à imagem, mas não sei bem se isso é necessário. Como fiz isso, precisei alterar o método append_simple_captcha_code que escreve o texto na imagem, de forma a utilizar diversos tamanhos, posições e fontes diferentes.