ActiveRecord method_missing with multiple inheritance

Recently I ran into a case where I had two separate “acts_as” style ActiveRecord model extensions which both abused respond_to? and method_missing to dynamically provide virtual attribute support.
The first thought I had was that won’t the two included method_missing definitions shadow each other? And which one will get invoked as the default one?

Well as it turns out then including a module into some class wont actually overwrite the methods, but instead creates local proxies for the included modules.
And when several definitions with the same name are found, the last included method will be invoked unless the method definition exists in the class (where the modules are included).

Illustrated in code:

class Foo
  def foo
    puts "foo"
  end
end

module Bar1
  def foo
    puts "bar1"
    super
  end
end

module Bar2
  def foo
    puts "bar2"
    super
  end
end

Foo.send(:include, Bar1)
Foo.send(:include, Bar2)

Foo.new.foo => "foo"

Now when you remove the original definition from Foo class…

class Foo
end

Foo.send(:include, Bar1)
Foo.send(:include, Bar2)

Foo.new.foo => "bar2"

Why it works like this? Because when you include modules into a class, the modules will end up as ancestors for the class.

>> Foo.ancestors
=> [Foo, Bar2, Bar1, Object, Kernel]

This means that when invoking a method, the class Foo is checked first. When no definition is found, the ancestors are invoked for the same method from the last included one to the first.
In our case Bar2 module will be asked for the foo method after its not found in the Foo class.

Something actually useful in the real world

As you might already know, ActiveRecord::Base defines a method_missing for all Rails models.
Now when i define my own method_missing implementations, i want to be sure that my methods will get invoked in conjunction with the default one.
My first thought was “heeeey… when ActiveRecord already defines method_missing, wont my methods get shadowed and ignored?”. Luckily, this is not the case at all.

ActiveRecord::Base.method_missing does its own little magic (like looking up attributes etc.) and when nothing is found, super is called.
As explained earlier – included modules end up as ancestors of the current class, meaning calling super inside ActiveRecord::Base.method_missing, it ends up calling the last included module definition of method_missing.

Queue up the metaprogramming hat!

class FooBar < ActiveRecord::Base
end

module Foo
  def method_missing(method, *args, &block)
    puts "#{method} invoked in Foo"

    if method.to_s == "foo" or method.to_s == "foo="
      # do my magic
    else
      super
    end
  end
end

module Bar
  def method_missing(method, *args, &block)
    puts "#{method} invoked in Bar"

    if method.to_s == "bar" or method.to_s == "bar="
      # do my magic
    else
      super
    end
  end
end

FooBar.send(:include, Foo)
FooBar.send(:include, Bar)

>> FooBar.new.method_missing("foo")
foo invoked in Bar
foo invoked in Foo

>> FooBar.new.method_missing("bar")
bar invoked in Bar

>> FooBar.new.method_missing(:lol)
lol invoked in Bar
lol invoked in Foo
NoMethodError: undefined method `lol' for #<foobar :0x2260640>
 from /lib/active_record/attribute_methods.rb:260:in `
method_missing'
 from (irb):12:in `method_missing'

 from (irb):24:in `method_missing'
 from (irb):39

The above example is basically intercepting any calls to method_missing in modules Foo and Bar, which in turn
process the arguments and decide whether to do some dark magic or pass the invocation onwards in the call chain, i.e the next included module.

More elaborate explanation

The difference in the two above examples is that in the first example, the foo method was called on the class itself, rather than its included modules.
In the second example the included modules method_missing methods were called first, the invocation was passed through and it finally ended up in ActiveRecord::Base.method_missing.
Kind of looks like it’s all backwards in the second example – FooBar is an ActiveRecord::Base object, so why wasn’t it’s definition of method_missing called first?
Thats’s because FooBar itself does not define this method, it’s actually included via ActiveRecord::Base from ActiveRecord::AttributeMethods. And this inclusion happens earlier than our module inclusions, meaning its higher up in the ancestors chain.
As modules included lower in the ancestors chain get invoked first, it’s perfectly clear why it works as it does.

Example of the call chain order:

1. FooBar.method_missing(:lol) # does not exist
2. Bar.method_missing(:lol) # exists, the invocation is passed through
3. Foo.method_missing(:lol) # exists, the invocation is passed through
4. ActiveRecord::AttributeMethods # exists, no attribute is found, passed through
5. Kernel.method_missing(:lol) # ends up here, no method named "lol" is found, exception thrown

All of this also applies to ActiveRecord::Base.responds_to? so you can apply the same logic while working with this method.

Using the information above you can easily write your model extensions, using magic methods like these and without having to worry too much about other parallel extensions.
Sure, there can be cases where two separate extension intercept the same method calls, but that’s a whole another issue.

How to: facepalm propely

Actually the first solution for my problem was using alias_method_chain.
It looked something like this:

module Foo
  def self.included(base)
    base.send(:include, InstanceMethods)
  end

  module InstanceMethods
    def method_missing_with_foo(method, *args, &block)
      if method.to_s == "foo" or method.to_s == "foo="
        # do my magic
      else
        method_missing_without_foo(method, *args, &block)
      end
    end
 
    alias_method_chain :method_missing, :foo
  end
end

module Bar
  def self.included(base)
    base.send(:include, InstanceMethods)
  end
 
  module InstanceMethods
    def method_missing_with_bar(method, *args, &block)
      if method.to_s == "bar" or method.to_s == "bar="
        # do my magic
      else
        method_missing_without_bar(method, *args, &block)
      end
    end
 
    alias_method_chain :method_missing, :bar
  end
end

No matter what attribute name, virtual or real, i tested this code with, i always ended up with false as the return value.
After debugging and testing the code for several hours with my co-worker, it hit us both at the same second – the alias_method_chain calls get evaluated in the module at runtime, which runs them in Module scope, not the ActiveRecord::Base scope. No wonder it returned false all the time.

This is what it should look like:

module Bar
  def self.included(base)
    base.send(:include, InstanceMethods)
    alias_method_chain :method_missing, :bar
  end

  module InstanceMethods
    def method_missing_with_bar(method, *args, &block)
      if method.to_s == "bar" or method.to_s == "bar="
        # do my magic
      else
       method_missing_without_bar(method, *args, &block)
      end
    end
  end
end

After several facepalms and “dohs” this solution actually led me to research on how and in what order are modules included and how the ancestors hierarchy will end up looking like.
That, in turn, led me to writing a better, cleaner solution for my problem and this long, hopefully useful blog post.

Conclusion: bugs are good :)

Tanel Suurhans
Tanel is an experienced Software Engineer with strong background in variety of technologies. He is extremely passionate about creating high quality software and constantly explores new technologies.

Comments are closed here.

Liked this post?

There’s more where that came from. Follow us on Facebook, Twitter or subscribe to our RSS feed to get all the latest posts immediately.