ruby_object
ruby_object

Reputation: 1266

Arity of scopes in Rails 4

I know how to check arity of a lambda, but I do not know how I can extract it from scope.

This question was asked 4 years ago:

https://groups.google.com/forum/#!topic/rubyonrails-core/7Cs0T34mj8c

All, In the course of working on a change for the meta-search gem I've run into an issue with the way that scopes are implemented in ActiveRecord. In order to include a scope in a search we pass the name of the scope to a search method like so: {:name_of_my_scope => 1}. Meta-search automatically passes the "1" as an argument to the scope. This causes an ArgumentError with lambda scopes that don't take an argument.

My intention was to check the arity of the scope before calling and dropping the "1" in the event the scope didn't take an argument. My issue is that the implementation of scope wraps scope_options up in a lambda that passes *args to the block (active_record/named_scope.rb: 106). This results in the call to arity always returning -1 regardless of the actual number of arguments required by the scope definition.

Is there a different way to implement scopes that would allow exposing the arity from the scope definition?

Ex.

class Post < ActiveRecord::Base  
     scope :today, lambda {where(:created_at => (Time.new.beginning_of_day...(Time.new.end_of_day)) }  
  end 

irb> Post.today.arity # => -1

It asks for help in finding scope's arity before calling it.

Has a solution been found?

Upvotes: 5

Views: 744

Answers (1)

Wand Maker
Wand Maker

Reputation: 18762

There are no explicit parameters for scope methods as shown below

irb(main):070:0> User.method(:active_users).parameters
=> [[:rest, :args]]

The :rest means that arguments are collected as an array. This allows for one to have no arguments to any number of arguments.

For the scopes that accept parameters, if you invoke with wrong number of arguments, you will receive an ArgumentError - somewhat like below:

ArgumentError: wrong number of arguments (1 for 2)

We could take advantage of this ArgumentError to figure out the arity, as arity is present in the error message.

Let me caution you that it is kind of a hack, may be it can be useful if you are in dire need to figure out arity of lambda-based scopes.

We can define a method in the Model which will try to invoke the scope with uncommonly large number of arguments, say 100 arguments - and thus force an ArgumentError - we can then capture the message and extract the arity from it.

A possible implementation of such method is shown below:

def self.scope_arity scope_symbol
    begin
        send(scope_symbol, (1..100).to_a) 
    rescue ArgumentError => e
        f = e.message.scan(/for\s+(\d+)/).flatten.first
        f.to_i.nonzero? || 0
    end
end

Possible usage can be like below:

irb(main):074:0> User.scope_arity(:active_users)
=> 2

Upvotes: 3

Related Questions