Сложность алиасинга `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?

Или используйте один из перечисленных ответов.

Другие вопросы по тегам