/* CODIFICANDO */

Relatos de um programador em contínua aprendizagem.

Archive for Setembro 18th, 2008

named_scope – combinando buscas

sem comentários

Não há dúvidas de que o named_scope é um dos recursos mais úteis dentre todas as novas funcionalidades do Rails 2.1. O que nem todo mundo sabe é que o named_scope serve para muito mais do que simplesmente criar finders mais “bonitinhos”. A opção mais utilizada dentro do named_scope continua sendo :conditions, mas com um pouco de criatividade podemos criar named_scopes que, quando combinados, nos permitem fazer buscas bastante complexas.

A princípal funcionalidade do named_scope é possibilitar que você concatene seus métodos e vá refinando sua busca através de uma cadeia de chamadas aos scopes. Funciona como se você estivesse sempre chamando o scope sobre a própria classe que define o model. Um exemplo:

class Camiseta < ActiveRecord::Base
  named_scope :vermelhas, :conditions => ["cor = 'vermelha'"]
end

E podemos usar isso assim:

v = Camiseta.vermelhas

Podemos melhorar isso e criar um named_scope que retorna camisetas de uma cor específica

named_scope :da_cor, lambda { |cor| { :conditions => ["cor = ?", cor] } }

Porque agora precisamos do lambda? Porque os named_scopes são definidos no momento em que a classe é carregada pelo interpretador Ruby. Se você não usar um lambda, não será possível definir o valor de retorno do bloco em tempo de execução e o scope acabará sendo sempre o mesmo, estático. Agora podemos fazer assim:

azuis = Camiseta.da_cor('azul')

E se quisermos pesquisar todas as camisetas e ordenar os resultados pela ordem alfabética das cores? Podemos criar um named_scope para isso também

named_scope :ordenado_por_cor, :order => 'cor'

E podemos usar assim:

camisetas = Camiseta.ordenado_por_cor

Vamos avançar mais um pouco. Quero agora poder escolher o campo pelo qual os resultados devem ser ordenados.

named_scope :ordenado_por, lambda { |coluna| { :order => coluna } }

E podemos usar assim

camisetas = Camiseta.ordenado_por(:cor)

Se agora no model Camiseta eu tiver também uma coluna para o preço, podemos combinar nossos named_scopes para fazer uma busca por camisetas de uma cor específica, ordenadas por preço.

amarelas = Camisetas.da_cor('amarela').ordenado_por(:preco)

Praticamente todos os parâmetros que você pode usar em um ActiveRecord::Base.find você pode usar também nos named_scopes, sendo possível gerar diversas buscas combinadas como a descrita acima.

Escrito por cassiomarques

Setembro 18, 2008 em 10:02 pm

Publicado em rails, ruby

Factory Girl para models com has_many

com 4 comentários

A idéia de usar fixtures para os testes no Rails parece muito boa em um primeiro momento, porém quando começamos a querer testar models com relacionamentos muito complexos manter essas fixtures se transforma em um verdadeiro inferno. Para cada fixture você acaba tendo que abrir vários arquivos e verificar na raça os valores dos ids referenciados nas demais fixtures associadas. É muito fácil se perder. Pior ainda, é totalmente improdutivo.
Para testar controllers esse problema pode e deve ser solucionado através do uso de algum framework para mocking, como o Mocha ou os recursos nativos de mocking do RSpec. Assim diminui-se a dependência dos models nos testes dos controllers e os testes executam mais rápido. Mas para os models nem sempre “mockar” todo o comportamento é uma boa solução. Muitas vezes você vai querer testar tudo que ocorre entre seus objetos e o banco de dados, incluindo os relacionamentos anteriormente citados.
Para os testes dos models tenho substituído as velhas fixtures pela utilização do factory_girl.
O que o factory_girl faz é possibilitar a criação de fábricas de objetos. Com essas fábricas, você pode gerar objetos de forma simplificada durante seus testes, elimando complemente a necessidade de utilização das fixtures. E com diversas vantagens.

Bom, eu não vou ficar falando sobre como usar o factory_girl, porque isso é realmente MUITO simples. Caso você ainda não conheça o factory_girl, basta olhar a documentação no site do projeto que com certeza você vai se virar bem.

O que eu gostaria de falar aqui é sobre como criar associações quando temos declaração do tipo :has_many em um dos nossos models. Eu tive que fuçar um pouquinho na net para conseguir encontrar essa informação, então talvez seja útil para mais alguém.

O fato é que cada vez que fazemos algo como

Factory.define :post do |p|
  p.title "Blabla"
  p.author { |author| author.association(:user) }
end

estamos apenas usando um proxy que usa uma espécie de “lazy loading” para instanciar objetos a partir da fábrica de Users. Logo, se você possui um model User com uma declaração do tipo belongs_to :post, a coisa vai funcionar e pronto. Mas e quando tenho algo como

class Post < ActiveRecord::Base
  has_many :comments
end

como podemos “fabricar” uma instância de Post já com uma lista de comentários?

Assim (supondo que já exista a factory para o model Comment):

Factory.define :post do |p|
  p.title "Blabla"
  p.author { |author| author.association(:user) }
  p.comments do |c|
    [c.association(:comment), c.association(:comment)]
  end
end

Perceberam? Cada vez que chamo o método association e passo o parâmetro :comment estou fazendo com que o factory_girl use a factory criada para o model Comment e gere uma nova instância desta classe, a qual é retornada como um elemento dentro de um array. Está feita minha fábrica com um relacionamento do tipo um-para-muitos.

Escrito por cassiomarques

Setembro 18, 2008 em 1:26 am

Publicado em rails, ruby, testes