Ruby On Rails-2.0.2源代码分析(3)-named rout…

2008-04-02 02:29:23来源: 阅读 ()

新老客户大回馈,云服务器低至5折

  • 前言

  在《Routing的载入》中,我大致介绍了一下Rails中最简单的route是如何加载的。这篇文章,我将来讲一讲Rails系统中更为复杂的named route和与RESTful相关的resource是如何被加载的。为了不重复太多的笔墨,这篇文章将在前文的基础上进行,如果发现单独看此文时,有少许云里雾里,建议先看一看我的前篇文章:Ruby On Rails-2.0.2源代码分析(2)-Routing的载入

  • 进化的routing-named route

  首先,named route的载入全部发生在routing.rb中。其实named route一点也不比普通的route高深些什么,Rails内部最终也是将named route解析为一个普通的route保存在RouseSet类的routes数组中(还记得这家伙么?最好牢牢记住他,因为,他还会在后续文章中继续登台发挥重要作用),之所以我称他进化,是因为named route既然提供了name,在Rails内部,将会生成一系列的helper方法,当我们在controller或者view中使用link_to,redirect_to等方法时,不需要指定相应的controller和action,从而简化我们的代码,不用多了,先来看一看我们所熟悉的routes.rb

Ruby代码 复制代码
  1. ActionController::Routing::Routes.draw do |map|   
  2.   map.purchase 'products/:id/purchase':controller => 'catalog':action => 'purchase'  
  3.   ...   
  4. end  


  这里,我定义了一个purchase的named route(当然,你完全可以使用connect方法定义普通的route)。前一篇文章提到过,block中的map对象是Mapper类的实例,其实你可以想象到,其实Mapper类并没有定义purchase方法(天知道你要给你的named route起啥名?翠花?旺财?)。所有的一切,都是通过Mapper类的method_missing方法处理的。具体代码如下:

Ruby代码 复制代码
  1. def method_missing(route_name, *args, &proc) #:nodoc:   
  2.   super unless args.length >= 1 && proc.nil?   
  3.   @set.add_named_route(route_name, *args)   
  4. end  


  如果你记性还好,应该还记得@set对象是一个RouteSet类的实例,所以这里Mapper类将这个route的名称,还有所有的参数都传递到了RouteSet类的add_named_route方法。

Ruby代码 复制代码
  1. def add_named_route(name, path, options = {})   
  2.   # TODO - is options EVER used?   
  3.   name = options[:name_prefix] + name.to_s if options[:name_prefix]   
  4.   named_routes[name.to_sym] = add_route(path, options)   
  5. end  


  这里,看到我们前面已经熟悉过了的add_route方法了吧?对此方法不用再过多解释,Rails将生成一个普通的route,保存在RouteSet的routes数组中,并将这个route返回,赋给named_routes对象,此对象是NamedRouteCollection类的一个实例。在NamedRouteCollection中有如下定义:


Ruby代码 复制代码
  1. def add(name, route)   
  2.   routes[name.to_sym] = route   
  3.   define_named_route_methods(name, route)   
  4. end  
  5.   
  6. def get(name)   
  7.   routes[name.to_sym]   
  8. end  
  9.   
  10. alias []=   add   
  11. alias []    get  


  所以,接下来似乎应该关心下add方法了。这里,首先将此普通的route保存在NamedRouteCollection类的routes哈希中(注意和RouteSet的routes数组区分开来)。然后,named route开始其“进化”了----通过define_named_route_methods方法生成自己的一系列helper方法。

Ruby代码 复制代码
  1. def define_named_route_methods(name, route)   
  2.   {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|   
  3.     hash = route.defaults.merge(:use_route => name).merge(opts)   
  4.     define_hash_access route, name, kind, hash   
  5.     define_url_helper route, name, kind, hash   
  6.   end  
  7. end  


  或者你已经知道了,named route有name_url,和name_path两类helper方法,从上面这段代码中,我们能看到真正的实现。Rails这里为url和path分别生成hash access和url helper方法,其实现都利用了ruby强大的动态特性。具体实现分别如下:


Ruby代码 复制代码
  1. def define_hash_access(route, name, kind, options)   
  2.      selector = hash_access_name(name, kind)   
  3.      @module.module_eval <<-end_eval # We use module_eval to avoid leaks   
  4.        def #{selector}(options = nil)   
  5.          options ? #{options.inspect}.merge(options) : #{options.inspect}   
  6.        end  
  7.        protected :#{selector}   
  8.      end_eval   
  9.      helpers << selector   
  10.    end  
  11.   
  12.    def define_url_helper(route, name, kind, options)   
  13.      selector = url_helper_name(name, kind)   
  14.      # The segment keys used for positional paramters   
  15.   
  16.      hash_access_method = hash_access_name(name, kind)   
  17.      @module.module_eval <<-end_eval # We use module_eval to avoid leaks   
  18.        def #{selector}(*args)   
  19.          #{generate_optimisation_block(route, kind)}   
  20.   
  21.          opts = if args.empty? || Hash === args.first   
  22.            args.first || {}   
  23.          else  
  24.            options = args.last.is_a?(Hash) ? args.pop : {}   
  25.            args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|   
  26.              h[k] = v   
  27.              h   
  28.            end  
  29.            options.merge(args)   
  30.          end  
  31.   
  32.          url_for(#{hash_access_method}(opts))   
  33.        end  
  34.        protected :#{selector}   
  35.      end_eval   
  36.      helpers << selector   
  37.    end  


  其中@module是NamedRouteCollection类中的一个匿名module,他通过module_eval方法动态的增加了这一系列的helper方法,并且将方法名保存在helpers数组当中,以供其后controller或者view的使用(link_to, redirect_to)。至此,named route的加载就全部完成了。十分简单,不是吗?

  • RESTful的化身----resource

  当然,光把RESTful和resource扯到一起似乎相当狭义,在Rails中,ActionController::Resources抽象了REST中的Resource,这里,我不谈REST的相关概念,网上资料一大坨。我们就来看看Rails中是如何通过Resource来轻松,简便的完成RESTful应用的吧。

  resources.rb

  源代码路径:/actionpack-2.0.2/lib/action_controller/resources.rb
  首先,我们也不需要将resource看得多么的高深,你可以把他理解为,当你在routes.rb中定义如下的resource的时候:

map.resources :products
  Rails会自动为我们生成众多的named route,这些route通过http verb和相应的controller中的action对应起来,当然了,众多的helper方法也随即产生。如下表所示:

Named Route Helpers
product
product_url, hash_for_product_url,
product_path, hash_for_product_path
new_product
new_product_url, hash_for_new_product_url,
new_product_path, hash_for_new_product_path
edit_product
edit_product_url, hash_for_edit_product_url,
edit_product_path, hash_for_edit_product_path
... ...
  从这个角度来想,你可以把resource想成是众多相关named route的一个马甲。我们先从宏观的角度来看一看Rails是如何完成Resource到route转换的。
 
  整个流程比较的直观,Rails通过resource按部就班的完成各种route的生成,接下来我们看一看核心代码是如何完成这些功能的。首先,还是在routes.rb中,可能会定义如下的resource:
Ruby代码 复制代码

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Ruby On Rails-2.0.2源代码分析(4)-寻找Controller

下一篇:插件rquerypad, 简化查询参数 增强关联查询能力