- 浏览: 41047 次
- 性别:
- 来自: 北京
最新评论
-
lijingtx:
为什么我报错了。in `alias_method`:undef ...
Rails中如何更加优雅的处理文件上传 -
gigix:
woody_420420 写道rainchen 写道几时cuc ...
让测试并行起来吧 -
woody_420420:
rainchen 写道几时cucumber也能并发跑scena ...
让测试并行起来吧 -
rainchen:
有时为了保证测试环境和开发、生产的特性一致,减少非必要的环境差 ...
让测试并行起来吧 -
woody_420420:
是的。耗时主要在数据库访问上。
内存数据库,我倒真没想过用这个 ...
让测试并行起来吧
-
前言
在前一篇文章中,我大致的讲解了一下Rails的启动过程,并罗列了个人觉得比较核心的源代码进行分析,算是管中窥豹吧~在分析initializer.rb代码的时候,我说过“initializer.rb的介绍暂时结束”,因为我特意略过了初始化过程中一个十分相当非常重要的过程--Routing的载入。这里,我专门用这篇文章来讲解一下。
Routing之于Rails就如同waiter(waitress)之于饭店。当你怀揣着这个月辛辛苦苦写软件得来的工资,来到一个上档次的饭店,如果没有门口的门生引领你到空闲的饭桌,上菜谱,点菜。。。恐怕哥们你只能一进门就甩着手中的票子大吼:给老子上两斤牛肉,一斤烧酒:)是的。当你通过浏览器指定到某一个Rails程序的URL时,Routing就跳了出来,通过分析,按照Rails内部的机制,指定相应的Controller,并执行上面相应的Action,于是,你得到服务了(小费就免了)~
(关于Routing系统的具体机制,请参见《Agile Web Development with Rails 2nd》或者《The Rails
Way》,里面有大把好的资料)
-
Routing的载入
好了,言归正传,在平日的开发中,我们很会很熟练的在/config/routes.rb中写类似如下的一些Routing信息(当然一些可以自动生成):
ActionController::Routing::Routes.draw do |map| map.resources :comments map.root :controller=>'posts' map.resources :posts map.resources :posts, :has_many => :comments map.namespace :admin do |admin| admin.resources :posts end map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' ... end
下面,我将讲讲Routing这个如此重要的东东是如何被载入的。按照自己学习的方法,我仍然还是喜欢先整理一张关于Routing载入所执行源代码的执行顺序图。如下所示:
请注意,由于Routing的载入仍然属于Rails启动阶段的工作,所以,我直接在上篇文章所用的代码执行顺序图的基础上进行了扩充(也删减了一些无关痛痒的部分),图中红色剪头表示Routing载入的相关执行代码。好了,下面,让我们稍微深入一点,详细看看每个代码文件到底都做了一些什么事情,来完成Routing的载入工作。
environment.rb
源代码路径:RAILS_ROOT/config/environment.rb
此代码文件内容比较少,且十分容易理解,我先把代码整理贴出来:
RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') Rails::Initializer.run do |config| ... end
第一行设置Rails
Gem的版本,用于加载相应版本的Rails,第二行执行的代码通过载入boot文件达到启动rails的目的(此时Rails还未初始化)。好了,到了Rails::Initializer.run方法了,还记得我前篇文章提到的此方法吗?:)让我们再深入的看一看吧。
initializer.rb
源代码路径:gems/rails-2.0.2/lib/initializer.rb
这次我们直接点,再来看看Initializer类的类方法run长什么样子:
def self.run(command = :process, configuration = Configuration.new) yield configuration if block_given? initializer = new configuration initializer.send(command) initializer end
在environment.rb中对run方法的调用,没有带任何参数,所以command是默认的:process(重要),configuration当然是重新生成一个新的配置类。当然,顺便提一下,这次的调用带了一个block,因此,第一行代码将Configuration实例yield出去,用户可以在environment.rb中对Rails的任何配置进行修改(尽管如此,我们需要修改的配置不会太多)。接下来,很直观的事,向initializer实例发送:process消息(调用process实例方法)。
在initializer的process实例方法中,会做很多事情,我先用下表罗列出来,并附上简单描述。
方法名 | 描述 |
check_ruby_version | 检测ruby的版本(最低版本为1.8.2。注意:不支持1.8.3) |
set_load_path | 设置装载路径 |
require_frameworks | 载入Rails的其他组件(AR,AV...) |
set_autoload_paths | 设置Rails自动加载源代码文件的路径(包括load_once) |
add_plugin_load_paths | 设置插件的load_path |
load_environment | 载入环境(根据目前运行环境development/test/production,载入正确的*.rb) |
initialize_encoding | 初始化编码(默认UTF-8) |
initialize_database | 初始化数据库(给AR传递数据库配置) |
initialize_logger | 初始化日志记录程序 |
initialize_framework_logging | 初始化各个组件(AR,AC,AM...)的日志记录程序 |
initialize_framework_views | 分别初始化AM和AC的template根目录和view路径 |
initialize_dependency_mechanism | 初始化依赖载入机制(这个以后将会详细谈到) |
initialize_whiny_nils | 初始化警告系统(在nil上调用某一个方法) |
initialize_temporary_directories | 初始化临时目录(为session,cache准备的目录) |
initialize_framework_settings | 初始化各个组件(通过send调用各个组件的setting方法) |
add_support_load_paths | 暂时未使用 |
load_plugins | 载入插件 |
load_observers | (以后有深入了解再记录) |
initialize_routing | 初始化Routing |
after_initialize | 调用用户提供的block(完成初始化后执行的block) |
load_application_initializers | 载入/config/initializers/目录下的所有.rb文件 |
当然,现在我主要关心表中黑体标出的initialize_routing方法,至于其他方法,感兴趣的同学们可以自己查看源代码,都十分简单易懂。下面,先来看看initialize_routing的内容
def initialize_routing return unless configuration.frameworks.include?(:action_controller) ActionController::Routing.controller_paths = configuration.controller_paths ActionController::Routing::Routes.reload end
第一,没有哪位同学用Rails不使用action_controller组件吧?呵呵。这里,我们暂时只用记住第三行执行代码Routes.reload,并且暂时先记住Routes是routing.rb中定义的一个RouteSet实例。那么。。。还等什么,到routing.rb里面去转转吧。
routing.rb
源代码路径:/actionpack-2.0.2/lib/action_controller/routing.rb
(actionpack?Agile Web Development with Rails 2nd!嘿嘿)
主角总算登场了,Routing可以算得上Rails当中最复杂的功能系统之一。此代码文件内容相当的多,首先,我先按照如何将大象放进冰箱的方法学,从一个很高的角度看一看,Rails是如何将Routing载入的(明显会比装大象的步骤多)。下面是一张很高层次的流程图:
从这个层次来看,Routing的载入流程比较清晰,所做的工作无非就是首先判断是否需要载入,如需载入,则清空原来保存的所有信息,然后重新从config/routes.rb中载入所有Routing信息。现在,先将这幅大流程图保留在我们的脑海中,接下来,深入细节看看。routing.rb中内容较多,包含的类也较多,先来看一看此代码文件中包含类的类图:
(为了直观起见,我只将个人认为核心的类描绘出来,并且只将与载入Routing相关的方法列了出来;从简洁的角度出发,我还省略了方法参数)
Route类
此类代表一个普通的Routing信息,例如“'test/:controller/show/:id/*spec',:action=>"show",
:requirements => { :id =>/\d+/}, :conditions => { :method => :get
}”。
segments----代表一个routing信息中path('test/:controller/show/:id/*spec')的“片段”数组。前面那个示意routing信息的“片段”数组有类似如下信息:["/","test","/",":controller","/","show","/",":id","spec"]
requirements----代表一个routing信息中的:requirements {:id => /\d+/}
conditions----代表一个routing信息中的:conditions
{:method=>:get}。注意,目前版本的conditions只支持:method。
(此类还包括很多非常重要的实例方法,比如:识别一个请求应该调用哪个Controller和Action;如何通过link_to,redirect_to等生成一个URL,但是这里我主要讲Routing的加载,所以这部分内容留到以后再讲。)
NamedRouteCollection类
次类代表一个named
routes集合。有些类似一个Hash,一个name对应着一组普通的route。
RouteSet类
还记得initializer.rb中的ActionController::Routing::Routes么?前面说了,他是一个RouteSet的实例。从类名就可以看出来,此类是一个Route的集合,他包括一个普通route的数组,还有一个named
route的集合。就Rails框架而言,此类是Routing系统的对外接口,Rails框架的其他部分会通过他来载入route信息,识别请求路径,生成URL等等。此类有两个内部类Mapper和NamedRouteCollection。
Mapper类
就用户而言,此类是Routing系统的对外接口,我们会在config/routes.rb用如下代码增加routes信息:
ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end
block中的参数map就是一个Mapper类的实例,我们就是通过调用他的connect等实例方法增加route信息。Mapper类包含一个RouteSet类实例,其实Mapper类只是一个Wrapper而已,讲所有的调用操作都转发到了RouteSet类实例。
RouteBuilder类
此类是Rails内部使用的类,确切的说,是给RouteSet实例使用的类。此类用于将用户提供的所有route信息Build成一个个普通的Route对象(然后当然是存放在RouteSet的普通route数组中或者named
routes集合中)。
Segment类
在讲Route类的时候,我提到了“片段”,这就是Segment类所抽象的东西。他是所有“片段”对象的父类。
DynamicSegment类
代表一个动态Segment,有两个子类,PathSegment和ControllerSegment。PathSegment表示Route
globbing,ControllerSegment表示代表controller的片段。
StaticSegment类
代表一个静态Segment,有一个子类,DividerSegment,表示分隔符片段。
相当抽象是吗?好了,那我们来看一个实际例子,你就会完全理解Segment了。例如,现在我们在config/routes.rb中定义了一个如下的route信息:
map.connect
'test/:controller/show/:id/*spec',:action=>"show", :requirements => { :id
=>/\d+/}, :conditions => { :method => :get }
当Rails框架通过map对象的connect方法调用后,会在内部将这个route分解为如下的Segment:
Segment类型 | 值 |
DividerSegment | / |
StaticSegment | test |
DividerSegment | / |
ControllerSegment | :controller |
DividerSegment | / |
StaticSegment | show |
DividerSegment | / |
DynamicSegment | :id |
DividerSegment | / |
PathSegment | :spec |
DividerSegment | / |
(注意,第一个和最后一个"/",尽管我们提供path的时候是没有的,但是Rails的Routing系统内部会自动加上的。)
通过这个具体的例子,我想对Segment已经不用再多做解释了,我想大家应该很了解各种Segment的用途了。为什么要将我们提供的route信息中的path分成Segment?当然是用于分析request和生成url用的了。
好了,我费了很多唾沫在介绍Routing高层面的东西上。我想如果你看了以上的东西,可能还不是很明了整个过程(如果你看完就明白了,我只能说我太有才了~~^0^你更有才!),下面,我们就来看一看一些关键的代码片段,希望能起到一个融会贯通的作用。
首先,我们还是先看看RouteSet类中的入口相关方法:
def load! Routing.use_controllers! nil # Clear the controller cache so we may discover new ones clear! load_routes! install_helpers end def reload if @routes_last_modified && defined?(RAILS_ROOT) mtime = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime # if it hasn't been changed, then just return return if mtime == @routes_last_modified # if it has changed then record the new time and fall to the load! below @routes_last_modified = mtime end load! end def load_routes! if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes load File.join("#{RAILS_ROOT}/config/routes.rb") @routes_last_modified = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime else add_route ":controller/:action/:id" end end
如果你脑中现在还有那张装大象方法学的流程图,我想你对上面的代码会理解得十分透彻,不用过多解释。下面再看看和用户(config/route.rb)打交道的Mapper类长什么样:
class Mapper #:doc: def initialize(set) #:nodoc: @set = set end # Create an unnamed route with the provided +path+ and +options+. See # ActionController::Routing for an introduction to routes. def connect(path, options = {}) @set.add_route(path, options) end ... end
其中实例变量@set是一个RouteSet对象实例,Mapper所做的工作基本都是转发消息到RouteSet实例。接下来,当然应该顺藤摸瓜的看一看RouteSet类的实例方法add_route都干了些什么了:
def add_route(path, options = {}) route = builder.build(path, options) routes << route route end
很简单,只是调用builder对象(RouteBuilder类)的build方法返回一个Route对象实例,然后存放在数组routes中。path和options?我想你应该知道他们是什么了吧?
path : 'test/:controller/show/:id/*spec'
options : {:action=>"show",
:requirements => { :id =>/\d+/}, :conditions => { :method => :get }
}
看这样子,真正干活的还是我们的Builder兄弟了,那让我们来看看他RouteBuilder对象的build实例方法都干了些什么:
def build(path, options) # Wrap the path with slashes path = "/#{path}" unless path[0] == ?/ path = "#{path}/" unless path[-1] == ?/ path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix] segments = segments_for_route_path(path) defaults, requirements, conditions = divide_route_options(segments, options) requirements = assign_route_options(segments, defaults, requirements) route = Route.new route.segments = segments route.requirements = requirements route.conditions = conditions ... route end end
我删减了一些代码,只将核心流程留了下来。首先,为path加两个保护罩(前后各加一个“/”),当然,如果不存在的话。接着,将path五马分尸,拆解成一个segments数组(具体规则?可以稍微看一下前面的内容)。接下来,会得到option对象中包含的requirement,conditions等信息。大功告成,最后生成一个Route实例,将相应属性赋给他,返回。Builder使命结束。RoutesSet的add_route得到这个route实例,保存在普通routes数组中。
keep going。。。循环处理完所有config/routes.rb中的route信息,就完成了整个路由表的载入!
(PS:关于Routing的载入暂时先告一个段落。你可能会发现,我没有提到named
route的载入,并且我也完全忽视了代码执行顺序图中resource.rb的存在,因为,我觉得可以把named route单独开一个话题来讲)
(再PS:本文可能不足以让你完全理解Routing载入的每一个细节,但是希望本文能起到一块转头的作用----当然不是拍人用----而是可以引来很多玉)
评论
我最不明白的一点!就是路由怎么会是在服务加载的时候进行处理呢?
我觉得是一个请求来时根据配置好路由来进行处理!
可能我是一个一根肠子到底的人吧!
建议全部系列完成后出个pdf图文完整版吧!
(小声说一句:javaeye现在实行 残酷地、专制地 发帖管理政策,大部分的鼓励帖/顶贴都被视为灌水给封杀了,不要以为没人支持阿,呵呵)
发表评论
-
Rack Middleware Profile
2009-05-26 21:27 1532Rack是一个高效,简洁的框架(Webserver Int ... -
column_timestamp plugin
2008-10-16 00:00 1055有些时候,我们可能需要记录某些列的更新时间,类似于rail ... -
为Rails中的validation error增加error_code
2008-08-05 22:49 1951各位同学对model中一 ... -
Rails中如何更加优雅的处理文件上传
2008-07-19 22:23 2224通常,在rails中处理文件上传,我们会这么做,在view ... -
慎用typo(theme_support)的换肤机制
2008-07-17 23:29 1807前言 本文提到的typo版本是目前最新的5.0.3 ... -
Ruby中&&操作符的妙用(旁门左道)
2008-07-09 22:30 1798几乎所有的现代编程 ... -
Ruby生成斐波拉契数列
2008-07-09 13:52 1805不管你是用c,c++,c#,java。。。不管你是用循环, ... -
Ruby On Rails-2.0.2源代码分析(4)-寻找Controller
2008-03-24 20:25 2971前言 经过一番试 ... -
Ruby On Rails-2.0.2源代码分析(3)-named route和resource
2008-03-21 00:28 2677前言 在《Routing的载入》中,我大致介绍了一 ... -
netbean调试ActiveSupport::OptionMerger需注意的一个问题
2008-03-18 15:08 1660这两天,在调试Rails ... -
Ruby On Rails-2.0.2源代码分析(1)-Rails的启动
2008-03-12 23:32 5449前言 本文主要是 ...
相关推荐
Ruby on Rails Guides v2 - Ruby on Rails 4.2.5
A cheatsheet for Ruby on Rails
rails-dev-box, 面向 Ruby on Rails 核心开发的虚拟机 用于 Ruby on Rails 核心开发的虚拟机简介注意:这个虚拟机不是为 Rails 应用程序开发而设计的,只是为。 这个项目自动设置开发环境,以便在 Ruby on Rails ...
《Ruby on Rails Tutorial》中文版(原书第2版,涵盖 Rails 4) Ruby 是一门很美的计算机语言,其设计原则就是“让编程人员快乐”。David Heinemeier Hansson 就是看重了这一点,才在开发 Rails 框架时选择了 Ruby...
Ruby on Rails安装,有人说难,但其实也很方便。要基于ruby开发应用程序,我们必须安装ruby、gem、rails、mongrel。
请结合我上次上传的“Ruby中文文档”,学习了解之后,再来实际操作,理解“Ruby On Rails”框架开发web程序,这个是很不错的实例,能够在短期内实现Rails的web开发。
Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 ...
Ruby on Rails入门经典代码,非常适合新手学习使用
Ruby On Rails中文教材(PDF)
Ruby on Rails源代码
ruby on rails社区网站开发源码
本书算是最好的rails的与ruby的教程。。。全书共144M,此为part2,part1: http://download.csdn.net/detail/jiaoxiaogu/3835985
Ruby on Rails与MongoDB 您可以在MongoDB的帮助下轻松... rails new ruby-on-rails-with-mongodb --skip-active-record从您的Gemfile中删除sqlite3(如果存在),将Mongoid添加到您的Gemfile中,然后运行“ bundle”。
原文是Web版本,已经导出成PDF版本供大家查看。原版是英文版的《Ruby on Rails Tutorial》,特别适合有其他语言开发经验的Rails入门。
Ruby on Rails入门经典-例子,有很多rails工程实例。
Ruby on Rails Web开发学习实录 内容简介: 在目前的主流web开发技术中,基于ruby语言的rails框架是做网站开发速度最快的工具。它可以达到j2ee框架开发速度的5~10倍,并且代码量也非常少。另外由于代码量的大幅度...
ruby_on_rails 开发者实战 源代码上 1-12章 来之不易啊。仅供学习.
本书算是最好的rails的与ruby的教程。。。全书共144M,此为part1,还有part2
ruby on rails对mongodb的操作ruby on rails对mongodb的操作ruby on rails对mongodb的操作ruby on rails对mongodb的操作