0
named_scope :with_country, lambad { |country_id| ...}

named_scope :with_language, lambad { |language_id| ...}

named_scope :with_gender, lambad { |gender_id| ...}

if params[:country_id]
  Event.with_country(params[:country_id])
elsif params[:langauge_id]
  Event.with_state(params[:language_id])
else 
  ......
  #so many combinations
end

If I get both country and language then I need to apply both of them. In my real application I have 8 different named_scopes that could be applied depending on the case. How to apply named_scopes incrementally or hold on to named_scopes somewhere and then later apply in one shot.

I tried holding on to values like this

 tmp = Event.with_country(1)

but that fires the sql instantly.

I guess I can write something like

if !params[:country_id].blank? && !params[:language_id].blank? && !params[:gender_id].blank?
  Event.with_country(params[:country_id]).with_language(..).with_gender
elsif country && language
elsif country &&  gender
elsif country && gender
 .. you see the problem
Roger
  • 3,919
  • 7
  • 30
  • 37

6 Answers6

2

Actually, the SQL does not fire instantly. Though I haven't bothered to look up how Rails pulls off this magic (though now I'm curious), the query isn't fired until you actually inspect the result set's contents.

So if you run the following in the console:

wc = Event.with_country(Country.first.id);nil # line returns nil, so wc remains uninspected
wc.with_state(State.first.id)

you'll note that no Event query is fired for the first line, whereas one large Event query is fired for the second. As such, you can safely store Event.with_country(params[:country_id]) as a variable and add more scopes to it later, since the query will only be fired at the end.

To confirm that this is true, try the approach I'm describing, and check your server logs to confirm that only one query is being fired on the page itself for events.

Matchu
  • 83,922
  • 18
  • 153
  • 160
  • If you remove nil then you can see that sql is fired instantly – Roger Dec 29 '09 at 16:38
  • my bad. script/console calls to_s and that's why sql is getting fired – Roger Dec 29 '09 at 16:44
  • Ayup. The nil was to ensure that the to_s never happened :) Now that you know you can stack scopes on top of each other, has your question been answered? – Matchu Dec 29 '09 at 22:06
1

I had to do something similar, having many filters applied in a view. What I did was create named_scopes with conditions:

named_scope :with_filter, lambda{|filter| { :conditions => {:field => filter}} unless filter.blank?}

In the same class there is a method which receives the params from the action and returns the filtered records:

def self.filter(params)
  ClassObject
  .with_filter(params[:filter1])
  .with_filter2(params[:filter2])
end

Like that you can add all the filters using named_scopes and they are used depending on the params that are sent.

I took the idea from here: http://www.idolhands.com/ruby-on-rails/guides-tips-and-tutorials/add-filters-to-views-using-named-scopes-in-rails

mlavandero
  • 11
  • 1
1

Check Anonymous Scopes.

Community
  • 1
  • 1
khelll
  • 23,590
  • 15
  • 91
  • 109
0

Event.with_country(params[:country_id]).with_state(params[:language_id])

will work and won't fire the SQL until the end (if you try it in the console, it'll happen right away because the console will call to_s on the results. IRL the SQL won't fire until the end).

I suspect you also need to be sure each named_scope tests the existence of what is passed in:

named_scope :with_country, lambda { |country_id| country_id.nil? ? {} : {:conditions=>...} }
Drew Blas
  • 3,028
  • 1
  • 21
  • 11
0

This will be easy with Rails 3:

products = Product.where("price = 100").limit(5) # No query executed yet
products = products.order("created_at DESC")     # Adding to the query, still no execution
products.each { |product| puts product.price }   # That's when the SQL query is actually fired

class Product < ActiveRecord::Base
  named_scope :pricey, where("price > 100")
  named_scope :latest, order("created_at DESC").limit(10)
end
Garrett
  • 7,830
  • 2
  • 41
  • 42
0

The short answer is to simply shift the scope as required, narrowing it down depending on what parameters are present:

scope = Example

# Only apply to parameters that are present and not empty
if (!params[:foo].blank?)
  scope = scope.with_foo(params[:foo])
end

if (!params[:bar].blank?)
  scope = scope.with_bar(params[:bar])
end

results = scope.all

A better approach would be to use something like Searchlogic (http://github.com/binarylogic/searchlogic) which encapsulates all of this for you.

tadman
  • 208,517
  • 23
  • 234
  • 262