Сложность алиасинга `is_x?` К `has_role? x` - PullRequest
3 голосов
/ 21 июля 2010

У каждого пользователя много ролей; чтобы узнать, есть ли у пользователя роль администратора, мы можем использовать метод 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 ]

3 голосов
/ 21 июля 2010

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
2 голосов
/ 21 июля 2010

Класс 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?

, или использовать один из перечисленных ответов.

...