Changeset 6107
- Timestamp:
- 02/04/07 17:19:45 (2 years ago)
- Files:
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/1-2-stable/actionpack/CHANGELOG
r6087 r6107 1 1 *SVN* 2 3 * Add singleton resources from trunk [Rick Olson] 2 4 3 5 * TestSession supports indifferent access so session['foo'] == session[:foo] in your tests. #7372 [julik, jean.helou] branches/1-2-stable/actionpack/lib/action_controller/resources.rb
r6063 r6107 19 19 20 20 def controller 21 (options[:controller] || plural).to_s21 @controller ||= (options[:controller] || plural).to_s 22 22 end 23 23 24 24 def path 25 "#{path_prefix}/#{plural}"25 @path ||= "#{path_prefix}/#{plural}" 26 26 end 27 27 28 28 def new_path 29 "#{path}/new"29 @new_path ||= "#{path}/new" 30 30 end 31 31 32 32 def member_path 33 "#{path}/:id"33 @member_path ||= "#{path}/:id" 34 34 end 35 35 36 36 def nesting_path_prefix 37 "#{path}/:#{singular}_id"38 end 39 40 pr ivate37 @nesting_path_prefix ||= "#{path}/:#{singular}_id" 38 end 39 40 protected 41 41 def arrange_actions 42 42 @collection_methods = arrange_actions_by_methods(options.delete(:collection)) … … 66 66 end 67 67 end 68 68 69 class SingletonResource < Resource #:nodoc: 70 def initialize(entity, options) 71 @plural = @singular = entity 72 @options = options 73 arrange_actions 74 add_default_actions 75 set_prefixes 76 end 77 78 alias_method :member_path, :path 79 alias_method :nesting_path_prefix, :path 80 end 81 69 82 # Creates named routes for implementing verb-oriented controllers. This is 70 83 # useful for implementing REST API's, where a single resource has different … … 218 231 end 219 232 233 # Creates named routes for implementing verb-oriented controllers for a singleton resource. 234 # A singleton resource is global to the current user visiting the application, such as a user's 235 # /account profile. 236 # 237 # See map.resources for general conventions. These are the main differences: 238 # - a singular name is given to map.resource. The default controller name is taken from the singular name. 239 # - To specify a custom plural name, use the :plural option. There is no :singular option 240 # - No default index, new, or create routes are created for the singleton resource controller. 241 # - When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1') 242 # 243 # Example: 244 # 245 # map.resource :account 246 # 247 # class AccountController < ActionController::Base 248 # # POST account_url 249 # def create 250 # # create an account 251 # end 252 # 253 # # GET new_account_url 254 # def new 255 # # return an HTML form for describing the new account 256 # end 257 # 258 # # GET account_url 259 # def show 260 # # find and return the account 261 # end 262 # 263 # # GET edit_account_url 264 # def edit 265 # # return an HTML form for editing the account 266 # end 267 # 268 # # PUT account_url 269 # def update 270 # # find and update the account 271 # end 272 # 273 # # DELETE account_url 274 # def destroy 275 # # delete the account 276 # end 277 # end 278 # 279 # Along with the routes themselves, #resource generates named routes for use in 280 # controllers and views. <tt>map.resource :account</tt> produces the following named routes and helpers: 281 # 282 # Named Route Helpers 283 # account account_url, hash_for_account_url, 284 # account_path, hash_for_account_path 285 # edit_account edit_account_url, hash_for_edit_account_url, 286 # edit_account_path, hash_for_edit_account_path 287 def resource(*entities, &block) 288 options = entities.last.is_a?(Hash) ? entities.pop : { } 289 entities.each { |entity| map_singleton_resource entity, options.dup, &block } 290 end 291 220 292 private 221 293 def map_resource(entities, options = {}, &block) … … 224 296 with_options :controller => resource.controller do |map| 225 297 map_collection_actions(map, resource) 298 map_default_collection_actions(map, resource) 299 map_new_actions(map, resource) 300 map_member_actions(map, resource) 301 302 if block_given? 303 with_options(:path_prefix => resource.nesting_path_prefix, &block) 304 end 305 end 306 end 307 308 def map_singleton_resource(entities, options = {}, &block) 309 resource = SingletonResource.new(entities, options) 310 311 with_options :controller => resource.controller do |map| 312 map_collection_actions(map, resource) 313 map_default_singleton_actions(map, resource) 226 314 map_new_actions(map, resource) 227 315 map_member_actions(map, resource) … … 241 329 end 242 330 end 243 331 end 332 333 def map_default_collection_actions(map, resource) 244 334 index_action_options = action_options_for("index", resource) 245 335 map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, index_action_options) 246 336 map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", index_action_options) 247 337 338 create_action_options = action_options_for("create", resource) 339 map.connect(resource.path, create_action_options) 340 map.connect("#{resource.path}.:format", create_action_options) 341 end 342 343 def map_default_singleton_actions(map, resource) 248 344 create_action_options = action_options_for("create", resource) 249 345 map.connect(resource.path, create_action_options) … … 287 383 map.connect("#{resource.member_path}.:format", destroy_action_options) 288 384 end 289 385 290 386 def conditions_for(method) 291 387 { :conditions => method == :any ? {} : { :method => method } } … … 294 390 def action_options_for(action, resource, method = nil) 295 391 default_options = { :action => action.to_s } 296 require_id = { :requirements => { :id => Regexp.new("[^#{Routing::SEPARATORS.join}]+") } }392 require_id = resource.kind_of?(SingletonResource) ? {} : { :requirements => { :id => Regexp.new("[^#{Routing::SEPARATORS.join}]+") } } 297 393 case default_options[:action] 298 394 when "index", "new" : default_options.merge(conditions_for(method || :get)) branches/1-2-stable/actionpack/test/controller/resources_test.rb
r6063 r6107 3 3 class ResourcesController < ActionController::Base 4 4 def index() render :nothing => true end 5 alias_method :show, :index 5 6 def rescue_action(e) raise e end 6 7 end … … 10 11 class CommentsController < ResourcesController; end 11 12 13 class AccountController < ResourcesController; end 14 class AdminController < ResourcesController; end 12 15 13 16 class ResourcesTest < Test::Unit::TestCase … … 33 36 resource = ActionController::Resources::Resource.new(:messages, :controller => 'posts') 34 37 assert_equal 'posts', resource.controller 38 end 39 40 def test_should_all_singleton_paths_be_the_same 41 [ :path, :nesting_path_prefix, :member_path ].each do |method| 42 resource = ActionController::Resources::SingletonResource.new(:messages, :path_prefix => 'admin') 43 assert_equal 'admin/messages', resource.send(method) 44 end 35 45 end 36 46 … … 188 198 end 189 199 200 def test_should_create_singleton_resource_routes 201 with_singleton_resources :account do 202 assert_singleton_restful_for :account 203 end 204 end 205 206 def test_should_create_multiple_singleton_resource_routes 207 with_singleton_resources :account, :admin do 208 assert_singleton_restful_for :account 209 assert_singleton_restful_for :admin 210 end 211 end 212 213 def test_should_create_nested_singleton_resource_routes 214 with_routing do |set| 215 set.draw do |map| 216 map.resource :admin do |admin| 217 admin.resource :account 218 end 219 end 220 221 assert_singleton_restful_for :admin 222 assert_singleton_restful_for :account, :path_prefix => 'admin/' 223 end 224 end 225 226 def test_singleton_resource_with_member_action 227 [:put, :post].each do |method| 228 with_singleton_resources :account, :member => { :reset => method } do 229 reset_options = {:action => 'reset'} 230 reset_path = "/account;reset" 231 assert_singleton_routes_for :account do |options| 232 assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method) 233 end 234 235 assert_singleton_named_routes_for :account do |options| 236 assert_named_route reset_path, :reset_account_path, reset_options 237 end 238 end 239 end 240 end 241 242 def test_singleton_resource_with_two_member_actions_with_same_method 243 [:put, :post].each do |method| 244 with_singleton_resources :account, :member => { :reset => method, :disable => method } do 245 %w(reset disable).each do |action| 246 action_options = {:action => action} 247 action_path = "/account;#{action}" 248 assert_singleton_routes_for :account do |options| 249 assert_recognizes(options.merge(action_options), :path => action_path, :method => method) 250 end 251 252 assert_singleton_named_routes_for :account do |options| 253 assert_named_route action_path, "#{action}_account_path".to_sym, action_options 254 end 255 end 256 end 257 end 258 end 259 260 def test_should_nest_resources_in_singleton_resource 261 with_routing do |set| 262 set.draw do |map| 263 map.resource :account do |account| 264 account.resources :messages 265 end 266 end 267 268 assert_singleton_restful_for :account 269 assert_simply_restful_for :messages, :path_prefix => 'account/' 270 end 271 end 272 273 def test_should_nest_resources_in_singleton_resource_with_path_prefix 274 with_routing do |set| 275 set.draw do |map| 276 map.resource(:account, :path_prefix => ':site_id') do |account| 277 account.resources :messages 278 end 279 end 280 281 assert_singleton_restful_for :account, :path_prefix => '7/', :options => { :site_id => '7' } 282 assert_simply_restful_for :messages, :path_prefix => '7/account/', :options => { :site_id => '7' } 283 end 284 end 285 286 def test_should_nest_singleton_resource_in_resources 287 with_routing do |set| 288 set.draw do |map| 289 map.resources :threads do |thread| 290 thread.resource :admin 291 end 292 end 293 294 assert_simply_restful_for :threads 295 assert_singleton_restful_for :admin, :path_prefix => 'threads/5/', :options => { :thread_id => '5' } 296 end 297 end 298 190 299 def test_should_not_allow_delete_or_put_on_collection_path 191 300 controller_name = :messages … … 211 320 end 212 321 end 322 323 def with_singleton_resources(*args) 324 with_routing do |set| 325 set.draw { |map| map.resource(*args) } 326 yield 327 end 328 end 213 329 214 330 # runs assert_restful_routes_for and assert_restful_named_routes for on the controller_name and options, without passing a block. … … 216 332 assert_restful_routes_for controller_name, options 217 333 assert_restful_named_routes_for controller_name, options 334 end 335 336 def assert_singleton_restful_for(singleton_name, options = {}) 337 assert_singleton_routes_for singleton_name, options 338 assert_singleton_named_routes_for singleton_name, options 218 339 end 219 340 … … 285 406 end 286 407 408 def assert_singleton_routes_for(singleton_name, options = {}) 409 (options[:options] ||= {})[:controller] ||= singleton_name.to_s 410 411 full_path = "/#{options[:path_prefix]}#{singleton_name}" 412 new_path = "#{full_path}/new" 413 edit_path = "#{full_path};edit" 414 formatted_edit_path = "#{full_path}.xml;edit" 415 416 with_options options[:options] do |controller| 417 controller.assert_routing full_path, :action => 'show' 418 controller.assert_routing new_path, :action => 'new' 419 controller.assert_routing edit_path, :action => 'edit' 420 controller.assert_routing "#{full_path}.xml", :action => 'show', :format => 'xml' 421 controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml' 422 controller.assert_routing formatted_edit_path, :action => 'edit', :format => 'xml' 423 end 424 425 assert_recognizes(options[:options].merge(:action => 'show'), :path => full_path, :method => :get) 426 assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get) 427 assert_recognizes(options[:options].merge(:action => 'edit'), :path => edit_path, :method => :get) 428 assert_recognizes(options[:options].merge(:action => 'create'), :path => full_path, :method => :post) 429 assert_recognizes(options[:options].merge(:action => 'update'), :path => full_path, :method => :put) 430 assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete) 431 432 assert_recognizes(options[:options].merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get) 433 assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get) 434 assert_recognizes(options[:options].merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get) 435 assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post) 436 assert_recognizes(options[:options].merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put) 437 assert_recognizes(options[:options].merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete) 438 439 yield options[:options] if block_given? 440 end 441 442 def assert_singleton_named_routes_for(singleton_name, options = {}) 443 (options[:options] ||= {})[:controller] ||= singleton_name.to_s 444 @controller = "#{options[:options][:controller].camelize}Controller".constantize.new 445 @request = ActionController::TestRequest.new 446 @response = ActionController::TestResponse.new 447 get :show, options[:options] 448 options[:options].delete :action 449 450 full_path = "/#{options[:path_prefix]}#{singleton_name}" 451 452 assert_named_route "#{full_path}", "#{singleton_name}_path", options[:options] 453 assert_named_route "#{full_path}/new", "new_#{singleton_name}_path", options[:options] 454 assert_named_route "#{full_path};edit", "edit_#{singleton_name}_path", options[:options] 455 assert_named_route "#{full_path}.xml", "formatted_#{singleton_name}_path", options[:options].merge(:format => 'xml') 456 assert_named_route "#{full_path}/new.xml", "formatted_new_#{singleton_name}_path", options[:options].merge(:format => 'xml') 457 assert_named_route "#{full_path}.xml;edit", "formatted_edit_#{singleton_name}_path", options[:options].merge(:format => 'xml') 458 end 459 287 460 def assert_named_route(expected, route, options) 288 461 actual = @controller.send(route, options) rescue $!.class.name