pachun
pachun

Reputation: 920

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?

Upvotes: 4

Views: 1457

Answers (4)

Boris Stitnicky
Boris Stitnicky

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

Jim Gay
Jim Gay

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

sawa
sawa

Reputation: 168101

In that case, classB should inherit classA.

Upvotes: 0

John Naegle
John Naegle

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

Related Questions