Reputation: 83680
I want to write small class with some methods, which actualy belongs to other classes, so how can I define methods in other classes which are copies of existing. I believe it is metaprogramming magi I don't understand.
class Foo
def initialize
# with blocks, I would just pass block, but this is methods
# so this won't work
Bar.class_eval(perform)
Bar.class_eval(process)
Bar.class_eval(save)
end
def perform
1+1
end
def process
# some code
end
def save
# some code
end
end
class Bar; end
foo = Foo.new
foo.perform
#=> 2
Bar.test
#=> 1
Why I need this? I am working on gem which takes a class with just three methods. On initializing (which ill be hidden in parent class) it will pass this methods to different classes. I can make this with blocks, but with methods it is little cleaner.
PS: It is like copying methods from one class to another
PSS: Or... how to convert method to proc, so I can pass it to class_eval
Upvotes: 5
Views: 4720
Reputation: 640
It is possible to copy methods from one class to another, but there is a major caveat: the destination class must be a kind_of? the source class, or the source must be a module. This restriction is partially documented in the docs for UnboundMethod#bind, but to see the module exception you have to look at the source code for the method. This answer contains more discussion on the topic.
Here is an example:
module A
def say_hello
puts "Hello"
end
end
class B
define_method(:say_hello, A.instance_method(:say_hello))
end
b = B.new
b.say_hello
=> Hello
This would also work if A
was a class and B
was a class that inherited from A
. But in that case, you would have already gotten the method through inheritance, so I don't personally see a use for it.
The one situation where I have found this pattern useful is when designing DSL's using objects that inherit from BasicObject
. Since BasicObject
doesn't include Kernel
like Object
does, you don't have easy access to a lot of useful methods like #instance_variables
or even #class
. But you can copy them individually from Kernel into your class:
class Foo < BasicObject
define_method(:class, ::Kernel.instance_method(:class))
end
f = Foo.new
puts f.class
=> Foo
Upvotes: 4
Reputation: 30445
To convert a method to something which can be called like a Proc, use obj.method(:method_name)
. That will give you a bound Method object, which when call
ed, will be invoked on obj
. If you want to invoke it on a different object of the same class, you can call method.unbind.bind(different_obj)
.
That still doesn't allow you to "copy" methods from one class to another. If you want to allow the user to pass a class which defines 3 methods, rather than passing 3 blocks, it might work better if you store a reference to that class (or an instance of it) internally, and call methods on it as required. That's what the person who commented about "delegation" meant.
OR, you can let the user pass a Module, and make your own class include
or extend
the module (as required).
Upvotes: 14