Rails: method_missing не вызывается для модели при использовании ассоциаций - PullRequest
2 голосов
/ 01 июня 2011

Мой текущий код:

class Product < ActiveRecord::Base
  belongs_to :category
end

class Category < ActiveRecord::Base
  def method_missing name
    true
  end
end

Category.new.ex_undefined_method          #=> true
Product.last.category.ex_undefined_method #=> NoMethodError: undefined method `ex_undefined_method' for #<ActiveRecord::Associations::BelongsToAssociation:0xc4cd52c>

Это происходит из-за этого кода в рельсах, который передает только те методы, которые существуют в модели.

private
def method_missing(method, *args)
  if load_target
    if @target.respond_to?(method)
      if block_given?
        @target.send(method, *args)  { |*block_args| yield(*block_args) }
      else
        @target.send(method, *args)
      end
    else
      super
    end
  end
end

Вот что я хочу:

Product.last.category.ex_undefined_method #=> true

Как мне это сделать?

Ответы [ 3 ]

8 голосов
/ 01 июня 2011

Обратите внимание, что объект AssociationProxy отправляет только методы, которые, как утверждает целевой объект, respond_to?. Поэтому исправление здесь также заключается в обновлении respond_to?:

class Category < ActiveRecord::Base
  def method_missing(name, *args, &block)
    if name =~ /^handleable/
      "Handled"
    else
      super
    end
  end

  def respond_to?(name)
    if name =~ /^handleable/
      true
    else
      super
    end
  end
end

На самом деле, вы должны всегда обновлять respond_to?, если вы переопределите method_missing - вы изменили интерфейс вашего класса, поэтому вам нужно убедиться, что все об этом знают. Смотри здесь .

2 голосов
/ 22 сентября 2011

Ответ Чоулетта - это действительно правильный путь.

Но, если вы используете Rails 3 *, обязательно включите второй аргумент , который имеетбыл введен в Responds_to?определение:

def respond_to?(name,include_private = false)
  if name =~ /^handleable/
    true
  else
    super 
  end
end
0 голосов
/ 01 июня 2011

Заменить

if @target.respond_to?(method)
  if block_given?
    @target.send(method, *args)  { |*block_args| yield(*block_args) }
  else
    @target.send(method, *args)
  end
else
  super
end

на

if block_given?
  @target.send(method, *args)  { |*block_args| yield(*block_args) }
else
  @target.send(method, *args)
end

Как патч обезьяны для AssociationProxy

...