2

My current code:

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>

This happens because of this code in rails which only passes methods that exist to the model.

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

This is what I want:

Product.last.category.ex_undefined_method #=> true

How can I accomplish this?

Tom Maeckelberghe
  • 1,969
  • 3
  • 21
  • 24
  • You have a typo in your `Product` `belongs_to` line. Is that in the original code, or just the code posted here? – Chowlett Jun 01 '11 at 08:11
  • This is very similar: http://stackoverflow.com/questions/3386567/method-missing-override-is-not-working. I could probably override method_missing on the BelongsToAssociation class, but that seems a little too universal.. No? – Tom Maeckelberghe Jun 01 '11 at 08:19

3 Answers3

8

Note that the AssociationProxy object only sends on methods that the target claims to respond_to?. Therefore, the fix here is to update respond_to? as well:

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

In fact, you should always update respond_to? if you redefine method_missing - you've changed the interface of your class, so you need to make sure that everyone knows about it. See here.

Chowlett
  • 45,935
  • 20
  • 116
  • 150
2

Chowlett's response is indeed the way to go imho.

But, if you are using Rails 3*, make sure to include the second argument that has been introduced in the responds_to? definition:

def respond_to?(name,include_private = false)
  if name =~ /^handleable/
    true
  else
    super 
  end
end
Community
  • 1
  • 1
Achilles
  • 766
  • 4
  • 13
0

Replace

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

by

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

As monkey patch to the AssociationProxy

Tom Maeckelberghe
  • 1,969
  • 3
  • 21
  • 24