Reputation: 920
As the title suggests, I would like to assign all the instance methods defined on one class to another. I know at I can get a list of the methods that I want to copy from ClassA
to ClassB
like this:
ClassA.instance_methods(false)
And I think I can define them on ClassB
like this:
ClassA.instance_methods(false).each do |method_name|
ClassB.method_define(method_name, [body here??])
end
Is there a way to get the corresponding method body, and if so, will this method work? If not, is there even a way to do this?
Upvotes: 4
Views: 1457
Reputation: 12578
Others already told you to subclass. But to answer your literal question, we would be getting involved with UnboundMethod
objects:
class Object
def kokot; 'kokot' end
end
o = Object.new
o.kokot
#=> kokot
3.kokot
#=> kokot
So far so good. Now let's redefine kokot
method on Numeric
:
class Numeric
def kokot; 'pica' end
end
o.kokot
#=> kokot
3.kokot
#=> pica
But what if we decide, that new kokot
method is great for numerics, but just complex numbers should keep using the old kokot
method. We can do it like this:
um = Object.instance_method :kokot
#=> #<UnboundMethod: Object#kokot>
Complex( 2, 3 ).kokot # gives the redefined kokot method
#=> pica
Complex.module_exec { define_method :kokot, um }
# Now we've just bound the old kokot to Complex
Complex( 2, 3 ).kokot
#=> kokot
In short, there is a way to "copy and paste" methods among related classes. It is required that the target be a subclass of the unbound method source. Method #source_location
shows the file and the line where #kokot
has been defined:
um.source_location
#=> ["(irb)", 2]
For built-in methods, #source_location
returns nil
. In Ruby 2.0, RubyVM
class has method #disassemble
:
RubyVM::InstructionSequence.disassemble( um )
#=> ( program listing goes here )
In any case, Ruby bytecode is not that beautiful to look at. Going back to your original needs, not even #define_method
or UnboundMethod#bind
can bind methods to incompatible objects. This cannot be cheated by tricks like redefining #kind_of?
, one would have to cheat CLASS_OF() function in the native code...
From the available gems, Sourcify, RubyParser and Sorcerer are of interest. (Thanks, @Casper.) Using these, one could theoretically transplant code between incompatible objects via #eval
-ling extracted method source. Long way as it goes, this technique would still fall short of realiable method transfer, as it fails whenever the source is not available at runtime (eg. self-modifying source).
Upvotes: 10
Reputation: 1488
In ruby 2.0 you can use modules. Matz explicitly forbade this behavior from classes.
But you can use instance_methods from modules.
ModuleA.instance_methods(false).each do |name|
meth = ModuleA.instance_method(name)
ClassB.send(:define_method, name, meth)
end
define_method
is a private method, so that's why you use send
here.
But why do this? Just include the module.
If you want to just apply behavior to an object you can unbind a method from any module and bind it to any object.
ModuleA.instance_method(:something).bind(some_object).call(args)
If this is what you want, take a look at casting
, a gem that adds a convenience to doing delegation like this as well as adding methods to an object only for the life of a block.
Upvotes: 2
Reputation: 8247
It seems like what you might want is mix-ins:
Taken from http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
module Debug
def whoAmI?
"#{self.type.name} (\##{self.id}): #{self.to_s}"
end
end
class Phonograph
include Debug
# ...
end
class EightTrack
include Debug
# ...
end
ph = Phonograph.new("West End Blues")
et = EightTrack.new("Surrealistic Pillow")
ph.whoAmI? » "Phonograph (#537766170): West End Blues"
et.whoAmI? » "EightTrack (#537765860): Surrealistic Pillow"
Upvotes: 3