- 浏览: 42064 次
- 性别:
- 来自: 北京
最新评论
-
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 1571Rack是一个高效,简洁的框架(Webserver Int ... -
column_timestamp plugin
2008-10-16 00:00 1092有些时候,我们可能需要记录某些列的更新时间,类似于rail ... -
为Rails中的validation error增加error_code
2008-08-05 22:49 1994各位同学对model中一 ... -
Rails中如何更加优雅的处理文件上传
2008-07-19 22:23 2268通常,在rails中处理文件上传,我们会这么做,在view ... -
慎用typo(theme_support)的换肤机制
2008-07-17 23:29 1852前言 本文提到的typo版本是目前最新的5.0.3 ... -
Ruby中&&操作符的妙用(旁门左道)
2008-07-09 22:30 1855几乎所有的现代编程 ... -
Ruby生成斐波拉契数列
2008-07-09 13:52 1838不管你是用c,c++,c#,java。。。不管你是用循环, ... -
Ruby On Rails-2.0.2源代码分析(4)-寻找Controller
2008-03-24 20:25 3081前言 经过一番试 ... -
Ruby On Rails-2.0.2源代码分析(3)-named route和resource
2008-03-21 00:28 2760前言 在《Routing的载入》中,我大致介绍了一 ... -
netbean调试ActiveSupport::OptionMerger需注意的一个问题
2008-03-18 15:08 1718这两天,在调试Rails ... -
Ruby On Rails-2.0.2源代码分析(1)-Rails的启动
2008-03-12 23:32 5576前言 本文主要是 ...
相关推荐
Ruby on Rails 安装指南是指安装 Ruby 1.8.6 和 Rails 2.0.2 的详细步骤。首先,需要下载 Ruby One-Click Installer 版本,并安装 Ruby。然后,下载 Rails 2.0.2 版本,并安装。接下来,需要安装 Mongrel 服务器。...
### Ruby on Rails Cheat Sheet 本篇文章将从给定的文件中提炼出关于Ruby on Rails的重要知识点,主要包括命令、URL映射、命名规范、ERB标签、链接创建、数据库配置及查询、模型之间的关系等方面。 #### Ruby on ...
3. **创建步骤定义(Step Definitions)**:在Ruby代码中为Gherkin步骤编写对应的实现,将自然语言转化为可执行的代码。 4. **运行测试**:执行特性文件,验证系统是否按预期工作。BDD工具如Cucumber会自动匹配并...
在本文中,我们将深入分析 Ruby on Rails 2.0.2 的源代码,特别是关注其启动过程。 首先,Rails 的启动始于 `config/boot.rb` 文件。这个文件是 Rails 应用程序的入口点,负责检查 Rails 是否已启动。如果没有,它...
### Ruby on Rails Guides v2 - Ruby on Rails 4.2.5 #### 一、重要概念及基础假设 - **重要概念**:本指南旨在帮助读者深入理解Ruby on Rails(以下简称Rails)4.2.5版本的核心功能与最佳实践。 - **基础假设**:...
本压缩包中的"Ruby on Rails入门经典代码"提供了新手学习Rails的宝贵资源,帮助初学者快速掌握这个强大的框架。 1. **Rails基础知识**: - MVC架构:Rails的核心设计模式,模型负责数据处理,视图负责展示,控制器...
rails-dev-box, 面向 Ruby on Rails 核心开发的虚拟机 用于 Ruby on Rails 核心开发的虚拟机简介注意:这个虚拟机不是为 Rails 应用程序开发而设计的,只是为。 这个项目自动设置开发环境,以便在 Ruby on Rails ...
Ruby on Rails(简称RoR或Rails)是一种基于Ruby语言的开源Web应用框架,它遵循Model-View-Controller(MVC)架构模式,旨在使Web开发更简洁、高效。本实例将帮助你深入理解和实践Rails的开发流程。 首先,让我们从...
Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zipRuby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zip Ruby_on_Rails_rails.zipRuby_on_...
这里的是FastCGI的源代码包,它允许Ruby on Rails应用通过FastCGI协议与Lighttpd交互。 5. **ruby-zlib-0.6.0.tar.gz**: Zlib是用于数据压缩的库,Ruby的内置标准库中包含了对Zlib的支持。此包可能用于Ruby与Gzip等...
接下来,Rails 2.0.2是Ruby on Rails的第二个主要版本,它引入了许多改进和新特性。这个版本的Rails强调了“Convention over Configuration”(约定优于配置)的理念,使得开发过程更加高效。安装Rails需要先确保...
《Ruby on Rails Tutorial》中文版(原书第2版,涵盖 Rails 4) Ruby 是一门很美的计算机语言,其设计原则就是“让编程人员快乐”。David Heinemeier Hansson 就是看重了这一点,才在开发 Rails 框架时选择了 Ruby...
Ruby on Rails,简称ROR或Rails,是一款基于Ruby语言的开源Web应用框架,它遵循Model-View-Controller(MVC)架构模式,旨在提高开发效率和代码可读性。本教程“Ruby on Rails 教程 - 201406”可能是针对2014年6月时...
### Ruby on Rails 101:深入理解与实践 #### 引言 《Ruby on Rails 101》是一本介绍Ruby on Rails(简称RoR或ROR)的基础书籍,旨在为初学者提供一个全面而深入的学习框架。本书由Peter Marklund编写,包含了五天...
《初识Ruby on Rails:源代码解析》 Ruby on Rails(简称Rails)是一个基于Ruby语言的开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在简化Web开发过程,提高开发效率。本资料包包含了...
《Ruby-on-Rails-3.rar》是一个在Pudn网站上分享的压缩文件,主要针对的是希望通过Ruby语言进行Web开发的初级程序员。该资源的核心内容是《Web开发敏捷之道 - 应用Rails进行敏捷Web开发 - 第三版》这本书的PDF版本。...
Ruby on Rails,简称Rails,是基于Ruby编程语言的一个开源Web应用程序框架,它遵循MVC(模型-视图-控制器)架构模式,旨在提高开发效率和代码的可读性。Rails以其“约定优于配置”(Convention over Configuration)...