Rails源码研究之ActionController:八,resources

深入了解一下ActionController的Resources--RESTful Rails

1,ActionController的resources用来实现REST api,一个单独的resource基于HTTP verb(method)有不同的行为(action),如:
[code]
map.resources :messages

class MessagesController < ActionController::Base
# GET messages_url
def index
# return all messages
end

# GET new_message_url
def new
# return an HTML form for describing a new message
end

# POST message_url
def create
# create a new message
end

# GET message_url(:id => 1)
def show
# find and return a specific message
end

# GET edit_message_url(:id => 1)
def edit
# return an HTML form for editing a specific message
end

# PUT message_url(:id => 1)
def update
# find and update a specific message
end

# DELETE message_url(:id => 1)
def destroy
# delete a specific message
end
end
[/code]

2,对于[b]map.resources :messages[/b]将生成如下named routes和helpers:
[code]
Named Routes Helpers

messages messages_url, hash_for_messages_url,
messages_path, hash_for_messages_path

message message_url(id), hash_for_message_url(id),
message_path(id), hash_for_message_path(id)

new_message new_message_url, hash_for_new_message_url,
new_message_path, hash_for_new_message_path

edit_message edit_message_url(id), hash_for_edit_message_url(id),
edit_message_path(id), hash_for_edit_message_path(id)
[/code]

3,由于浏览器不支持PUT和DELETE,我们需要添加:method参数,如:
[code]
<% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
[/code]

4,resources方法有一些参数:
[color=red]:controller[/color] -- specify the controller name for the routes.
[color=red]:singular[/color] -- specify the singular name used in the member routes.
[color=red]:path_prefix[/color] -- set a prefix to the routes with required route variables.
对于如下routes:
[code]
map.resources :articles
map.resources :comments, :path_prefix => '/articles/:article_id'
[/code]
我们可以使用嵌套写法:
[code]
map.resources :articles do |article|
article.resources :comments
end
[/code]
使用的时候多加一个:article_id参数即可:
[code]
comment_url(@article, @comment)
comment_url(:article_id => @article, :id => @comment)
[/code]
[color=red]:name_prefix[/color] -- define a prefix for all generated routes, usually ending in an underscore.
使用前缀来避免named routes名字冲突:
[code]
map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
[/code]
[color=red]:collection[/color] -- add named routes for other actions that operate on the collection.
比如对于如下route:
[code]
map.resources :messages, :collection => { :rss => :get }
[/code]
生成的named route为[b]rss_messages[/b],生成的helper方法为[b]rss_messages_path[/b],url则为[b]/messages;rss[/b]
该参数形式为#{action} => #{method}的hash,method为:get/:post/:put/:delete/:any,如果method无所谓则可以使用:any
[color=red]:member[/color] -- same as :collection, but for actions that operate on a specific member.
即:collection参数为对多个对象操作的方法,:member参数则为对单个对象操作的方法
[color=red]:new[/color] -- same as :collection, but for actions that operate on the new resource action.

5,map.resource的参数以及用法与map.resources差不多,只是map.resource为一个单独的resource生成named routes

源码全在resources.rb文件里,Rails经常这样弄的一个文件几百行甚至上千行代码,可读性很不好,不过倒也不错,不用到处找关联的文件
[code]
module ActionController
module Resources
class Resource
attr_reader :collection_methods, :member_methods, :new_methods
attr_reader :path_prefix, :name_prefix
attr_reader :plural, :singular
attr_reader :options

def initialize(entities, options)
@plural = entities
@singular = options[:singular] || plural.to_s.singularize
@options = options
arrange_actions
add_default_actions
set_prefixes
end

def controller
@controller ||= (options[:controller] || plural).to_s
end

def path
@path ||= "#{path_prefix}/#{plural}"
end

def new_path
@new_path ||= "#{path}/new"
end

def member_path
@member_path ||= "#{path}/:id"
end

def nesting_path_prefix
@nesting_path_prefix ||= "#{path}/:#{singular}_id"
end
end

class SingletonResource < Resource
alias_method :member_path, :path
alias_method :nesting_path_prefix, :path
end

def resources(*entities, &block)
options = entities.last.is_a?(Hash) ? entities.pop : { }
entities.each { |entity| map_resource entity, options.dup, &block }
end

def resource(*entities, &block)
options = entities.last.is_a?(Hash) ? entities.pop : { }
entities.each { |entity| map_singleton_resource entity, options.dup, &block }
end

private

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)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, &block)
end
end
end

def map_singleton_resource(entities, options = {}, &block)
resource = SingletonResource.new(entities, options)
with_options :controller => resource.controller do |map|
map_collection_actions(map, resource)
map_default_singleton_actions(map, resource)
map_new_actions(map, resource)
map_member_actions(map, resource)
if block_given?
with_options(:path_prefix => resource.nesting_path_prefix, &block)
end
end
end

def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
map.named_route("#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path};#{action}", action_options)
map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.plural}", "#{resource.path}.:format;#{action}", action_options)
end
end
end

def map_default_collection_actions(map, resource)
index_action_options = action_options_for("index", resource)
map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, index_action_options)
map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", index_action_options)
create_action_options = action_options_for("create", resource)
map.connect(resource.path, create_action_options)
map.connect("#{resource.path}.:format", create_action_options)
end

def map_default_singleton_actions(map, resource)
create_action_options = action_options_for("create", resource)
map.connect(resource.path, create_action_options)
map.connect("#{resource.path}.:format", create_action_options)
end

def map_new_actions(map, resource)
resource.new_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
if action == :new
map.named_route("#{resource.name_prefix}new_#{resource.singular}", resource.new_path, action_options)
map.named_route("formatted_#{resource.name_prefix}new_#{resource.singular}", "#{resource.new_path}.:format", action_options)
else
map.named_route("#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path};#{action}", action_options)
map.named_route("formatted_#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path}.:format;#{action}", action_options)
end
end
end
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("#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path};#{action}", action_options)
map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path}.:format;#{action}",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 conditions_for(method)
{ :conditions => method == :any ? {} : { :method => method } }
end

def action_options_for(action, resource, method = nil)
default_options = { :action => action.to_s }
require_id = resource.kind_of?(SingletonResource) ? {} : { :requirements => { :id => Regexp.new("[^#{Routing::SEPARATORS.join}]+") } }
case default_options[:action]
when "index", "new" : default_options.merge(conditions_for(method || :get))
when "create" : default_options.merge(conditions_for(method || :post))
when "show", "edit" : default_options.merge(conditions_for(method || :get)).merge(require_id)
when "update" : default_options.merge(conditions_for(method || :put)).merge(require_id)
when "destroy" : default_options.merge(conditions_for(method || :delete)).merge(require_id)
else default_options.merge(conditions_for(method))
end
end

end
end

ActionController::Routing::RouteSet::Mapper.send :include, ActionController::Resources
[/code]

Resources模块定义了Resource类和SingletonResource类,前者表示多个资源,后者则表示单个资源
resources方法和resource方法分别调用map_resource方法和map_singleton_resource方法
map_resource和map_singleton_resource方法分别用Resource和SingletonResource类初始化对象
最后分别调用map.named_route和map.connect来生成各自的routes

注意resources和resource方法对生成的routes作了HTTP method上的限制,如果我们在request时提供的method参数有误则会抛出RoutingError异常

这样看来,resources只不过是为我们生成了一些named routes而已,简化了我们的工作,提高了效率

我们在看源码时带着问题去看,就会非常有收获
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值