當然,光把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的產生,接下來我們看一看核心代碼是如何完成這些功能的。首先,還是在routes.rb中,可能會定義如下的resource:
Ruby代碼
- ActionController::Routing::Routes.draw do |map|
-
- map.resources :products
- ...
- end
ActionController::Routing::Routes.draw do |map| map.resources :products ...end
resources方法定義在ActionController::Resources這個module中,然後通過mixin進入到Mapper類的。那我們首先來看一看這個方法:
Ruby代碼
- def resources(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_resource(entity, options.dup, &block) }
- end
def resources(*entities, &block) options = entities.extract_options! entities.each { |entity| map_resource(entity, options.dup, &block) }end
很簡單,將entities和options從參數中分離開來,然後針對每一個entity執行map_resource操作。我們繼續進行,看看map_resource方法的真面目:
Ruby代碼
- def map_resource(entities, options = {}, &block)
- resource = Resource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_collection_actions(map, resource)
- map_default_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
-
- map_associations(resource, options)
-
- if block_given?
- with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block)
- end
- end
- end
def map_resource(entities, options = {}, &block) resource = Resource.new(entities, options) with_options :controller => resource.controller do |map| map_collection_actions(map, resource) map_default_collection_actions(map, resource) map_new_actions(map, resource) map_member_actions(map, resource) map_associations(resource, options) if block_given? with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], &block) end endend
有了entity和options,還等什麼呢?馬上產生我們的Resource對象,Resource對象封裝了和此resource相關的collection method,member method,new method,path prefix,name prefix,單/複數表示,還有option。產生這個Resource對象無非就是將此對象的相應屬性從options中解析出來,儲存起來,代碼比較簡單,這裡就不再貼出。
現在,Resource對象有了,從上面代碼我們就可以看出來,接下來,就該處理和此resource相關named route了。具體的處理邏輯都類似,這裡將map_member_actions(map, resource)拿出來作為示意,感興趣的同學們可以自己查看相關的原始碼。
Ruby代碼
- def map_member_actions(map, resource)
- resource.member_methods.each do |method, actions|
- actions.each do |action|
- action_options = action_options_for(action, resource, method)
- map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options)
- map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format",action_options)
- end
- end
-
- show_action_options = action_options_for("show", resource)
- map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
- map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
-
- update_action_options = action_options_for("update", resource)
- map.connect(resource.member_path, update_action_options)
- map.connect("#{resource.member_path}.:format", update_action_options)
-
- destroy_action_options = action_options_for("destroy", resource)
- map.connect(resource.member_path, destroy_action_options)
- map.connect("#{resource.member_path}.:format", destroy_action_options)
- end
def map_member_actions(map, resource) resource.member_methods.each do |method, actions| actions.each do |action| action_options = action_options_for(action, resource, method) map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options) map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format",action_options) end end show_action_options = action_options_for("show", resource) map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options) map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options) update_action_options = action_options_for("update", resource) map.connect(resource.member_path, update_action_options) map.connect("#{resource.member_path}.:format", update_action_options) destroy_action_options = action_options_for("destroy", resource) map.connect(resource.member_path, destroy_action_options) map.connect("#{resource.member_path}.:format", destroy_action_options)end
這裡,我們可以很直觀的看到,Rails為resource的member相關方法產生了眾多的route,我們可以看到Controller中熟悉的show,update,destroy action。是的,在這裡,Rails就為url到controller的action產生了相應的route。
本文轉自:http://woody-420420.javaeye.com/blog/174352