在《Routing的載入》中,我大致介紹了一下Rails中最簡單的route是如何載入的。這篇文章,我將來講一講Rails系統中更為複雜的named route和與RESTful相關的resource是如何被載入的。為了不重複太多的筆墨,這篇文章將在前文的基礎上進行,如果發現單獨看此文時,有少許雲裡霧裡,建議先看一看我的前篇文章:Ruby On Rails-2.0.2原始碼分析(2)-Routing的載入
首先,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代碼
- ActionController::Routing::Routes.draw do |map|
- map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
- ...
- 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代碼
- def method_missing(route_name, *args, &proc) #:nodoc:
- super unless args.length >= 1 && proc.nil?
- @set.add_named_route(route_name, *args)
- 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代碼
- 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
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代碼
- def add(name, route)
- routes[name.to_sym] = route
- define_named_route_methods(name, route)
- end
-
- def get(name)
- routes[name.to_sym]
- end
-
- alias []= add
- 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代碼
- 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
- end
- 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代碼
- 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
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