1+ unless Object . const_defined? ( "ActiveSupport" )
2+ class Module
3+ # Provides a delegate class method to easily expose contained objects' public methods
4+ # as your own. Pass one or more methods (specified as symbols or strings)
5+ # and the name of the target object via the <tt>:to</tt> option (also a symbol
6+ # or string). At least one method and the <tt>:to</tt> option are required.
7+ #
8+ # Delegation is particularly useful with Active Record associations:
9+ #
10+ # class Greeter < ActiveRecord::Base
11+ # def hello
12+ # 'hello'
13+ # end
14+ #
15+ # def goodbye
16+ # 'goodbye'
17+ # end
18+ # end
19+ #
20+ # class Foo < ActiveRecord::Base
21+ # belongs_to :greeter
22+ # delegate :hello, to: :greeter
23+ # end
24+ #
25+ # Foo.new.hello # => "hello"
26+ # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
27+ #
28+ # Multiple delegates to the same target are allowed:
29+ #
30+ # class Foo < ActiveRecord::Base
31+ # belongs_to :greeter
32+ # delegate :hello, :goodbye, to: :greeter
33+ # end
34+ #
35+ # Foo.new.goodbye # => "goodbye"
36+ #
37+ # Methods can be delegated to instance variables, class variables, or constants
38+ # by providing them as a symbols:
39+ #
40+ # class Foo
41+ # CONSTANT_ARRAY = [0,1,2,3]
42+ # @@class_array = [4,5,6,7]
43+ #
44+ # def initialize
45+ # @instance_array = [8,9,10,11]
46+ # end
47+ # delegate :sum, to: :CONSTANT_ARRAY
48+ # delegate :min, to: :@@class_array
49+ # delegate :max, to: :@instance_array
50+ # end
51+ #
52+ # Foo.new.sum # => 6
53+ # Foo.new.min # => 4
54+ # Foo.new.max # => 11
55+ #
56+ # It's also possible to delegate a method to the class by using +:class+:
57+ #
58+ # class Foo
59+ # def self.hello
60+ # "world"
61+ # end
62+ #
63+ # delegate :hello, to: :class
64+ # end
65+ #
66+ # Foo.new.hello # => "world"
67+ #
68+ # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
69+ # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
70+ # delegated to.
71+ #
72+ # Person = Struct.new(:name, :address)
73+ #
74+ # class Invoice < Struct.new(:client)
75+ # delegate :name, :address, to: :client, prefix: true
76+ # end
77+ #
78+ # john_doe = Person.new('John Doe', 'Vimmersvej 13')
79+ # invoice = Invoice.new(john_doe)
80+ # invoice.client_name # => "John Doe"
81+ # invoice.client_address # => "Vimmersvej 13"
82+ #
83+ # It is also possible to supply a custom prefix.
84+ #
85+ # class Invoice < Struct.new(:client)
86+ # delegate :name, :address, to: :client, prefix: :customer
87+ # end
88+ #
89+ # invoice = Invoice.new(john_doe)
90+ # invoice.customer_name # => 'John Doe'
91+ # invoice.customer_address # => 'Vimmersvej 13'
92+ #
93+ # If the delegate object is +nil+ an exception is raised, and that happens
94+ # no matter whether +nil+ responds to the delegated method. You can get a
95+ # +nil+ instead with the +:allow_nil+ option.
96+ #
97+ # class Foo
98+ # attr_accessor :bar
99+ # def initialize(bar = nil)
100+ # @bar = bar
101+ # end
102+ # delegate :zoo, to: :bar
103+ # end
104+ #
105+ # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
106+ #
107+ # class Foo
108+ # attr_accessor :bar
109+ # def initialize(bar = nil)
110+ # @bar = bar
111+ # end
112+ # delegate :zoo, to: :bar, allow_nil: true
113+ # end
114+ #
115+ # Foo.new.zoo # returns nil
116+ def delegate ( *methods )
117+ options = methods . pop
118+ unless options . is_a? ( Hash ) && to = options [ :to ]
119+ raise ArgumentError , 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
120+ end
121+
122+ prefix , allow_nil = options . values_at ( :prefix , :allow_nil )
123+
124+ if prefix == true && to =~ /^[^a-z_]/
125+ raise ArgumentError , 'Can only automatically set the delegation prefix when delegating to a method.'
126+ end
127+
128+ method_prefix = \
129+ if prefix
130+ "#{ prefix == true ? to : prefix } _"
131+ else
132+ ''
133+ end
134+
135+ file , line = caller . first . split ( ':' , 2 )
136+ line = line . to_i
137+
138+ to = to . to_s
139+ to = 'self.class' if to == 'class'
140+
141+ methods . each do |method |
142+ # Attribute writer methods only accept one argument. Makes sure []=
143+ # methods still accept two arguments.
144+ definition = ( method =~ /[^\] ]=$/ ) ? 'arg' : '*args, &block'
145+
146+ if allow_nil
147+ module_eval ( <<-EOS , file , line - 2 )
148+ def #{ method_prefix } #{ method } (#{ definition } ) # def customer_name(*args, &block)
149+ if #{ to } || #{ to } .respond_to?(:#{ method } ) # if client || client.respond_to?(:name)
150+ #{ to } .#{ method } (#{ definition } ) # client.name(*args, &block)
151+ end # end
152+ end # end
153+ EOS
154+ else
155+ exception = %(raise "#{ self } ##{ method_prefix } #{ method } delegated to #{ to } .#{ method } , but #{ to } is nil: \# {self.inspect}")
156+
157+ module_eval ( <<-EOS , file , line - 1 )
158+ def #{ method_prefix } #{ method } (#{ definition } ) # def customer_name(*args, &block)
159+ #{ to } .#{ method } (#{ definition } ) # client.name(*args, &block)
160+ rescue NoMethodError # rescue NoMethodError
161+ if #{ to } .nil? # if client.nil?
162+ #{ exception } # # add helpful message to the exception
163+ else # else
164+ raise # raise
165+ end # end
166+ end # end
167+ EOS
168+ end
169+ end
170+ end
171+ end
172+ end
0 commit comments