`
missall
  • 浏览: 127635 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

rails2.0.2源码分析-routing载入

阅读更多
  •   前言

  在前一篇文章中,我大致的讲解了一下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载入的每一个细节,但是希望本文能起到一块转头的作用----当然不是拍人用----而是可以引来很多玉)

 

原文:http://www.iteye.com/topic/172796

分享到:
评论
1 楼 yangzhihuan 2008-07-05  
写得非常好,对于理解rails的内部机制很有帮助。
我想其实有很多rails的使用者都不太明白rails的内部机制的,虽然不了解也照样可以把rails用得很好。
对楼主的后续文章很期待。

相关推荐

    rails 2.0.2 分页 需另外下载插件

    在Ruby on Rails框架中,`Rails 2.0.2`是一个较早的版本,而分页功能在那个时期并不像现在的Rails应用那样内置在框架内。为了实现分页,开发者通常需要安装并使用第三方插件,比如"will_paginate"。这个插件允许你在...

    rails-chm-2-0-2.rar

    `rails-documentation-2-0-2.chm` 文件详细涵盖了这些概念,包含了关于Rails 2.0.2的API参考、教程和指南。通过仔细阅读和实践,开发者能够深入理解Rails的工作原理,并有效地开发出高效、可维护的Web应用。

    ruby1.8.6 + rails2.0.2 安装配置 详细说明

    在本文中,我们将深入探讨如何在您的计算机上安装和配置Ruby 1.8.6、Rails 2.0.2、RadRails 0.7.2 IDE以及MySQL数据库。这是一个适用于初学者和有一定经验的开发者的技术指南,旨在帮助您创建一个稳定的开发环境,...

    Ruby on Rails安装指南(Ruby 1.8.6+Rails 2.0.2)

    在命令行工具中执行以下命令来安装Rails 2.0.2: ```bash gem install rails -v 2.0.2 ``` 该命令会从Ruby的包管理库中查找并安装指定版本的Rails。安装完成后,同样通过`rails -v`来检查是否安装成功。 **知识点...

    rails-hackernews-reddit-producthunt-clone, 黑客 news/reddit/social 链接分享网站 用 Rails 构建.zip

    rails-hackernews-reddit-producthunt-clone, 黑客 news/reddit/social 链接分享网站 用 Rails 构建 Rails 上的 Reddit-Hackernews-ProductHunt克隆演示 这是一个 readme.md的Ruby on Rails 应用程序,模仿了 Hacker...

    rails3-mongoid-devise, 示例 Rails 3.2应用,带有数据 Mongoid,用于验证.zip

    rails3-mongoid-devise, 示例 Rails 3.2应用,带有数据 Mongoid,用于验证 Rails 4.1有关设计的Rails 4.1示例应用程序,请参见:rails设计有一个用于设计的教程:Rails 设计教程。类似示例和教程这是来自 RailsApps...

    Agile Web Development with Rails-Second Edition-Beta一书例子

    《Agile Web Development with Rails-Second Edition-Beta》是一本专注于使用Ruby on Rails进行敏捷Web开发的书籍。这本书的第二版beta版提供了关于如何利用Rails框架高效构建动态、响应式网站的深入指导。作者们...

    rails-react-components-源码.rar

    本文将深入探讨"rails-react-components-源码.rar"中的关键知识点,帮助开发者理解如何在Rails应用中集成React组件。 1. **React组件化开发** React的核心概念是组件,它允许我们将UI拆分为独立、可重用的部分。在...

    awesome-rails-gem-zh_CN, Rails 常用 Gem 列表 - Awesome Rails Gem 中文版.zip

    在这个压缩包中,`awesome-rails-gem-zh_CN-master`可能是项目源码或文档的主目录。以下是一些可能包含在列表中的关键Rails Gem及其功能简介: 1. **Devise**:这是一个灵活的身份认证解决方案,提供了一套完整的...

    rails-exporter-源码.rar

    《Rails Exporter 源码解析》 Rails Exporter 是一个用于 Rails 应用程序的开源工具,主要用于数据导出功能。源码分析将帮助我们深入理解其内部工作原理,以便更好地利用它来优化我们的应用。 一、Rails 框架基础 ...

    rails-documentation-2-0-2

    rails-documentation-2-0-2

    rails-yelp-mvp-源码.rar

    【标题】"rails-yelp-mvp-源码" 指的是一个基于Rails框架开发的类似于Yelp(美国知名餐饮评论网站)的最小可行产品...通过分析源码,你可以学习到Rails开发的实践技巧和设计模式,对于提升你的Web开发技能非常有帮助。

    rails-builds-test-源码.rar

    在"rails-builds-test-源码.rar"这个压缩包中,我们很显然会接触到一个使用Rails框架构建的测试项目。接下来,我们将深入探讨Rails的几个关键知识点,以及如何通过源码来理解其工作原理。 1. **Gemfile与Gemfile....

    rails-beginner-s-guide

    rails-beginner-s-guide是Rails 指导手册,帮组学习了解rails开发

    基于java的开发源码-Rails3消息队列系统 Sidekiq.zip

    基于java的开发源码-Rails3消息队列系统 Sidekiq.zip 基于java的开发源码-Rails3消息队列系统 Sidekiq.zip 基于java的开发源码-Rails3消息队列系统 Sidekiq.zip 基于java的开发源码-Rails3消息队列系统 Sidekiq.zip ...

    rails-documentation-1-2-0-rc1.chm

    rails-documentation-1-2-0-rc1.chm

    RVM+Ruby1.9.3+Rails3(1-Cygwin 安装配置)

    ### RVM + Ruby 1.9.3 + Rails 3 安装与配置指南 #### 一、前言 在 Windows 7 环境下搭建 Rails 3 开发环境是一项颇具挑战性的任务,尤其是当涉及到 Cygwin、Ruby、Rails 以及一系列其他必要的组件时。本文将详细...

Global site tag (gtag.js) - Google Analytics