先来看一看run_callbacks:
- def run_callbacks(kind, enumerator = :each)
- callbacks[kind].send!(enumerator) do |callback|
- case callback
- when Proc; callback.call(self)
- when String, Symbol; send!(callback)
- when Array; callback[1].call(self)
- else raise ArgumentError, "Unrecognized callback #{callback.inspect}"
- end
- end
- end
def run_callbacks(kind, enumerator = :each)
callbacks[kind].send!(enumerator) do |callback|
case callback
when Proc; callback.call(self)
when String, Symbol; send!(callback)
when Array; callback[1].call(self)
else raise ArgumentError, "Unrecognized callback #{callback.inspect}"
end
end
end
其中的callbacks(hash)是Dispatcher的类属性,用来执行一些dispatch前,后,准备的工作。这里,我们直接看看他的值
{:before=>[:reload_application,:prepare_application],:after=>[:flush_logger,:cleanup_application],:prepare=>[:activerecord_instantiate_observers,"Proc"]}。就这里而言,我们要执行所有:before的callback,不一一列出,只看其中一个:
- def reload_application
- if Dependencies.load?
- Routing::Routes.reload
- self.unprepared = true
- end
- end
def reload_application
if Dependencies.load?
Routing::Routes.reload
self.unprepared = true
end
end
这段代码揭示了在程序运行时,我们改动了routes.rb中的路由信息后,下一次request马上就能生效的原理。另外prepare_application的功能是require我们熟悉的Controller/application.rb,并且验证ActiveRecord的数据库连接是否正常(当然,你需要使用AR框架的话)。好了,这里稍微偏离了主线,接下来,让我们回到dispatch方法中,看看下面的调用handle_request:
- def handle_request
- @controller = Routing::Routes.recognize(@request)
- @controller.process(@request, @response).out(@output)
- end
def handle_request @controller = Routing::Routes.recognize(@request) @controller.process(@request, @response).out(@output) end
上面的代码非常直观,首先通过Routing系统,根据客户端的request找到相应的controller,然后执行并且将返回数据写入到@output中(这也是我前面提到的那个StringIO对象)。至于如何具体找到controller的,进入下一步吧。
(四)寻找controller
源代码:/actionpack-2.0.2/lib/action_controller/routing.rb
从前面的方法调用中,我们看出寻找controller的入口是RouteSet对象的recognize方法(还记得Routing::Routes是一个RouteSet的对象实例吗?要理解Rails中的Routing子系统,我在第二篇文章中整理的那张类图十分重要!)。下面看看此方法的具体内容:
- def recognize(request)
- params = recognize_path(request.path, extract_request_environment(request))
- request.path_parameters = params.with_indifferent_access
- "#{params[:controller].camelize}Controller".constantize
- end
def recognize(request)
params = recognize_path(request.path, extract_request_environment(request))
request.path_parameters = params.with_indifferent_access
"#{params[:controller].camelize}Controller".constantize
end
首先调用recognize_path方法,其中request.path是客户端请求的路径(比如:如果客户端访问地址是http://localhost:3000/posts,那么此参数就是/posts,extract_request_environment(request)方法只是得到请求的http方法(get,post,put,delete),当然,此方法返回的结果params便是我们的Controller和Action。我们知道,Routing系统通过path和http verb就可以确定应该使用哪个Controller的哪个Action,下面看看他是怎么做到的吧:
- def recognize_path(path, environment={})
- routes.each do |route|
- result = route.recognize(path, environment) and return result
- end
- allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
- if environment[:method] && !HTTP_METHODS.include?(environment[:method])
- raise NotImplemented.new(*allows)
- elsif !allows.empty?
- raise MethodNotAllowed.new(*allows)
- else
- raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
- end
- end
def recognize_path(path, environment={})
routes.each do |route|
result = route.recognize(path, environment) and return result
end
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
raise NotImplemented.new(*allows)
elsif !allows.empty?
raise MethodNotAllowed.new(*allows)
else
raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
end
end
如果你看过我的第二,三篇文章,我想你应该知道这里的routes数组就是Routing系统中庞大的路由表,routes数组的元素是Route对象,里面记录了相应的path pattern对应于哪个Controller的哪个Action方法。这里,通过path,和environment(http verb)参数,调用每一个Route对象的recognize方法,如果找到相应的Controller,则返回;如果未找到,则进行接下来的错误处理,这里我们可以看到很熟悉的“No route matches...”。那我们再来看看Route对象是如何通过Path和environment来识别Controller的,先来看看Route类的recognize方法:
- def recognize(path, environment={})
- write_recognition
- recognize path, environment
- end
def recognize(path, environment={})
write_recognition
recognize path, environment
end
在recognize方法中调用recognize方法?传说中的死循环?呵呵。当然不是了,看完write_recognition你就知道是怎么回事了:
- def write_recognition
- # Create an if structure to extract the params from a match if it occurs.
- body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
- body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
- # Build the method declaration and compile it
- method_decl = "def recognize(path, env={})\n#{body}\nend"
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
- method_decl
- end
def write_recognition
# Create an if structure to extract the params from a match if it occurs.
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
# Build the method declaration and compile it
method_decl = "def recognize(path, env={})\n#{body}\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
method_decl
end
这里,Rails利用instance_eval重写了该对象(此Route对象)的recognize方法(可不是override哦)。完成重写后,将再次调用recognize方法,此时,这个方法已经是动态生成的了。那么现在我们来看看这个动态方法长什么样,这里,我假设客户端访问url为http://localhost:3000/posts,并且,在routes.rb中,我们已经通过map.resources :posts建立了一系列针对posts的Route,其中当然包括“Get /posts/”(对应的Controller是posts,对应的Action是index)。那么在调用这个Route对象的write_recognition方法时,将会动态生成如下代码:
- def recognize(path, env={})
- if (match = /\A\/posts\/?\Z/.match(path)) && conditions[:method] === env[:method]
- params = parameter_shell.dup
- params
- end
- end
def recognize(path, env={})
if (match = /\A\/posts\/?\Z/.match(path)) && conditions[:method] === env[:method]
params = parameter_shell.dup
params
end
end
逻辑很简单,只是通过正则表达式来判断此Route的pattern是否与客户端请求的path一致,并且http verb也匹配,如果是的话,则将parameter_shell方法的结果dup出来,并且返回。
(注意,这里if子句的条件是通过recognition_conditions方法根据不同Route的不同condition动态生成的。因此针对每个Route,此条件都不同。另外recognition_extraction方法我一直没搞懂他干什么用的-_-!)
这里,我们还是看一看parameter_shell方法:
- def parameter_shell
- @parameter_shell ||= returning({}) do |shell|
- requirements.each do |key, requirement|
- shell[key] = requirement unless requirement.is_a? Regexp
- end
- end
- end
def parameter_shell
@parameter_shell ||= returning({}) do |shell|
requirements.each do |key, requirement|
shell[key] = requirement unless requirement.is_a? Regexp
end
end
end
无非就是将requirements(包括controller和action)塞到shell数组,然后返回。
好啦,针对路由表中的每一个Route,调用其recognize方法,知道找到匹配的Route,然后将结果(controller和action数组)返回(如未找到匹配的,则进行错误处理),接下来,我们的思路得回到RouteSet对象的recognize方法,最终,使用#{params[:controller].camelize}Controller".constantize,将controller参数转换为首字符大写的形式,并且加上“Controller”字符串,最终将整个字符串(“PostsController”),转换为一个常量(PostsController,表示控制器对象),并且,调用此Controller的process方法(此方法其实是ActionController::Base的类方法),接下来的事,后续文章会继续分析。
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




