Reputation: 6998
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?
Upvotes: 13
Views: 13160
Reputation: 21130
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.
Upvotes: 1
Reputation: 457
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!
Upvotes: 0
Reputation: 107999
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
Upvotes: 0
Reputation: 29895
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)
Upvotes: 4
Reputation: 25964
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
Upvotes: 3
Reputation: 370162
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.
Upvotes: 8
Reputation: 31726
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"
Upvotes: 25