Method transplanting in Ruby

Mario Alberto Chávez
michelada.io
Published in
3 min readNov 20, 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

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"

Method :hello was successfully transplanted from module Greeter into class Cat.

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

Looks like :hello truly belongs to Cat. You unbounded :hello method from the Greeter module and then bounded it back to the Cat class.

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

The :hello method is not bound to the Cat class, it’s still bound to the Greeter module.

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 Greeter module.

Use module

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 Use module.

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

Ruby 2.2 or newer not only supports method transplanting from a module but also from a class.

Conclusions

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.

Originally published at blog.michelada.io. Check out Michelada.io at michelada.io.

--

--

michelada.io
michelada.io

Published in michelada.io

We plan, code and launch awesome web and mobile products.

Mario Alberto Chávez
Mario Alberto Chávez

Written by Mario Alberto Chávez

Rubyist and software engineer. @micheladaio co-founder. Author of Aprendiendo Ruby on Rails @railsenespanol. Photography http://mario_chavez.500px.com