13

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?

Grant McLean
  • 6,898
  • 1
  • 21
  • 37
  • 2
    Um, if you're dynamically setting or changing the parent of a class then it's a reasonable assertion that your object model doesn't reflect a true *is-a* relationship. A more suitable solution is probably composition. – cletus Jun 27 '10 at 10:29
  • 1
    You should make clear if this is a thought exercise, or why you'd want to use that in practice. Dynamic reflective languages do not mean it's clean to dynamically change inheritance. With great (expressive) power, comes great responsibility :) – Damien Pollet Jun 27 '10 at 10:31
  • @cletus I'm not a big fan of inheritance in general but for the problem I'm currently looking at, inheritance and the is-a relationship perfectly describe the relationship. The only problem is that an object won't know what it 'is' until run time. – Grant McLean Jun 27 '10 at 21:04

7 Answers7

25

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"
Joshua Cheek
  • 30,436
  • 16
  • 74
  • 83
  • Thanks Joshua. Since it appears that I can't change the inheritance chain at runtime, I'll probably end up using some variation of your first option in my solution. I appreciate the time you put into responding. – Grant McLean Jun 27 '10 at 21:28
  • Another thing you can look at, if this is _really_ what you want to do, is change_class written by Ryan Davis. Here is the video where he introduces the code at 5:00 http://rubyconf2008.confreaks.com/evil-code.html Here is the project's home page. http://seattlerb.rubyforge.org/change_class/ Note the disclaimer "Yes, it is dangerous... don’t come crying to me if your computer ignites." – Joshua Cheek Jun 28 '10 at 02:34
8

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.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
4

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)
horseyguy
  • 29,455
  • 20
  • 103
  • 145
3

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
Konstantin Haase
  • 25,687
  • 2
  • 57
  • 59
  • I'm guessing that the use of 'evil' would probably be frowned upon in production code:-) Dynamically creating a class is probably the solution I'll end up using. Thanks! – Grant McLean Jun 27 '10 at 21:27
1

I know this question is pretty old and already has some good answers. However I still miss a certain solution.

If your intention is not to dynamically assign the superclass, but rather create a hook to execute some code on inheritance (XY Problem). There is a build-in way to do this.

inherited(subclass)

Callback invoked whenever a subclass of the current class is created.

Example:

class Foo
  def self.inherited(subclass)
    puts "New subclass: #{subclass}"
  end
end

class Bar < Foo
end

class Baz < Bar
end

produces:

New subclass: Bar
New subclass: Baz

See: Class#inherited

If your intention is to dynamically create classes, I'd recommend looking at the answer of Joshua Cheek.

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
0

Ruby's SimpleDelegator class (in the delegate library) may help, provided that it's sufficient that the object quack like the base class, rather than actually be an instance of the base class.

require 'delegate'

class Agent < SimpleDelegator
  def baz
    puts "baz"
  end
end

class BarParent
  def bar
    puts "bar"
  end
end

class FooParent
  def foo
    puts "foo"
  end
end

agent = Agent.new(FooParent.new)
agent.baz    # => baz
agent.foo    # => foo
agent.__setobj__(BarParent.new)
agent.baz    # => baz
agent.bar    # => bar
Wayne Conrad
  • 103,207
  • 26
  • 155
  • 191
0

Look at this

  class MyClass < inherit_orm("Adapter")
  end

And the class selector:

  def inherit_orm(model="Activity", orm=nil)
    orm = Config.orm || orm
    require "orm/#{orm.to_s}"
    "ORM::#{orm.to_s.classify}::#{model}".constantize
  end

So, when instance MyClass it will be inherit from a dynamic class depending of orm and model. Be sure to define both in a module. It work fine in public_activity gem ( selector example ).

I hope to help.. Bye!

CristianOrellanaBak
  • 447
  • 1
  • 5
  • 13