Improved Rails’ delegate method

The Rails framework includes a simple “delegate” method, which is mixed in the models, and can be used in order to delegate methods to other classes. However it’s limited at least in the following:
1. It doesn’t prevent “train wrecks” – e.g. if the class that the method is delegated to is nil, then there will be an attempt to call this method on the nil class.
2. It doesn’t allow renaming of the methods, e.g. if you have a method called “name” in your class, and a method called “name” in other class you want to delegate to, you have no means of doing this.
The implementation below remedies these shortcomings.

class Module
  def delegate(*methods)
    options = methods.pop
    unless options.is_a?(Hash) && to = options[:to]
      raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
    prefix = options[:prefix] || ''
    orig_prefix = prefix
    if prefix != true and !prefix.empty? 
      prefix += '_'
    methods.each do |method|
      prefix = to.to_s + '_' if orig_prefix == true
      module_eval(<<-EOS, "(__DELEGATION__)", 1)
        def #{prefix}#{method}(*args, &block)
          return nil unless #{to}
          #{to}.__send__(#{method.inspect}, *args, &block)

You can use it like this:

delegate :name, :to=> :other, :prefix=>true

and it will delegate a method “name” in “other” class as “other_name” in your class.
Or, alternatively

delegate :name, :to=> :other, :prefix=>'some'

will delegate “some_name” to class “other”, method “name”.

2 Responses to “Improved Rails’ delegate method”

  1. jolly jim Says:

    Awesome! This works with Merb/Datamapper as well with no changes required.


  2. Emerson Macedo Says:

    A DRY way to apply Law of Demeter with demeter gem


Leave a Reply