Od railsów 2.x możemy korzystać z dobrodziejstw named_scopes, za pomocą których możemy definiować w łatwy sposób findery. Mowiąc prościej, możemy budować zapytania do bazy danych. Nie są to jednak zwykłe metody ktore wyszukują nam coś w bazie, zamiast prostego zbioru wyników, zwracany jest scope (zakres).
Aby rzucić więcej swiatła na sprawę, zacznijmy od przykładu:
class Product < ActiveRecord::Base
named_scope :available, :conditions => "amount > 0"
named_scope :featured, :conditions => {:featured => true}
end
Pierwszy argument to nazwa scopu, drugi to hash taki jak przyjmuje metoda :find, albo lambda która zwraca taki hash. Mozemy także wywołać named_scope z blokiem, ale o tym poźniej.
Product.available
# SELECT * FROM "products" WHERE (amount > 0)
Product.featured
# SELECT * FROM "products" WHERE ("products"."featured" = 't')
No tak, w sumie mozna by napisac "normalne" metody klasy, ktore zrobia to samo.
Wygladało by to tak:
def self.available
all(:conditions => ["amount > 0"])
end
def self.featured
all(:conditions => {:featured => true})
end
Dlaczego więc warto stosować named_scopes, zamiast "normalnych" metod ?
- Ładniejszy, krótszy kod
- Mozliwość łaczenia scopów
Łaczenie scopów
Tak, scopy mozemy łaczyć. Aby pobrać produkty, które są dostępne i promowane, napiszemy:
Product.available.featured
# SELECT * FROM "products" WHERE (("products"."featured" = 't') AND (amount > 0))
Jak widac, zostało wykonane jedno zapytanie, a warunki obu scopów zostały połączone.
Scopy które przyjmują argumenty
class Product < ActiveRecord::Base
named_scope :limit, lambda { |limit| {:limit => limit} }
end
Wystarczy aby lambda zwracała hash opcji wyszukiwania, taki jak przekazujemy do metody :find.
Product.available.limit(20)
Anonimowy scope
Za pomocą metody scoped, moźemy tworzyć anonimowe scopy.
Product.scoped(:conditions => "name ~* 'tv'").limit(100)
Nie zaleca się jednak stosowania tego typu konstrukcji gdzie popadnie, gdyż możemy poprostu napisać scope nazwany w modelu, tam gdzie powinnismy trzymac nasze zapytania.
Są miejsca gdzie przydaje się anonimowy scope, tutaj jest przykład ciekawego wykorzystania Railscasts - Anonymous Scopes
Rozszerzenia dla named_scopes
Podobnie jak w przypadku associacji, możemy tworzyć rozszerzenia dla scopów. Dodajmy metode która zamówi wszystkie produkty, których brakuje.
named_scope :unavailable, :conditions => {:amount => 0} do
def order
each { |product| Order.create(:product => product) }
end
end
Product.unavailable
Zwraca wszystkie produkty ktorych brakuje
natomiast
Product.unavailable.order
Tworzy zamowienia, dla kazdego z tych produktów.
Rails 2.3
W railsach 2.3 został wprowadzony scope dynamiczny i domyslny.
Dynamiczny scope
Możemy używac dynamicznych scopow, podobnie jak dynamiczynych finderow, czyli:
Product.scoped_by_amount_and_featured(100, true)
Domyślny scope
Nastepną bardzo użyteczna konstrukcją, jest możliwość zdefiniowania domyślnego scopu dla modelu. Bardzo często konkretna tabele sortujemy po konkretnej kolumnie/kolumnach.
class Product < ActiveRecord::Base
default_scope :order => "name DESC"
end
Przydaje się też, gdy chcemy ukryc pewne rekordy, np. produkty które nie są aktualnie odstępne:
default_scope :conditions => "amount > 0"
Linki
Module ActiveRecord::NamedScope::ClassMethods
What's New in Edge Rails: Has Finder Functionality
named_scope pojawiły się 2.1.0.
default_scope do mnie nie przemawia, szczególnie przypadek z conditions => “amount > 0”. Dynamiczny scope też wydaje mi się trochę naciągany na siłe, np. Product.scoped_by_amount_and_featured_and_sth_and_another_and_pretty_code(100, true, true,true, false) trąci mi jakimś masochizmem i bałaganiarstwem
Dzieki, juz poprawiam. Co do default scope to uwazam ze jest bardzo uzyteczna konstrukcja, bardzo czesto sortuje konkretne modele, i czesto jej uzywam, moze ten przyklad z amount taki sredni. A z tym dynamicznym scopem, to tak jak z alkoholem, trzeba znac umiar :)
nauczylem sie bardzo wiele