Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assign a method from one class to an instance of another

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?

like image 453
pachun Avatar asked Dec 09 '22 18:12

pachun


1 Answers

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).

like image 87
Boris Stitnicky Avatar answered Dec 11 '22 09:12

Boris Stitnicky