Piotr Sarnacki home

Named scope

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 ;-)

blog comments powered by Disqus
Fork me on GitHub