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, 开发者_如何学Cbut 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?
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.
精彩评论