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

时间: 2024-10-24 03:47:54

Rails::Application中的一个方法调用的相关文章

在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法

在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的. 比如,下面代码例子中,有两方法,一个有@Transational注解,一个没有.如果调用了有注解的addPerson()方法,会启动一个Transaction:如果调用updatePersonByPhoneNo(),因为它内部调用了有注解的addPerson(),如果你以为系统也会为它启动一个Transaction,那就错了,实际上是没有的. @Service public cla

vue中methods一个方法调用另外一个方法

简谈 JavaScript、Java 中链式方法调用大致实现原理

相信,在 JavaScript .C# 中都见过不少链式方法调用,那么,其中实现该类链式调用原理,大家有没有仔细思考过?其中 JavaScript 类库:jQuery 中就存在大量例子,而在 C# 中,其中 lambda 表达式的大量使用以及扩展方法的新增,使链式调用也见的不少. 首先,就谈谈 JavaScript 中链式调用,其实,也是就是运用之前提及的 this . var Person=function(name,age){ this.Name=name; this.Age=age; };

Fragment中的setUserVisibleHint()方法调用

使用Fragment的时候难免会遇到想在视图可见与不可见之中做些操作,此时通常会想到类似Activity中的onResume()和onPause()方法.Fragment中也确实有这两个方法,然而亲测support.v4.Fragment中的这两个方法其实是执行的Activity的方法,也就是只有在Activity的可视性发生变化时才会调用.又从网上找到类似的方法,setUserVisibleHint(), 但是事实证明改方法也不见得就能满足需求,在Fragment中重写该方法系统并不会调用改方

struts2中的动态方法调用

1.首先,strtus2.xml中配置开启动态调用方法: <constant name="struts.enable.DynamicMethodInvocation" value="true" /> 2.动态方法调用不是直接使用action的名字,而是使用actionNamme!methodName的方式进行调用,如:loginAction!login.

Struts2中DMI(动态方法调用)

1 <package name="front" namespace="/front" extends="struts-default"> 2 <default-action-ref name="index" /> 3 <action name="helloword" class="struts.IndexAction"> 4 <result na

关于c++中public &amp; private方法调用问题

class IDNoIdentifier { public: IDNoIdentifier(); ~IDNoIdentifier(); typedef vector<cv::Rect> CvRectVectorType; bool Init(); bool Cleanup(); bool HandleData(const Mat& rawImg,string& sNoInfo,string& sError); private: bool IDNoLocation(con

java中计算一个方法执行时长,耗费单位(秒)

long startTime=System.currentTimeMillis(); //执行方法 long endTime=System.currentTimeMillis(); float excTime=(float)(endTime-startTime)/1000; System.out.println("执行时间:"+excTime+"s");

关于Laravel中使用response()方法调用json()返回数据unicode编码转换的问题解决

laravel默认返回的json是unicode码,如果为直接可以看的文字,后面要加  ->setEncodingOptions(JSON_UNESCAPED_UNICODE) 不让转成unicode码 return Response::json($data, $this->getStatusCode(), $header)->setEncodingOptions(JSON_UNESCAPED_UNICODE); public function response(){ // 返回json