所以例如:
class A delegate :hello,:to => :b end class B def hello p hello end end
在委托被称为rails的时候,rails将在A类中创建一个(* args,& block)的hello方法(技术上在类A写入的文件中),所有rails都使用“:to “值(它应该是一个对象或已经在类A中定义的Class),并将其分配给局部变量_,然后调用该对象上的方法或在params中传递的Class.
所以为了让代表工作而不会提出异常…我们以前的例子. 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类都有>的祖先对象>内核> 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,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
结束
解决方法
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时,他们只是重新打开它被称为它的当前类,并动态地定义你委派的方法
我认为这会更好地回答你的问题:
Class.ancestors #=> [Module,Object,PP::ObjectMixin,Kernel,BasicObject]
由于Ruby中的所有内容都是一个类,所以方法查找链将会遍历所有这些对象,直到找到它正在寻找的内容.通过reoping模块,您可以向所有内容添加行为.这里的祖先链是有点欺骗的,因为BasicObject.class#=> Class和Module位于Class的查找层次结构中,即使BasicObject继承了repening模块的行为.在这里重新打开Module的优点在于您可以从模块内部以及类中调用此方法!很酷,自己在这里学到东西.