gpt4 book ai didi

ruby - Rails 委托(delegate)方法如何工作?

转载 作者:数据小太阳 更新时间:2023-10-29 06:37:00 30 4
gpt4 key购买 nike

在阅读了下面 jvans 的回答并多看了几次源代码之后,我现在明白了:)。如果有人仍然想知道 Rails 委托(delegate)的工作原理。 Rails 所做的只是在您运行委托(delegate)方法的文件/类中使用 (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 所做的一切都是使用“:到”值(它应该是一个对象或一个已经在类A中定义的类)并将其分配给一个局部变量_,然后调用该对象或传递参数的类的方法。

因此,为了让委托(delegate)在不引发异常的情况下工作……使用我们之前的示例。 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 中使用委托(delegate)方法”的问题,我已经知道了。我想知道“委托(delegate)”到底是如何委托(delegate)方法的:D。在 Rails 4 源代码中,委托(delegate)在核心 Ruby 模块类中定义,这使得它可以在所有 Rails 应用程序中作为类方法使用。

实际上我的第一个问题是 Ruby 的 Module 类是如何包含的?我的意思是每个 Ruby 类都有 > Object > Kernel > BasicObject 的祖先,而 ruby​​ 中的任何模块都有相同的祖先。那么当有人重新打开 Module 类时,ruby 到底是如何向所有 ruby​​ 类/模块添加方法的呢?

我的第二个问题是..我知道 rails 中的委托(delegate)方法使用 module_eval 进行实际委托(delegate),但我并不真正理解 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

结束

最佳答案

Ruby 不会在这里重新打开模块类。在 ruby​​ 中,类 Module 和类 Class 几乎相同。

    Class.instance_methods - Module.instance_methods #=> [:allocate, :new, :superclass]

主要区别在于您不能“新建”模块。模块是多重继承的 ruby​​ 版本,所以当你这样做时:

 module A
end
module B
end

class C
include A
include B
end

在幕后,ruby 实际上正在创建一种叫做匿名类的东西。所以上面实际上等同于:

 class A
end
class B < A
end
class C < B
end

module_eval 这里有点骗人。您正在查看的代码中没有任何内容与模块打交道。 class_eval 和 module_eval 是同一件事,它们只是重新打开它们被调用的类,所以如果你想向类 C 添加方法,你可以这样做:

 C.class_eval do 
def my_new_method
end
end

 C.module_eval do 
def my_new_method
end
end

两者都相当于手动重新打开类并定义方法

  class C
end
class C
def my_new_method
end
end

所以当他们在上面的源代码中调用 module_eval 时,他们只是重新打开当前正在调用它的类并动态定义您要委托(delegate)的方法

我认为这会更好地回答您的问题:

 Class.ancestors #=> [Module, Object, PP::ObjectMixin, Kernel, BasicObject]

因为 ruby​​ 中的一切都是一个类,方法查找链将遍历所有这些对象,直到找到它要查找的内容。通过重新操作模块,您可以为所有内容添加行为。这里的祖先链有点欺骗性,因为 BasicObject.class #=> Class 和 Module 在 Class 的查找层次结构中,即使 BasicObject 也继承了重复模块的行为。在此处重新打开 Module 相对于 Class 的优势在于,您现在可以从模块内以及类内调用此方法!非常酷,我自己在这里学到了一些东西。

关于ruby - Rails 委托(delegate)方法如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20873591/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com