Method transplanting in Ruby20th November 2015
I had the chance to watch Akira Matsuda’s live streaming presentation from RubyConf 2015; Ruby 2 Methodology. presentation name, the talk is about what you can do with methods in Ruby.
Recorded video is still not available, although his slides are on Speaker Deck https://speakerdeck.com/a_matsuda/ruby-2-methodology.
Matsuda’s presentation is a nice overview on how to work with methods in Ruby. It is not a talk about new features, it's more of a talk about his findings on how to use methods effectively in Ruby.
From all the cases presented, there was one feature that I found particularly interesting: Method Transplanting.
Method transplanting is a way for you to unbound a method from a module and bound it back into a class. Here is the example Matsuda used in his slides:
module Greeter def hello p 'hello' end end class Cat define_method :hello, Greeter.instance_method(:hello) end Cat.new.hello #=> "hello"
:hello was successfully transplanted from module
Greeter into class
You will probably say, “well, I have being doing this for a while including modules into classes, how is this different?”.
module Greeter def hello p 'hello' end end class Cat include Greeter end Cat.new.hello #=> "hello"
You are right, kind of. By using
include and method transplanting you can achieve almost the same result, however, let's see how they are different.
For our example where the method was transplanted, if you ask the class to report its
public_instance_methods ignoring ancestors you will get:
Cat.public_instance_methods(false) #=> [:hello] Cat.new.respond_to?(:hello) #=> true
:hello truly belongs to
Cat. You unbounded
:hello method from the
Greeter module and then bounded it back to the
Now, by doing the same for the version where you include the module you get:
Cat.public_instance_methods(false) #=>  Cat.new.respond_to?(:hello) #=> true
:hello method is not bound to the
Cat class, it's still bound to the
The most interesting point of this feature is the possibility to cherry pick which methods you want to transplant from any module to your class.
Most of the time, when you consider including a module in a class, there is a concern due to the possibility of polluting its interface. If you are on Ruby 2.0 or better, you can now opt to simply transplant what you need.
Let's see another example.
module Greeter def say_hi puts "Hi!" end def say_bye puts "Bye!" end end class Cat define_method :say_bye, Greeter.instance_method(:say_bye) end Cat.public_instance_methods(false) #=> [:say_bye] Cat.new.say_bye #=> "Bye!" Cat.new.respond_to?(:say_hi) #=> false
In this example you are cherry picking only what you want to transplant to your class. By doing this you are keeping the class neat, avoiding any unnecessary method definitions from the
Let's create a module which will define a class method named
use that will allow us to transplant methods with this signature
use Greeter, only:[:say_bye].
module Use def use(source, only: ) if !only.respond_to?(:to_ary) only = [only] end if only.empty? only = source.public_instance_methods(false) end only.each do |method| define_method method, source.instance_method(method) end end end
This is an example on how you could transplant methods with the
class Cat extend Use use Greeter, only: [:say_bye] end Cat.public_instance_methods(false) #=> [:say_bye] Cat.new.say_bye #=> "Bye!" Cat.new.respond_to?(:say_hi) #=> false
The syntax is simpler, the param
only can accept an array with the symbol, or just a single symbol of methods that you want to transplant.
If the param
only is not present, then all the instance methods of our module are transplanted into our class. However, this is something that you want to avoid and simply use the old plain
Ruby 2.2 or newer not only supports method transplanting from a module but also from a class.
Method transplanting is an elegant solution to keeping classes clean. This solution will work perfectly when a transplanted method has no dependencies on other methods from the original module considering unbound/bound do not track them.
I have been writing Ruby programs for several years now, but it still amazes me that there are so many features within the language that are not so popular or common.
Transplanting methods feel useful, especially in Rails development, where almost everything is breaking down to modules.
Also, you have to admit that saying “I’m transplanting these methods” instead of “I’m including these methods” sounds nicer.