Ruby On Rails-2.0.2原始碼分析(3)-named route

來源:互聯網
上載者:User
  • 前言

  在《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  
ActionController::Routing::Routes.draw do |map|  map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'  ...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  
def method_missing(route_name, *args, &proc) #:nodoc:  super unless args.length >= 1 && proc.nil?  @set.add_named_route(route_name, *args)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  
def add_named_route(name, path, options = {})  # TODO - is options EVER used?  name = options[:name_prefix] + name.to_s if options[:name_prefix]  named_routes[name.to_sym] = add_route(path, options)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  
def add(name, route)  routes[name.to_sym] = route  define_named_route_methods(name, route)enddef get(name)  routes[name.to_sym]endalias []=   addalias []    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  
def define_named_route_methods(name, route)  {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|    hash = route.defaults.merge(:use_route => name).merge(opts)    define_hash_access route, name, kind, hash    define_url_helper route, name, kind, hash  endend

  或者你已經知道了,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  
 def define_hash_access(route, name, kind, options)      selector = hash_access_name(name, kind)      @module.module_eval <<-end_eval # We use module_eval to avoid leaks        def #{selector}(options = nil)          options ? #{options.inspect}.merge(options) : #{options.inspect}        end        protected :#{selector}      end_eval      helpers << selector    end    def define_url_helper(route, name, kind, options)      selector = url_helper_name(name, kind)      # The segment keys used for positional paramters      hash_access_method = hash_access_name(name, kind)      @module.module_eval <<-end_eval # We use module_eval to avoid leaks        def #{selector}(*args)          #{generate_optimisation_block(route, kind)}          opts = if args.empty? || Hash === args.first            args.first || {}          else            options = args.last.is_a?(Hash) ? args.pop : {}            args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|              h[k] = v              h            end            options.merge(args)          end          url_for(#{hash_access_method}(opts))        end        protected :#{selector}      end_eval      helpers << selector    end

  其中@module是NamedRouteCollection類中的一個匿名module,他通過module_eval方法動態增加了這一系列的helper方法,並且將方法名儲存在helpers數組當中,以供其後controller或者view的使用(link_to, redirect_to)。至此,named route的載入就全部完成了。十分簡單,不是嗎?

 

本文轉自:http://woody-420420.javaeye.com/blog/174352

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.