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.
7
# Delegation is particularly useful with Active Record associations:
9
# class Greeter < ActiveRecord::Base
10
# def hello() "hello" end
11
# def goodbye() "goodbye" end
14
# class Foo < ActiveRecord::Base
16
# delegate :hello, :to => :greeter
19
# Foo.new.hello # => "hello"
20
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
22
# Multiple delegates to the same target are allowed:
24
# class Foo < ActiveRecord::Base
26
# delegate :hello, :goodbye, :to => :greeter
29
# Foo.new.goodbye # => "goodbye"
31
# Methods can be delegated to instance variables, class variables, or constants
32
# by providing them as a symbols:
35
# CONSTANT_ARRAY = [0,1,2,3]
36
# @@class_array = [4,5,6,7]
39
# @instance_array = [8,9,10,11]
41
# delegate :sum, :to => :CONSTANT_ARRAY
42
# delegate :min, :to => :@@class_array
43
# delegate :max, :to => :@instance_array
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
54
# Person = Struct.new(:name, :address)
56
# class Invoice < Struct.new(:client)
57
# delegate :name, :address, :to => :client, :prefix => true
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"
65
# It is also possible to supply a custom prefix.
67
# class Invoice < Struct.new(:client)
68
# delegate :name, :address, :to => :client, :prefix => :customer
71
# invoice = Invoice.new(john_doe)
72
# invoice.customer_name # => "John Doe"
73
# invoice.customer_address # => "Vimmersvej 13"
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:
81
# def initialize(bar = nil)
84
# delegate :zoo, :to => :bar
87
# Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
91
# def initialize(bar = nil)
94
# delegate :zoo, :to => :bar, :allow_nil => true
97
# Foo.new.zoo # returns nil
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)."
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."
109
prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
111
file, line = caller.first.split(':', 2)
114
methods.each do |method|
116
if options[:allow_nil]
119
%(raise "#{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
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?