Rails::Application中的一个方法调用

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

Rails::Application中的一个方法调用

上一篇:Android 自定义View


下一篇:php 解决json_encode中文null和UNICODE转码问题