Ruby语言的动态性使得想仅仅通过查找相关API文档来学习Rails是完全不够的,因为API文档只记录了静态代码定义的方法,而Rails中有很多的方法都是在加载时或运行时动态生成的,想要了解这些方法必须要阅读源代码。本文以Rails工程下的config/application.rb文件中的一个方法调用为例,分析方法调用的具体过程。
1. 问题描述
假设Rails工程叫做Sample,那么在config/application.rb中会定义Sample::Application,并可以在其中调用config方法,如下:
1 module Sample 2 class Application < Rails::Application 3 ... 4 config.autoload_paths << "#{config.root}/lib/validators" 5 ... 6 end 7 end
config方法在API文档中是不存在的,下面将分析config方法是如何被找到的。
2. 方法查找
在Sample::Application的定义中,self=Sample::Application,所以config是Sample::Application的类方法。查找Sample::Application的祖先链,并逐一查找,终于在Rails::Railtie中找到了该方法,如下:
1 Sample::Application.ancestors 2 # => [FirstApp::Application, Rails::Application, Rails::Engine, Rails::Railtie, Rails::Initializable, Object, PP::ObjectMixin, ActiveSupport::Dependencies::Loadable, V8::Conversion::Object, JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject] 3 4 Rails::Railtie.singleton_methods(false).grep /^config$/ 5 # => [:config] 6 7 #也可以用source_location来查看方法的位置 8 Sample::Application.method(:config).source_location 9 # => ["/usr/local/rvm/gems/ruby-2.1.1/gems/railties-4.1.4/lib/rails/railtie.rb", 123]
最终在Rails::Railtie中找到该方法定义,如下:
1 module Rails 2 class Railtie 3 ... 4 class << self 5 ... 6 delegate :config, to: :instance 7 ... 8 end 9 ... 10 end 11 end
可以看到第6行采用了元编程的方式定义了config方法,则直到文件被加载完毕config方法才会生成,因此该方法不会出现在API文档中。
3. 方法的定义
config方法的定义是挺有趣的,它利用了Rails对Module的扩展delegate方法进行了定义,采用了委托模式,如下是delegate方法中的核心部分:
1 class Module 2 ... 3 def delegate(*methods) 4 ... 5 methods.each do |method| 6 ... 7 method_def = [ 8 "def #{method_prefix}#{method}(#{definition})", 9 " _ = #{to}", 10 " if !_.nil? || nil.respond_to?(:#{method})", 11 " _.#{method}(#{definition})", 12 " else", 13 " #{exception unless allow_nil}", 14 " end", 15 "end" 16 ].join ‘;‘ 17 18 module_eval(method_def, file, line) 19 end 20 end 21 end
根据上面代码,delegate :config, to: :instance 相当于如下代码(进行了省略),即在Rails::Railtie中定义了一个类方法:
1 class << Rails::Railtie 2 def config 3 instance.config 4 end 5 end
4. 方法的调用
最后在问题描述中,Sample::Application调用了了方法,调用过程相当于执行了如下代码:
1 Sample::Application.instance.config
instance方法是类方法,定义于Rails::Railtie中,其定义以及根据instance代码得到的中间结果如下:
1 #instance的定义 2 class << self 3 def instance 4 @instance ||= new 5 end 6 end 7 8 #根据instance的定义,得到中间结果 9 Sample::Application.new.config
此时self是Sample::Application.new,config方法是一个实例方法,定义于Rails::Application中,其定义以及最终执行结果如下:
1 #config方法的定义 2 def config #:nodoc: 3 @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) 4 end 5 6 #最终执行结果 7 Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd))
由此可以知道config方法是如何被调用的了。
5. 总结
上面分析了Rails::Application中的一个方法调用的例子,可以看出还是比较复杂的,尤其是在对self的判断上要绝对清晰,否则可能会对被调用的方法产生判断错误。Module#delegate方法是一个Rails中常用的委托方法,未来将详细讨论下该方法。
Rails::Application中的一个方法调用,布布扣,bubuko.com