Metaprogramming Ruby: defining inheritance in class_eval

Go To StackoverFlow.com

1

I would like to expand the functionality of some class using class_eval. I would like to force the class to inherit some methods from some other class.

I.e.:

SomeClass.class_eval do
  # force inheritence from some other class
end

What's the best way to achieve it?

2012-04-04 02:01
by alexs333


1

If overriding existing functionality is a hard requirement here, you need to have those existing methods defined in a module that's also included.

class SomeClass
  include DefaultBehaviour
end

module DefaultBehaviour
  def run
    puts "ran default"
  end
end

module AlternateBehaviour
  def run
    puts "ran alternate"
  end
end

SomeClass.class_eval {
  include AlternateBehaviour
}

SomeClass.new.run #=> "ran alternate"

The reason for this is because of ruby's method lookup path.

It starts off as SomeClass -> Object.

When you include AlternateBehaviour, it becomes SomeClass -> AlternateBehaviour -> Object. So methods defined directly on SomeClass still take precedence.

However, if those methods are defined on DefaultBehaviour, the lookup path becomes SomeClass -> AlternateBehaviour -> DefaultBehaviour -> Object, so your alternate method takes priority. Whichever module was included most recently is the highest priority.

In the case where you do not have control of the original class, you can do instead:

module AlternateBehaviour
  def self.included(base)
    base.send(:remove_method, :run)
  end

  def run
    puts "ran alternate"
  end
end

Though at this point, one starts to wonder whether you might be better off by just doing

SomeClass.class_eval {
  def run
    "ran alternate"
  end
end
2012-04-04 04:23
by Burke
The complication comes when the class method I'm trying to override sits in some 3rd party engine code that I cannot refactor into module... :( Otherwise your solution works.. - alexs333 2012-04-04 04:37
Okay, updating solution then. It's only a bit more complex - Burke 2012-04-04 04:43
Why do you need to use classeval? You can just dump the method definitions directly into the classeval, or if you want the replacement methods to live elsewhere, you can build a module that removes the existing methods before defining replacements - Burke 2012-04-04 04:49
It would be most clear-looking solution in my given scenario. Btw, the updated solution works - alexs333 2012-04-04 06:02


0

Try using include and extend, both explained here. They only work with modules; you just can't modify/add superclasses of a class in Ruby after it has already been created.

Only one problem: you can't override already existing methods in a class for the explained in the third comment to this post.

Also see this topic for more information.

2012-04-04 02:08
by Jwosty
How I would go in the situation when I already have a class and would like to override it's methods from what is defined in external module - alexs333 2012-04-04 03:37
Updating post.. - Jwosty 2012-04-04 03:56
Modules won't actually work to override functionality specified in the class itself, since ruby backtracks up the inheritance hierarchy to find methods, and including a method just inserts that module into the hierarchy immediately above the class. The class itself is still the first place that's checked.

...though I would still agree with Jwosty that this is the correct solution to this problem. If you really need to override methods that are defined on the class already, you could solve this by defining them on their own module - Burke 2012-04-04 04:14

Oh, and so I was wrong. Oops - Jwosty 2012-04-04 04:17