阅读下面 jvans 的答案并再查看源代码几次后,我现在明白了:)。如果有人仍然想知道 Rails delegate 到底是如何工作的。 Rails 所做的就是在运行委托方法的文件/类中使用 (module_eval) 创建一个新方法。
例如:
class A
delegate :hello, :to => :b
end
class B
def hello
p hello
end
end
当调用 delegate 时,rails 会在 A 类中创建一个带有 (*args, &block) 的 hello 方法(技术上讲是在 A 类所写入的文件中),并且在该方法中,rails 所做的所有操作都是使用“:to”值(应该是类 A 中已经定义的对象或类)并将其分配给局部变量 _,然后只需调用该对象或类上传递参数的方法。
因此,为了让委托能够在不引发异常的情况下工作......以我们之前的示例为例。 A 的实例必须已经有一个引用类 B 的实例的实例变量。
class A
attr_accessor :b
def b
@b ||= B.new
end
delegate :hello, :to => :b
end
class B
def hello
p hello
end
end
这不是一个我已经知道的“如何在rails中使用委托方法”的问题。我想知道“委托”到底是如何委托方法的:D。在 Rails 4 源代码中,委托是在核心 Ruby Module 类中定义的,这使得它可以在所有 Rails 应用程序中作为类方法使用。
实际上我的第一个问题是 Ruby 的 Module 类是如何包含的?我的意思是每个 Ruby 类都有 > Object > Kernel > BasicObject 的祖先,并且 ruby 中的任何模块都具有相同的祖先。那么当有人重新打开 Module 类时,ruby 到底是如何向所有 ruby 类/模块添加方法的呢?
我的第二个问题是..我知道rails中的委托方法使用 module_eval 来做实际的委托,但我不太明白 module_eval 是如何工作的。
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).'
end
prefix, allow_nil = options.values_at(:prefix, :allow_nil)
if prefix == true && to =~ /^[^a-z_]/
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
method_prefix = \
if prefix
"#{prefix == true ? to : prefix}_"
else
''
end
file, line = caller.first.split(':', 2)
line = line.to_i
to = to.to_s
to = 'self.class' if to == 'class'
methods.each do |method|
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
# The following generated methods call the target exactly once, storing
# the returned value in a dummy variable.
#
# Reason is twofold: On one hand doing less calls is in general better.
# On the other hand it could be that the target has side-effects,
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
if allow_nil
module_eval(<<-EOS, file, line - 3)
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
_ = #{to} # _ = client
if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name)
_.#{method}(#{definition}) # _.name(*args, &block)
end # end
end # end
EOS
else
exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
module_eval(<<-EOS, file, line - 2)
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
_ = #{to} # _ = client
_.#{method}(#{definition}) # _.name(*args, &block)
rescue NoMethodError => e # rescue NoMethodError => e
if _.nil? && e.name == :#{method} # if _.nil? && e.name == :name
#{exception} # # add helpful message to the exception
else # else
raise # raise
end # end
end # end
EOS
end
end
end