W railsach od jakiegoś czasu można używać named_scope. Jest to bardzo fajny mechanizm umożliwiający łatwiejsze skonstruowanie zapytania używając wcześniej zdefiniowanych metod. Wcześniej był dostępny jako plugin has_finder.
Wygląda to mniej więcej tak:
named_scope :active, :conditions => { :active => true }
named_scope :paid, :conditions => { :paid => true }
named_scope :recent, lambda { { :conditions => ['created_at >= ?', 1.week.ago] } }
Po zdefiniowaniu takich sope’ów (macie pomysł jak to spolszczyć?) można ich używać w ten sposób (zakładam, że scope’y zostały zadeklarowane w modelu Product):
Product.active.paid.recent
Jak można łatwo zauważyć, scope’y można łączyć w łańcuchy. Dzięki takiej konstrukcji otrzymujemy prosty i treściwy kod, który łatwo zrozumieć. Teraz coś lekko trudniejszego:
named_scope :in_category, lambda { |category| { :conditions => { :category_id => category } } }
# i użycie
Product.active.recent.in_category(category)
O co chodzi? Jako 2 argument można przekazać lambdę, która zostanie wywołana przy użyciu scope’a i powinna zwrócić Hash.
Ale to nie są jedyne zastosowania scope’ów. Można do nich przekazać też inne parametry, które może przyjąć metoda find.
Na przykład order, offset i limit:
named_scope :order, lambda { |*args| { :order => args.first || "created_at DESC" } }
named_scope :offset lambda { |offset| { :offset => offset } }
named_scope :limit, lambda { |*args| { :limit => args.first || 10 } }
# i użycie:
Product.active.order("id DESC").offset(10).limit
W pierwszym i trzecim scope’ie zastosowałem trick, który pozwala na użycie zdefiniowanej wartości domyślnej w razie gdy żadna nie zostanie podana. `*args` pakuje argumenty do tablicy. Jeżeli args.first zwróci nil, to znaczy, że tablica jest pusta, czyli nie zostały podane żadne argumenty i trzeba zaaplikować wartość domyślną.
Coś jeszcze? Fajnie by było, żeby można było sortować nie tylko po wartościach z jednej tablicy… a do tego wypadałoby użyć joins/include. Nic prostszego.
named_scope :joins, lambda { |joins| { :joins => joins } }
# a teraz można tak:
Product.active.joins(:user).order('users.email')
How cool is that?
No i na koniec coś co ostatecznie przekonało mnie, że named_scope jest genialnym wynalazkiem. Jeżeli ktoś robił kiedyś formularze, w których można wybrać kilka opcji i na ich podstawie trzeba zbudować zapytanie, zapewne wie, że jest to nieco upierdliwe. Używając `named_scopes` można to zrobić bardzo łatwo.
Ryan Bates napisał plugin scope_builder, dzięki któremu można to zrobić jeszcze łatwiej. Przypuśćmy, że mamy formę, w której można zaznaczyć kilka pól i po ich wysłaniu należy na ich podstawie pobrać odpowiednie rekordy z bazy.
# przygotowujemy listę parametrów, które użytkownik może ustawić
parameters = [:active, :paid, :recent, :title]
# Tworzymy scope
@products = Product.scope_builder
# teraz można sprawdzić, które pola zostały zazanczone
parameters.each do |param|
@products.send(param) if params[param]
end
# na koniec można na przykład dodać paginację
@products = @products.paginate(:per_page => 10, :page => params[:id])
Dla mnie genialne. :) Można dzięki temu łatwiej tworzyć zaawansowane wyszukiwanie, sortowanie i inne tego typu rzeczy.
Oczywiście najlepiej jest zamknąć taki kawałek kodu jako metodę modelu, zgodnie ze skinny controller, fat model, ale to pozostawię jako ćwiczenie ;-)