rails委托方法如何工作?

在阅读了下面的jvans的答案后,再看一下源代码,我现在得到它:)。 如果有人仍然想知道rails委员会的工作原理。 所有rails都在创建一个新方法,在您运行委托方法的文件/类中使用(module_eval)。

例如:

class A delegate :hello, :to => :b end class B def hello p hello end end 

在委托被调用时,rails将在类A中创建一个带有(* args,&block)的hello方法(技术上在写入类A的文件中),并且在该方法中,所有rails都使用“:to”值(应该是已经在类A中定义的对象或类)并将其分配给局部变量_,然后只调用该对象上的方法或传递参数的类。

因此,为了让代表在不引发exception的情况下工作……使用我们之前的示例。 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 (eg 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(< 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的多重inheritance版本,所以当你这样做时:

  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时,他们只是重新打开当前正在调用它的类并动态定义你委派的方法

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

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

因为ruby中的所有东西都是一个类,所以方法查找链将遍历所有这些对象,直到它找到它正在寻找的东西。 通过重新组合模块,您可以为所有内容添加行 这里的祖先链有点欺骗性,因为BasicObject.class#=> Class和Module在Class的查找层次结构中,甚至BasicObject也inheritance了repening模块的行为。 在Class上重新打开Module的优点是你现在可以在一个模块内以及一个类中调用这个方法! 很酷,自己在这里学到了一些东西。

在阅读了下面的jvans的答案后,再看一下源代码,我现在得到它:)。 如果有人仍然想知道rails委员会的工作原理。 所有rails都在创建一个新方法,在您运行委托方法的文件/类中使用(module_eval)。

例如:

  class A delegate :hello, :to => :b end class B def hello p hello end end 

在委托被调用时,rails将在类A中创建一个带有(* args,&block)的hello方法(技术上在写入类A的文件中),并且在该方法中,所有rails都使用“:to”值(应该是已经在类A中定义的对象或类)并将其分配给局部变量_,然后只调用该对象上的方法或传递参数的类。

因此,为了让代表在不引发exception的情况下工作……使用我们之前的示例。 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