The tools that are right under your nose

I recently had a fantastic experience giving a presentation at RubyConf called The Secrets of the Standard Library. The slides are available and once the video is up I'll send along a link to that.

There are no secrets in the Standard Library

There are no secrets, of course, because the code is right there for you to read but many developers don't realize that powerful tools like SimpleDelegator and Forwardable are built into Ruby. There's no gem to find and install, just require the file you need and you've got it. It's as easy as:

require 'delegate'
class MyWrapper < SimpleDelegator; end

require 'forwardable'
class MyClass
  extend Forwardable
end

My goal in the RubyConf presentation was to show that these tools are available and to describe how they work. Using the tools is one thing, but understanding them is another.

In previous posts we've been looking at ways to use SimpleDelegator to handle presentation behaviors and there's been a bit of Forwardable too. Once you've got a clear understanding of them, you can be more creative with their use and build your own tools to make your code more habitable.

I find that implementing my own library can be a great exercise in understanding an existing one. It was fun to write two short lines in an attempt to create SimpleDelegator and Forwardable in what would fit in less than 140 characters:

# My SimpleDelegator
class D;def initialize(o);@o=o;end;
def method_missing(m, *r, &b);@o.send(m,*r,&b);end;end
# My Forwardable
module Fwd;def fwd(a,m,n=m);self.class_eval
"def #{n}(*r,&b);#{a}.__send__(:#{m},*r,&b);end";end;end

Expanding those out with more descriptive names reveals a bit more about what they do:

# My SimpleDelegator
class Delegator
  def initialize(object)
    @object = object
  end
  def method_missing(method, *args, &block)
    @object.send(method, *args, &block)
  end
end
# My Forwardable
module Forward
  def forward(to, method_name, alternative=method_name)
    self.class_eval "
     def #{alternative}(*args, &block)
       #{to}.__send__(:#{method_name},*args, &block)
     end
    "
  end
end

In both my simple implementation and the actual implementation one uses method_missing at run time and the other defines methods at compile time.

Knowing this, and seeing how they work, can help you make decisions about when to use one over the other. Defined methods are faster than method_missing but they require a more rigid structure.

When do you use SimpleDelegator and when do you use Forwardable?

I generally follow these simple rules for choosing which library to select:

  1. Use SimpleDelegator when you either don't know or don't care which messages may be sent to your object. This excepts (of course) the methods you define in your wrapper.
  2. Use Forwardable when you know the messages to forward ahead of time.

Both libraries follow the same pattern that a message to one object will be sent along unaffected to the relevant object. But there are times when building your own gives you a better understanding and more control over when errors occurr.

Building your own SimpleDelegator can be tricky. There's quite a lot of work done for you already to help ensure that your wrapper acts like the wrapped object so do read the code and get familiar with the trade-offs when writing your own.

Errors instead of method_missing

Sometimes you may want more control or restriction on the behavior of objects.

You can build a wrapper with Forwardable, for example, by swapping out the object of concern and instead of everything passing through method_missing, you'll get a NoMethodError.

Here's a short example:

class Person
  attr_accessor :name, :color, :number, :email
end
require 'forwardable'
class Wrapper
  extend Forwardable
  delegate [:name, :color, :number] => :person

  attr_accessor :person
end
wrapper = Wrapper.new
wrapper.person = person1
wrapper.name #=> person1 name value
wrapper.person = person2
wrapper.name #=> person2 name value
wrapper.email #=> NoMethodError

A wrapper like this allows us to specify only the methods we want to pass-through and any others that person may have will raise an error.

How have you used these libraries? How do you decide what to use?

What have you built to do similar things?

Last but not least, I recently wrote about undestanding delegation on my blog and how it helped me create the casting gem.

If you enjoyed this post, sign-up for my newsletter below and get more.