Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically alter inheritance in Ruby

I would like to dynamically specify the parent class for a class in Ruby. Consider this code:

class Agent
  def self.hook_up(calling_class, desired_parent_class)
    # Do some magic here
  end
end

class Parent
  def bar
    puts "bar"
  end
end

class Child
  def foo
    puts "foo"
  end

  Agent.hook_up(self, Parent)
end

Child.new.bar

Neither the Parent nor the Child class definition specifies a parent class, so they both inherit from Object. My first question is: what would I need to do in Agent.hook_up in order to make Parent the superclass of Child (so for example Child objects can inherit the 'bar' method).

My second question is: do I need to pass the first argument to Agent.hook_up, or is there some way the hook_up method can programmatically determine the class from which it was called?

like image 958
Grant McLean Avatar asked Jun 27 '10 10:06

Grant McLean


4 Answers

Perhaps you are looking for this

Child = Class.new Parent do
  def foo
    "foo"
  end
end

Child.ancestors   # => [Child, Parent, Object, Kernel]
Child.new.bar     # => "bar"
Child.new.foo     # => "foo"

Since parent is an argument to Class.new, you can swap it out with other classes.

I've used this technique before when writing certain kinds of tests. But I have difficulty thinking of many good excuses to do such a thing.


I suspect what you really want is a module.

class Agent
  def self.hook_up(calling_class, desired_parent_class)
    calling_class.send :include , desired_parent_class
  end
end

module Parent
  def bar
    "bar"
  end
end

class Child
  def foo
    "foo"
  end

  Agent.hook_up(self, Parent)
end

Child.ancestors   # => [Child, Parent, Object, Kernel]
Child.new.bar     # => "bar"
Child.new.foo     # => "foo"

Though, of course, there is no need for the Agent at all

module Parent
  def bar
    "bar"
  end
end

class Child
  def foo
    "foo"
  end

  include Parent
end

Child.ancestors   # => [Child, Parent, Object, Kernel]
Child.new.bar     # => "bar"
Child.new.foo     # => "foo"
like image 129
Joshua Cheek Avatar answered Oct 11 '22 17:10

Joshua Cheek


Joshua has already given you a great list of alternatives, but to answer your question: You can't change the superclass of a class after the class has been created in ruby. That's simply not possible.

like image 43
sepp2k Avatar answered Oct 11 '22 18:10

sepp2k


Ruby 1.9 only: (1.8 is similar, but use RCLASS(self)->super instead)

require 'inline'
class Class
    inline do |builder|

        builder.c %{            
            VALUE set_super(VALUE sup) {
                RCLASS(self)->ptr->super = sup;
                return sup;
            }
        }

        builder.c %{
            VALUE get_super() {
                return RCLASS(self)->ptr->super;
            }
        }

    end


J = Class.new
J.set_super(Class.new)
like image 4
horseyguy Avatar answered Oct 11 '22 19:10

horseyguy


As pointed out already, you should probably look into modules or dynamically create classes. However, you can use evil-ruby to change the superclass. There even is a fork for Ruby 1.9 available. This does only work for MRI. Should be easy to build on Rubinius (clearing methods caches would be the main issue), no clue about JRuby. Here is the code:

require 'evil'

class Agent
  def self.hook_up(calling_class, desired_parent_class)
    calling_class.superclass = desired_parent_class
  end
end

class Parent
  def bar
    puts "bar"
  end
end

class Child
  def foo
    puts "foo"
  end

  Agent.hook_up(self, Parent)
end

Child.new.bar
like image 3
Konstantin Haase Avatar answered Oct 11 '22 19:10

Konstantin Haase