views:

481

answers:

1

The background to this problem is quite complex and convoluted, and as I am looking for a simple answer, I will leave it by the wayside in explaining my problem, and instead provide this hypothetical situation.

If I have a simple ActiveRecord model called Automobile, with named_scopes like below:

named_scope :classic, :conditions => { :build_date <= 1969 }
named_scope :fast, lambda { |speed| :top_speed >= speed }

Ignoring the scopes themselves, if I were to call:

Automobile.scopes

What exactly would this be returning? What I see in the console is:

[ :classic => #<Proc:0x01a543d4@/Users/user_name/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/named_scope.rb:87>,
  :fast => #<Proc:0x01a543d4@/Users/user_name/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/named_scope.rb:87> ]

This seems to me to be an array of key/values, the key being the symbol of the named scope, and the value being a Proc pointing to the named_scope.rb file in ActiveRecord.

If I want the hash or Proc given as the actual named scope (meaning for :classic, I would receive ":conditions => { :build_date <= 1969 }", how could I go about finding this?

I am writing a plugin that conditional merges some named_scopes, and I am running up against some resistance in regards to this. I am currently using the following to merge these scopes:

scopes_to_use = Automobile.scopes
scoped_options = {}
Automobile.scopes.each do |scope|
    scoped_options.safe_merge!(eval "Automobile.#{scope}(self).proxy_options")
end

Ignoring the 'correctness' of what I am doing here, is there a better way that I can retrieve the actual Hash or Proc given to the named_scope? I don't like using 'eval' in this function, and if I could actually retrieve the Hash or Proc, then I would be able to introduce some much more powerful merging logic. Any thoughts on this would be much appreciated. Thanks.

+1  A: 

Neither named scope you define in your example would do anything. They are wrong syntactically. That could cause problems you might be having.

Assuming that the examples were hastily created and you've got working ones. On to the answer.

How could I go about finding the hash or Proc given as the actual named scope.

Model.scopes[:scope_name] gives you the proc. Model.send(:scope_name).proxy_options gives you the options hash given to the scope ie: { :conditions => ["build_date <= ?", 1969] }

To programatically retrieve the options hash of each named scope in a model you can do this:

scopes_to_use = Automobile.scopes
scoped_options = {}
Automobile.scopes.keys.each do |scope|
    scoped_options.safe_merge!(Automobile.send(scope).proxy_options)
end

This doesn't work as nicely for scopes that require arguments, because they could raise exceptions. Unfortunately I can't think of a simple way around it.

The best I can come up with is to test for arity of the proc and then provide unique dummy arguments and analyze the returned proxy options to figure out what changed. But that's a lot of work, because the arity of any named scope is -2. The best you could do in retrieving the arity is call the proc, rescue an argument error and parse it for the number or arguments expected. Then use that number of dummy arguments.

The whole process requires a rescue block and some eval magic to work. And that's before you can process the proxy_options hash for your safe merge.

In short you'll want to do something close to this, it's not pretty but it works:

scopes_to_use = Automobile.scopes
    scoped_options = {}
Automobile.scopes.each do |scope,proc|
  next if scope == :scoped
  number_of_args = 1
  begin
    scoped_options.safe_merge! Automobile.send(scope, "#{scope}_argument_1").proxy_options
  rescue 
    $!.to_s.match /(\d+)\)$/
    number_of_args = $1.to_i
    puts number_of_args
  end
     scoped_options.safe_merge!(Automobile.send(scope, *(1..number_of_args).map{|i| "#{scope}_argument_#{i}"}.proxy_options)
end

Which should be safe because proxy_options doesn't execute the SQL nor does it do any type checking.

EmFi
This solution looks viable, but is rather more intensive than I would want at this point. I was looking for something available directly via the rails API that I might have been missing, but if nothing exactly exists, then I think I'd rather find some other solution to my overall problem than a questionable one using named scopes. Thanks for your answer. It definitely answered my question, but also raised some more for me. If the solution is this complex, then my aim must be wrong.
Synthlabs
Not that it matters at this point, but I've since learnt about the * operator. I've edited the solution to use it in place of the eval.
EmFi