File: active_support/core_ext/module/delegation.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: <Built-in Module>
  class: Module#1
includes
  Module ( ActiveSupport::CoreExtensions )
  ClassMethods ( ActiveSupport::Deprecation )
inherits from
  Object ( Builtin-Module )
has properties
method: delegate / 1 #99

Class Hierarchy

Object ( Builtin-Module )
  Module    #1

Code

   1  class Module
   2    # Provides a delegate class method to easily expose contained objects' methods
   3    # as your own. Pass one or more methods (specified as symbols or strings)
   4    # and the name of the target object as the final <tt>:to</tt> option (also a symbol
   5    # or string).  At least one method and the <tt>:to</tt> option are required.
   6    #
   7    # Delegation is particularly useful with Active Record associations:
   8    #
   9    #   class Greeter < ActiveRecord::Base
  10    #     def hello()   "hello"   end
  11    #     def goodbye() "goodbye" end
  12    #   end
  13    #
  14    #   class Foo < ActiveRecord::Base
  15    #     belongs_to :greeter
  16    #     delegate :hello, :to => :greeter
  17    #   end
  18    #
  19    #   Foo.new.hello   # => "hello"
  20    #   Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
  21    #
  22    # Multiple delegates to the same target are allowed:
  23    #
  24    #   class Foo < ActiveRecord::Base
  25    #     belongs_to :greeter
  26    #     delegate :hello, :goodbye, :to => :greeter
  27    #   end
  28    #
  29    #   Foo.new.goodbye # => "goodbye"
  30    #
  31    # Methods can be delegated to instance variables, class variables, or constants
  32    # by providing them as a symbols:
  33    #
  34    #   class Foo
  35    #     CONSTANT_ARRAY = [0,1,2,3]
  36    #     @@class_array  = [4,5,6,7]
  37    #     
  38    #     def initialize
  39    #       @instance_array = [8,9,10,11]
  40    #     end
  41    #     delegate :sum, :to => :CONSTANT_ARRAY
  42    #     delegate :min, :to => :@@class_array
  43    #     delegate :max, :to => :@instance_array
  44    #   end
  45    #
  46    #   Foo.new.sum # => 6
  47    #   Foo.new.min # => 4
  48    #   Foo.new.max # => 11
  49    #
  50    # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
  51    # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
  52    # delegated to.
  53    #
  54    #   Person = Struct.new(:name, :address)
  55    #
  56    #   class Invoice < Struct.new(:client)
  57    #     delegate :name, :address, :to => :client, :prefix => true
  58    #   end
  59    #
  60    #   john_doe = Person.new("John Doe", "Vimmersvej 13")
  61    #   invoice = Invoice.new(john_doe)
  62    #   invoice.client_name    # => "John Doe"
  63    #   invoice.client_address # => "Vimmersvej 13"
  64    #
  65    # It is also possible to supply a custom prefix.
  66    #
  67    #   class Invoice < Struct.new(:client)
  68    #     delegate :name, :address, :to => :client, :prefix => :customer
  69    #   end
  70    #
  71    #   invoice = Invoice.new(john_doe)
  72    #   invoice.customer_name    # => "John Doe"
  73    #   invoice.customer_address # => "Vimmersvej 13"
  74    #
  75    # If the object to which you delegate can be nil, you may want to use the
  76    # :allow_nil option. In that case, it returns nil instead of raising a
  77    # NoMethodError exception:
  78    #
  79    #  class Foo
  80    #    attr_accessor :bar
  81    #    def initialize(bar = nil)
  82    #      @bar = bar
  83    #    end
  84    #    delegate :zoo, :to => :bar
  85    #  end
  86    #
  87    #  Foo.new.zoo   # raises NoMethodError exception (you called nil.zoo)
  88    #
  89    #  class Foo
  90    #    attr_accessor :bar
  91    #    def initialize(bar = nil)
  92    #      @bar = bar
  93    #    end
  94    #    delegate :zoo, :to => :bar, :allow_nil => true
  95    #  end
  96    #
  97    #  Foo.new.zoo   # returns nil
  98    #
  99    def delegate(*methods)
 100      options = methods.pop
 101      unless options.is_a?(Hash) && to = options[:to]
 102        raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
 103      end
 104 
 105      if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
 106        raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
 107      end
 108 
 109      prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
 110 
 111      file, line = caller.first.split(':', 2)
 112      line = line.to_i
 113 
 114      methods.each do |method|
 115        on_nil =
 116          if options[:allow_nil]
 117            'return'
 118          else
 119            %(raise "#{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
 120          end
 121 
 122        module_eval(<<-EOS, file, line)
 123          def #{prefix}#{method}(*args, &block)               # def customer_name(*args, &block)
 124            #{to}.__send__(#{method.inspect}, *args, &block)  #   client.__send__(:name, *args, &block)
 125          rescue NoMethodError                                # rescue NoMethodError
 126            if #{to}.nil?                                     #   if client.nil?
 127              #{on_nil}
 128            else                                              #   else
 129              raise                                           #     raise
 130            end                                               #   end
 131          end                                                 # end
 132        EOS
 133      end
 134    end
 135  end