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 explain开发者_如何学运维ing 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.
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.
精彩评论