1
1
# frozen_string_literal: true
2
2
3
- require "set"
4
-
5
3
class Module
6
- # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
7
- # option is not used.
8
- class DelegationError < NoMethodError
9
- class << self
10
- def nil_target ( method_name , target ) # :nodoc:
11
- new ( "#{ method_name } delegated to #{ target } , but #{ target } is nil" )
12
- end
13
- end
14
- end
15
-
16
- RUBY_RESERVED_KEYWORDS = %w( __ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
17
- case class def defined? do else elsif END end ensure false for if in module next nil
18
- not or redo rescue retry return self super then true undef unless until when while yield )
19
- DELEGATION_RESERVED_KEYWORDS = %w( _ arg args block )
20
- DELEGATION_RESERVED_METHOD_NAMES = Set . new (
21
- RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
22
- ) . freeze
4
+ require "active_support/delegation"
5
+ DelegationError = ActiveSupport ::DelegationError # :nodoc:
23
6
24
7
# Provides a +delegate+ class method to easily expose contained objects'
25
8
# public methods as your own.
26
9
#
27
10
# ==== Options
28
11
# * <tt>:to</tt> - Specifies the target object name as a symbol or string
29
12
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
30
- # * <tt>:allow_nil</tt> - If set to true, prevents a +Module ::DelegationError+
13
+ # * <tt>:allow_nil</tt> - If set to true, prevents a +ActiveSupport ::DelegationError+
31
14
# from being raised
32
15
# * <tt>:private</tt> - If set to true, changes method visibility to private
33
16
#
@@ -138,7 +121,7 @@ def nil_target(method_name, target) # :nodoc:
138
121
# User.new.age # => 2
139
122
#
140
123
# If the target is +nil+ and does not respond to the delegated method a
141
- # +Module ::DelegationError+ is raised. If you wish to instead return +nil+,
124
+ # +ActiveSupport ::DelegationError+ is raised. If you wish to instead return +nil+,
142
125
# use the <tt>:allow_nil</tt> option.
143
126
#
144
127
# class User < ActiveRecord::Base
@@ -147,7 +130,7 @@ def nil_target(method_name, target) # :nodoc:
147
130
# end
148
131
#
149
132
# User.new.age
150
- # # => Module ::DelegationError: User#age delegated to profile.age, but profile is nil
133
+ # # => ActiveSupport ::DelegationError: User#age delegated to profile.age, but profile is nil
151
134
#
152
135
# But if not having a profile yet is fine and should not be an error
153
136
# condition:
@@ -174,113 +157,16 @@ def nil_target(method_name, target) # :nodoc:
174
157
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
175
158
#
176
159
# The target method must be public, otherwise it will raise +NoMethodError+.
177
- def delegate ( *methods , to : nil , prefix : nil , allow_nil : nil , private : nil , as : nil )
178
- unless to
179
- raise ArgumentError , "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
180
- end
181
-
182
- if prefix == true && /^[^a-z_]/ . match? ( to )
183
- raise ArgumentError , "Can only automatically set the delegation prefix when delegating to a method."
184
- end
185
-
186
- method_prefix = \
187
- if prefix
188
- "#{ prefix == true ? to : prefix } _"
189
- else
190
- ""
191
- end
192
-
193
- location = caller_locations ( 1 , 1 ) . first
194
- file , line = location . path , location . lineno
195
-
196
- receiver = to . to_s
197
- receiver = "self.#{ receiver } " if DELEGATION_RESERVED_METHOD_NAMES . include? ( receiver )
198
-
199
- explicit_receiver = false
200
- receiver_class = if as
201
- explicit_receiver = true
202
- as
203
- elsif to . is_a? ( Module )
204
- to . singleton_class
205
- elsif receiver == "self.class"
206
- singleton_class
207
- end
208
-
209
- method_def = [ ]
210
- method_names = [ ]
211
-
212
- method_def << "self.private" if private
213
-
214
- methods . each do |method |
215
- method_name = prefix ? "#{ method_prefix } #{ method } " : method
216
- method_names << method_name . to_sym
217
-
218
- # Attribute writer methods only accept one argument. Makes sure []=
219
- # methods still accept two arguments.
220
- definition = \
221
- if /[^\] ]=\z / . match? ( method )
222
- "arg"
223
- else
224
- method_object = if receiver_class
225
- begin
226
- receiver_class . public_instance_method ( method )
227
- rescue NameError
228
- raise if explicit_receiver
229
- # Do nothing. Fall back to `"..."`
230
- end
231
- end
232
-
233
- if method_object
234
- parameters = method_object . parameters
235
-
236
- if parameters . map ( &:first ) . intersect? ( [ :opt , :rest , :keyreq , :key , :keyrest ] )
237
- "..."
238
- else
239
- defn = parameters . filter_map { |type , arg | arg if type == :req }
240
- defn << "&block"
241
- defn . join ( ", " )
242
- end
243
- else
244
- "..."
245
- end
246
- end
247
-
248
- # The following generated method calls the target exactly once, storing
249
- # the returned value in a dummy variable.
250
- #
251
- # Reason is twofold: On one hand doing less calls is in general better.
252
- # On the other hand it could be that the target has side-effects,
253
- # whereas conceptually, from the user point of view, the delegator should
254
- # be doing one call.
255
- if allow_nil
256
- method = method . to_s
257
-
258
- method_def <<
259
- "def #{ method_name } (#{ definition } )" <<
260
- " _ = #{ receiver } " <<
261
- " if !_.nil? || nil.respond_to?(:#{ method } )" <<
262
- " _.#{ method } (#{ definition } )" <<
263
- " end" <<
264
- "end"
265
- else
266
- method = method . to_s
267
- method_name = method_name . to_s
268
-
269
- method_def <<
270
- "def #{ method_name } (#{ definition } )" <<
271
- " _ = #{ receiver } " <<
272
- " _.#{ method } (#{ definition } )" <<
273
- "rescue NoMethodError => e" <<
274
- " if _.nil? && e.name == :#{ method } " <<
275
- " raise DelegationError.nil_target(:#{ method_name } , :'#{ receiver } ')" <<
276
- " else" <<
277
- " raise" <<
278
- " end" <<
279
- "end"
280
- end
281
- end
282
- module_eval ( method_def . join ( ";" ) , file , line )
283
- method_names
160
+ def delegate ( *methods , to : nil , prefix : nil , allow_nil : nil , private : nil )
161
+ ::ActiveSupport ::Delegation . generate (
162
+ self ,
163
+ methods ,
164
+ location : caller_locations ( 1 , 1 ) . first ,
165
+ to : to ,
166
+ prefix : prefix ,
167
+ allow_nil : allow_nil ,
168
+ private : private ,
169
+ )
284
170
end
285
171
286
172
# When building decorators, a common pattern may emerge:
@@ -322,45 +208,18 @@ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil, as: n
322
208
# variables, methods, constants, etc.
323
209
#
324
210
# The delegated method must be public on the target, otherwise it will
325
- # raise +DelegationError+. If you wish to instead return +nil+,
211
+ # raise +ActiveSupport:: DelegationError+. If you wish to instead return +nil+,
326
212
# use the <tt>:allow_nil</tt> option.
327
213
#
328
214
# The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from
329
215
# delegation due to possible interference when calling
330
216
# <tt>Marshal.dump(object)</tt>, should the delegation target method
331
217
# of <tt>object</tt> add or remove instance variables.
332
218
def delegate_missing_to ( target , allow_nil : nil )
333
- target = target . to_s
334
- target = "self.#{ target } " if DELEGATION_RESERVED_METHOD_NAMES . include? ( target )
335
-
336
- module_eval <<-RUBY , __FILE__ , __LINE__ + 1
337
- def respond_to_missing?(name, include_private = false)
338
- # It may look like an oversight, but we deliberately do not pass
339
- # +include_private+, because they do not get delegated.
340
-
341
- return false if name == :marshal_dump || name == :_dump
342
- #{ target } .respond_to?(name) || super
343
- end
344
-
345
- def method_missing(method, ...)
346
- if #{ target } .respond_to?(method)
347
- #{ target } .public_send(method, ...)
348
- else
349
- begin
350
- super
351
- rescue NoMethodError
352
- if #{ target } .nil?
353
- if #{ allow_nil == true }
354
- nil
355
- else
356
- raise DelegationError.nil_target(method, :'#{ target } ')
357
- end
358
- else
359
- raise
360
- end
361
- end
362
- end
363
- end
364
- RUBY
219
+ ::ActiveSupport ::Delegation . generate_method_missing (
220
+ self ,
221
+ target ,
222
+ allow_nil : allow_nil ,
223
+ )
365
224
end
366
225
end
0 commit comments