Сложность алиасинга `is_x?` К `has_role? x`
Каждый пользователь имеет много ролей; чтобы выяснить, есть ли у пользователя роль администратора, мы можем использовать has_role?
метод:
some_user.has_role?('admin')
Который определяется так:
def has_role?(role_in_question)
roles.map(&:name).include?(role_in_question.to_s)
end
Я хотел бы иметь возможность написать some_user.has_role?('admin')
как some_user.is_admin?
, так я и сделал:
def method_missing(method, *args)
if method.to_s.match(/^is_(\w+)[?]$/)
has_role? $1
else
super
end
end
Это прекрасно работает для some_user.is_admin?
случай, но не удается, когда я пытаюсь вызвать его на пользователя, на которого ссылаются в другой ассоциации:
>> 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
Что дает?
2 ответа
Rails проверяет, если вы respond_to? "is_admin?"
прежде чем делать send
,
Так что нужно специализироваться respond_to?
так же как:
def respond_to?(method, include_private=false)
super || method.to_s.match(/^is_(\w+)[?]$/)
end
Примечание: не спрашивайте меня, почему рельсы проверяют respond_to?
вместо того, чтобы просто делать send
там я не вижу веской причины.
Также: лучший способ (Ruby 1.9.2+) - определить respond_to_missing?
вместо этого, и вы можете быть совместимы со всеми версиями с чем-то вроде:
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
ActiveRecord::Associations::AssociationProxy
переопределения класса method_missing
и перехватывает вызов, который вы ищете, прежде чем он попадет в модель.
Это происходит потому, что AP проверяет, является ли модель respond_to?
метод, который в вашем случае это не так.
У вас есть несколько решений помимо редактирования исходного кода Rails:
Сначала вручную определите каждый из методов is_* для пользовательского объекта, используя метапрограммирование. Что-то вроде:
class User
Role.all.each do |role|
define_method "is_#{role.name}?" do
has_role?(role.name)
end
end
end
Другой способ - загрузить объект User с помощью других средств, таких как
User.find(Annotation.first.user_id).is_admin?
Или используйте один из перечисленных ответов.