Difficulty aliasing `is_x?` to `has_role? x`

Each user has many roles; to find out whether a user has the "admin" role, we can use the has_role? method:

some_user.has_role?('admin')

Which is defined like this:

def has_role?(role_in_question)
  roles.map(&:name).include?(role_in_question.to_s)
end

I'd like to be able to write some_user.has_role?('admin') as some_user.is_admin?, so I did:

  def method_missing(method, *args)
    if method.to_s.match(/^is_(\w+)[?]$/)
      has_role? $1
    else
      super
    end
  end

This works fine for the some_user.is_admin? case, but fails when I try to call it on a user referenced in another association:

>> Annotation.first.created_by.is_admin?
NoMethodError: undefined method `is_admin?' for "KKadue":User
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/associations/association_proxy.rb:215:in `method_missing'
    from (irb):345
    from :0

What gives?

Answers


Rails checks if you respond_to? "is_admin?" before doing a send.

So you need to specialize respond_to? also like:

def respond_to?(method, include_private=false)
  super || method.to_s.match(/^is_(\w+)[?]$/)
end

Note: Don't ask me why rails checks for respond_to? instead of just doing a send there, I don't see a good reason.

Also: The best way (Ruby 1.9.2+) is to define respond_to_missing? instead, and you can be compatible with all versions with something a bit fancy like:

def respond_to_missing?(method, include_private=false)
  method.to_s.match(/^is_(\w+)[?]$/)
end

unless 42.respond_to?(:respond_to_missing?) # needed for Ruby before 1.9.2:
  def respond_to?(method, include_private=false)
    super || respond_to_missing?(method, include_private)
  end
end

The ActiveRecord::Associations::AssociationProxy class overrides method_missing and intercepts the call you are looking for before it gets to the model.

This happens because AP checks if the model respond_to? the method, which in your case, it doesn't.

You have a few solutions aside from editing Rails' source:

First, manually define each of the is_* methods for the user object using metaprogramming. Something like:

class User
  Role.all.each do |role|
    define_method "is_#{role.name}?" do
      has_role?(role.name)
    end
  end
end

Another is to load the User object via some other means such as

User.find(Annotation.first.user_id).is_admin?

Or use one of the other answers listed.


Need Your Help

Why does the stack have to be page aligned?

linux stack kernel aslr

In Linux, I've tried (just for fun) to modify the kernel source in process.c create a stack address that has more entropy, i.e. in particular the line:

Python "requests" not working / blocking in a thread?

python multithreading visual-studio python-requests

I've tried the requests module in python 2.7 and liked it a lot, but when I tried to use it in a thread, it doesn't seem to work: