Fork me on GitHub

Keep Learning Conhecimento nunca é o bastante

Postado em
30 July 2008 @ 0:23

Tag(s)
Dicas, Rails

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

Comentário por
Pedro Pimentel
30 July 2008 @ 0:43

Realmente dá pra pirar muito com o escopo nomeado.
Eu só não entendo pq deram ao método “proxy_options” este nome, não significa muito pra mim…. Tu saberia me dizer o por quê ?

abraços


Comentário por
Lucas Húngaro
30 July 2008 @ 2:23

Pedro, numa pesquisa rápida não encontrei nada.

Por inferência, creio que seja o seguinte: o named_scope é um proxy para uma finder, isto é, um conjunto de opções (:conditions, :order, :limit etc) nomeado. Logo, proxy_options retorna as opções utilizadas nesse proxy.

Algo como scope_options provavelmente faria mais sentido.


Comentário por
Philipe Farias
30 July 2008 @ 11:15

O shoulda já conta com um helper pra named_scopes:

http://giantrobots.thoughtbot.com/2008/7/29/testing-named_scope


Comentário por
Lucas Húngaro
30 July 2008 @ 12:03

Muito legal! Eu gosto bastante do Shoulda e ia escrever esse helper. Escrevi o artigo antes de ver que tinha post novo no Giant Robots!

Valeu Philipe!


Comentário por
Weldys Santos
8 August 2008 @ 15:50

Pô Lucas, o post tá show de bola hein! Named scopes realmente é mão na roda pra desenvolver regras pra buscas


Comentário por
Lucas Húngaro
8 August 2008 @ 16:47

Valeu Weldys! Pô, nem linkaram no Rails Podcast. 😉


Deixe um comentário