Um pouco mais sobre named_scopes
Um pouco mais? Mas cadê o primeiro artigo sobre isso? Bom, não o escrevi, mas vou partir do ponto em que parou o Nando Vieira em seu artigo sobre named_scopes. Logo, assumo que você já sabe o que é um named_scope e conhece algumas possibilidades, como condições dinâmicas e encadeamento de named_scopes.
Apenas lendo uma introdução aos named_scopes já dá pra ficar bem empolgado. É um recurso simples e, ao mesmo tempo, poderoso e, quando bem utilizado, pode resulta no código bonito e sucinto que buscamos todo dia.
Vamos, então, explorar mais algumas possibilidades desse recurso.
Modificadores para filtros
Além de estabelecer condições de filtro, podemos aplicar modificadores a eles. Um exemplo é aplicando ordenação:
class User < ActiveRecord::Base named_scope :boys, :conditions => ["gender = ?", 'M'] named_scope :girls, :conditions => ["gender = ?", 'F'] named_scope :created_since, lambda { |days| {:conditions => ["created_at >= ?", days]}} #modificando o filtro com ordenação named_scope :by_first_name, :order => "first_name ASC" end |
Isso possibilita os seguintes usos:
User.girls.by_first_name User.boys.by_first_name #ou, somente: User.by_first_name #encadeando como se não houvesse amanhã e operando sobre o resultado! User.girls.created_since(5.days.ago).by_first_name.map(&:first_name) |
Flexibilizando os escopos
Que tal um filtro aplicável a diferentes atributos do modelo? Vamos lá:
#adding the scope to ActiveRecord::Base so it's available to all models class ActiveRecord::Base named_scope :contains, lambda { |column, text| {:conditions => ["lower(#{column}) LIKE ?", "%#{text.downcase}%"]} } end |
Utilizando:
User.contains(:first_name, "%mat%") User.contains(:last_name, "Sinclair") #encadeando... User.boys.contains(:last_name, "S%").by_first_name #enlouquecendo... User.girls.contains(:first_name, "Je%").created_since(5.days.ago).by_first_name.map(&:first_name) |
Mesclando finders dinâmicos e escopos
User.boys.find_all_by_first_name("Peter") |
Note que o finder deve ser o último método na cadeia, pois retorna um Array, onde seus escopos não existem:
=> User.find_all_by_first_name("A%").boys NoMethodError: undefined method `boys' for #<array:0x21e7bf0> |
Testando named_scopes
É claro que temos que testar se os escopos estão formando as condições da maneira que desejamos. Existem alguns métodos que ajudam a fazer isso:
should "correctly generate the 'boys' scope" do expected = {:conditions => ["gender = ?", "M"]} assert_equal(expected, User.boys.proxy_options) end |
Update Fev/2009: Na verdade, nunca teste named_scopes dessa maneira. Quando estamos desenvolvendo com TDD, não nos interessa saber se algo está codificado numa classe mas, sim, se essa classe possui o comportamento esperado. O teste que coloquei acima apenas verifica se há um named_scope declarado no model e isso, definitivamente, não é bom. Segue um outro exemplo de teste:
setup do @boys = [] 3.times do @boys << Factory(:user, :gender => "M") Factory(:user, :gender => "F") end end should "be able to list just the users that are boys" do assert_equal(@boys, User.boys) end |
Agora sim estamos usando o teste da maneira correta. Não nos importa como esse filtro está escrito, apenas se ele nos proporciona o comportamento esperado.
Observação: os exemplos são para ilustrar o que pode ser feito com esse recurso. Tome muito cuidado com os encadeamentos que executar, sempre observando as queries resultantes. Um pequeno deslize pode significar um gargalo significativo.
Leia mais:
More Named Scope Awesomeness
6 Comentários