Added map.resources from the Simply Restful plugin (backwards incompatible with the plugin!) [DHH]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4637 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
David Heinemeier Hansson
2006-07-31 18:59:58 +00:00
parent ffaecb792e
commit 865b175765
6 changed files with 290 additions and 9 deletions

View File

@@ -1,5 +1,11 @@
*SVN*
* Added map.resources from the Simply Restful plugin [DHH]. Examples (the API has changed to use plurals!):
map.resources :messages
map.resources :messages, :comments
map.resources :messages, :new => { :preview => :post }
* Fixed that integration simulation of XHRs should set Accept header as well [Edward Frederick]
* TestRequest#reset_session should restore a TestSession, not a hash [Koz]

View File

@@ -2,6 +2,7 @@ require 'action_controller/mime_type'
require 'action_controller/request'
require 'action_controller/response'
require 'action_controller/routing'
require 'action_controller/resources'
require 'action_controller/url_rewriter'
require 'drb'
require 'set'

View File

@@ -15,7 +15,9 @@ module ActionController
# Returns the HTTP request method as a lowercase symbol (:get, for example)
def method
@request_method ||= @env['REQUEST_METHOD'].downcase.to_sym
@request_method ||= (method = parameters[:_method] && method == :post) ?
method.to_s.downcase.to_sym :
@env['REQUEST_METHOD'].downcase.to_sym
end
# Is this a GET request? Equivalent to request.method == :get

View File

@@ -0,0 +1,170 @@
module ActionController
module Resources
class Resource #:nodoc:
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
(options[:controller] || plural).to_s
end
def path
"#{path_prefix}/#{plural}"
end
def new_path
"#{path}/new"
end
def member_path
"#{path}/:id"
end
def nesting_path_prefix
"#{path_prefix}/#{plural}/:#{singular}_id"
end
private
def arrange_actions
@collection_methods = arrange_actions_by_methods(options.delete(:collection))
@member_methods = arrange_actions_by_methods(options.delete(:member))
@new_methods = arrange_actions_by_methods(options.delete(:new))
end
def add_default_actions
add_default_action(collection_methods, :post, :create)
add_default_action(member_methods, :get, :edit)
add_default_action(member_methods, :put, :update)
add_default_action(member_methods, :delete, :destroy)
add_default_action(new_methods, :get, :new)
end
def set_prefixes
@path_prefix = options.delete(:path_prefix)
@name_prefix = options.delete(:name_prefix)
end
def arrange_actions_by_methods(actions)
arrayize_values(flip_keys_and_values(actions || {}))
end
def add_default_action(collection, method, action)
(collection[method] ||= []).unshift(action)
end
def flip_keys_and_values(hash)
hash.inject({}) do |flipped_hash, (key, value)|
flipped_hash[value] = key
flipped_hash
end
end
def arrayize_values(hash)
hash.each do |(key, value)|
unless value.is_a?(Array)
hash[key] = []
hash[key] << value
end
end
end
end
def resources(*entities)
options = entities.last.is_a?(Hash) ? entities.pop : { }
entities.each { |entity| map_resource(entity, options) { yield if block_given? } }
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_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|
primary = actions.shift.to_s if method != :get
route_options = requirements_for(method)
actions.each do |action|
map.named_route(
"#{resource.name_prefix}#{action}_#{resource.plural}",
"#{resource.path};#{action}",
route_options.merge(:action => action.to_s)
)
map.named_route(
"formatted_#{resource.name_prefix}#{action}_#{resource.plural}",
"#{resource.path}.:format;#{action}",
route_options.merge(:action => action.to_s)
)
end
unless primary.blank?
map.connect(resource.path, route_options.merge(:action => primary))
map.connect("#{resource.path}.:format", route_options.merge(:action => primary))
end
map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, :action => "index", :conditions => { :method => :get })
map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", :action => "index", :conditions => { :method => :get })
end
end
def map_new_actions(map, resource)
resource.new_methods.each do |method, actions|
route_options = requirements_for(method)
actions.each do |action|
path = action == :new ? resource.new_path : "#{resource.new_path};#{action}"
name = "new_#{resource.plural}"
name = "#{action}_#{name}" unless action == :new
map.named_route("#{resource.name_prefix}#{name}", path, route_options.merge(:action => action.to_s))
map.named_route("formatted_#{resource.name_prefix}#{name}", action == :new ? "#{resource.new_path}.:format" : "#{resource.new_path}.:format;#{action}", route_options.merge(:action => action.to_s))
end
end
end
def map_member_actions(map, resource)
resource.member_methods.each do |method, actions|
route_options = requirements_for(method)
primary = actions.shift.to_s unless [ :get, :post, :any ].include?(method)
actions.each do |action|
map.named_route("#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path};#{action}", route_options.merge(:action => action.to_s))
map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path}.:format;#{action}", route_options.merge(:action => action.to_s))
end
map.connect(resource.member_path, route_options.merge(:action => primary)) unless primary.blank?
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, :action => "show", :conditions => { :method => :get })
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", :action => "show", :conditions => { :method => :get })
end
end
def requirements_for(method)
method == :any ? {} : { :conditions => { :method => method } }
end
end
end
ActionController::Routing::RouteSet::Mapper.send :include, ActionController::Resources

View File

@@ -58,7 +58,7 @@ module ActionController
use_controllers! nil
end
def normalize_paths(paths=$LOAD_PATH)
def normalize_paths(paths = $LOAD_PATH)
# do the hokey-pokey of path normalization...
paths = paths.collect do |path|
path = path.
@@ -351,7 +351,6 @@ module ActionController
end
protected
def requirement_for(key)
return requirements[key] if requirements.key? key
segments.each do |segment|
@@ -415,7 +414,6 @@ module ActionController
def optionality_implied?
false
end
end
class StaticSegment < Segment
@@ -450,11 +448,9 @@ module ActionController
def to_s
value
end
end
class DividerSegment < StaticSegment
def initialize(value = nil)
super(value)
self.raw = true
@@ -464,7 +460,6 @@ module ActionController
def optionality_implied?
true
end
end
class DynamicSegment < Segment
@@ -760,7 +755,6 @@ module ActionController
end
class RouteSet
# Mapper instances are used to build routes. The object passed to the draw
# block in config/routes.rb is a Mapper instance.
#
@@ -839,7 +833,6 @@ module ActionController
end
private
def url_helper_name(name, kind = :url)
:"#{name}_#{kind}"
end

View File

@@ -0,0 +1,109 @@
require File.dirname(__FILE__) + '/../abstract_unit'
class MessagesController < ActionController::Base
def rescue_action(e) raise e end
end
class CommentsController < ActionController::Base
def rescue_action(e) raise e end
end
class ResourcesTest < Test::Unit::TestCase
def test_default_restful_routes
with_restful_routing :messages do
assert_restful_routes_for :messages do
routing_options = {:controller => '/messages'}
end
end
end
def test_with_path_prefix
with_restful_routing :messages, :path_prefix => '/thread/:thread_id' do
assert_restful_routes_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
end
end
def test_with_collection_action
with_restful_routing :messages, :collection => { :rss => :get } do
assert_restful_routes_for :messages do |options|
assert_routing "/messages;rss", options.merge(:action => 'rss')
end
end
end
def test_with_member_action
[:put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method } do
assert_restful_routes_for :messages do |options|
assert_recognizes(
options.merge(:action => 'mark', :id => '1'),
{:path => "/messages/1;mark", :method => method})
end
end
end
end
def test_with_new_action
with_restful_routing :messages, :new => { :preview => :post } do
assert_restful_routes_for :messages do |options|
assert_recognizes(
options.merge(:action => 'preview'),
{:path => "/messages/new;preview", :method => :post})
end
end
end
def xtest_nested_restful_routes
with_routing do |set|
set.draw do |map|
map.resources(:messages) do
map.resources(:comments)
end
end
with_options({ :controller => :comments }) do |controller|
controller.assert_routing "/messages/1/comments", :action => 'index'
controller.assert_routing "/messages/1/comments.xml" , :action => 'index', :format => 'xml'
controller.assert_routing "/messages/1/comments/new", :action => 'new'
controller.assert_routing "/messages/1/comments/1", :action => 'show', :id => '1'
controller.assert_routing "/messages/1/comments/1;edit", :action => 'edit', :id => '1'
controller.assert_routing "/messages/1/comments/1.xml", :action => 'show', :id => '1', :format => 'xml'
end
end
end
protected
def with_restful_routing(resource, *args)
with_routing do |set|
set.draw { |map| map.resources(resource, *args) }
yield
end
end
def assert_restful_routes_for(controller_name, options = {})
(options[:options] ||= {})[:controller] = controller_name.to_s
with_options(options[:options]) do |controller|
controller.assert_routing "/#{options[:path_prefix]}#{controller_name}", :action => 'index'
controller.assert_routing "/#{options[:path_prefix]}#{controller_name}.xml" , :action => 'index', :format => 'xml'
controller.assert_routing "/#{options[:path_prefix]}#{controller_name}/new", :action => 'new'
controller.assert_routing "/#{options[:path_prefix]}#{controller_name}/1", :action => 'show', :id => '1'
controller.assert_routing "/#{options[:path_prefix]}#{controller_name}/1;edit", :action => 'edit', :id => '1'
controller.assert_routing "/#{options[:path_prefix]}#{controller_name}/1.xml", :action => 'show', :id => '1', :format => 'xml'
end
assert_recognizes(
options[:options].merge(:action => 'create'),
{:path => "/#{options[:path_prefix]}#{controller_name}", :method => :post})
assert_recognizes(
options[:options].merge(:action => 'update', :id => '1'),
{:path => "/#{options[:path_prefix]}#{controller_name}/1", :method => :put})
assert_recognizes(
options[:options].merge(:action => 'destroy', :id => '1'),
{:path => "/#{options[:path_prefix]}#{controller_name}/1", :method => :delete})
yield options[:options] if block_given?
end
end