In Ruby, people sometimes make modules called "helpers" that define some useful methods:
module MyHelper def add(a,b) a + b end end
If you include the module in a class, you can call the helper methods easily without any kind of prefix:
class MyClass include MyHelper def join(str1, str2) add str1, str2 end end puts MyClass.new.join("a", "b") # => "ab"
However, I think this has bad semantics. Look at the output below and think about why it is bad:
puts MyClass.new.is_a?(MyHelper) # => true puts MyClass.new.add(4, 3) # => 7
The fact that
MyClass uses methods from
MyHelper should be an internal implementation detail, but that information is leaking out to the users of
MyClass in all kinds of ways.
is_a? function returns true, telling you that an instance of
MyClass is a
MyHelper. However, if you design a structure like this, it is more likely that your mental model is that an instance uses the helper to accomplish its tasks without being a helper.
There are two more practical problems with this setup. First, people are free to call the helper method
#add on instances of MyClass. That does not make any sense, so we should design our code to disallow that. Second, the
#add method would show up in the list of methods returned by
#methods in IRB is a super useful way to look for features of an unfamiliar class, and our setup makes that harder because
#methods will return lots of junk. An easy way to fix these two problems is to add a new line that says
private at the top of the module, but I think that is not as good as the solution I propose below.
In Ruby 2.0, the language was expanded to include refinements. If you have not heard of refinements, Google it now. However, beware that
Module#using was recently removed so nearly all of the examples you will find are wrong. You can mostly only call
using from the top level of a Ruby file.
Refinements were mainly intended to make monkey-patching safer, but I believe they can also greatly improve the way we use helpers. The
Object class is the parent class of (almost) every Ruby object, so if we refine it with some helper methods, those methods will be available on every object, including
self. This means we can call them easily without any kind of prefix:
module MyHelper refine Object do def add(a, b) a + b end end end using MyHelper class MyClass def join(str1, str2) add str1, str2 end end puts MyClass.new.join("f", "g") # => "fg" puts MyClass.new.is_a?(MyHelper) # => false puts MyClass.new.respond_to?(:add) # => false puts MyClass.new.add(4, 5) # throws NoMethodError if line # is in a different file
As you can see above, by making a few easy changes to the helper and the code that uses it, we are able to fix all of the semantic problems we were having earlier. This is nice, except for one caveat which I will get to later.
Changing a helper module into a refinement
We had to add two lines of code to
MyHelper to use it as a refinement. What if you do not want to edit the original module? Maybe the module is part of a third-party library or maybe you are not ready to refactor all the code that uses the module. The following works:
module MyRefinement refine Object do include MyHelper end end
Generally, including a module with
include is equivalent to defining some functions in the same place, and that continues to be true with refinements. This makes me happy.
Making it even better, maybe
Object is troublesome because the code that uses the helper would be allowed to call helper methods on random objects. Again, this does not make sense so our code should disallow it:
44.add(4, 5) # => 9
Maybe this is OK if you are the only one writing code that uses your helpers, but if you are writing a system or framework that involves helpers, it would be so much nicer to just refine the class that actually uses the helpers. We can do that, as shown below, but it gets a little messy because of the limitations of the Ruby language:
# This method is defined once per project. def helper(mod, in_class: Object) Module.new do refine in_class do include mod end end end # This is a helper module. module MyHelper def add(a,b) a + b end end # This is how you write code to use the helper module: class MyClass; end using helper MyHelper, in_class: MyClass class MyClass def join(str1, str2) add str1, str2 end end puts MyClass.new.join("a", "b") # => "ab"
Unfortunately, I think this is the best we can do if we don't want to refine the
Object class. If anyone—especially Ruby language designers—has an idea of how to clean this up, let me know! I will put all of my failed ideas here.
I think we are very close to having a great new use for refinements, but maybe we need a new feature or two in the language to make it feasible.
Note: refinements are experimental, and the behavior may change in future versions of Ruby! This article was based on ruby 2.0.0dev (2013-01-07 trunk 38733) [x86_64-linux], also known as ruby 2.0.0-rc1.