diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 26f0e14e7276e..2546bdd43e5bd 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -259,6 +259,7 @@ def initialize(*args) def scope(*args) options = args.extract_options! + options = options.dup case args.first when String @@ -440,6 +441,32 @@ def name_for_action(action) def id_segment ":#{singular}_id" end + + def constraints + options[:constraints] || {} + end + + def id_constraint? + options[:id] && options[:id].is_a?(Regexp) || constraints[:id] && constraints[:id].is_a?(Regexp) + end + + def id_constraint + options[:id] || constraints[:id] + end + + def collection_options + (options || {}).dup.tap do |options| + options.delete(:id) + options[:constraints] = options[:constraints].dup if options[:constraints] + options[:constraints].delete(:id) if options[:constraints].is_a?(Hash) + end + end + + def nested_options + options = { :name_prefix => member_name } + options["#{singular}_id".to_sym] = id_constraint if id_constraint? + options + end end class SingletonResource < Resource #:nodoc: @@ -484,12 +511,14 @@ def resource(*resources, &block) yield if block_given? end - get :show if resource.actions.include?(:show) - post :create if resource.actions.include?(:create) - put :update if resource.actions.include?(:update) - delete :destroy if resource.actions.include?(:destroy) - get :new, :as => resource.name if resource.actions.include?(:new) - get :edit, :as => resource.name if resource.actions.include?(:edit) + scope(resource.options) do + get :show if resource.actions.include?(:show) + post :create if resource.actions.include?(:create) + put :update if resource.actions.include?(:update) + delete :destroy if resource.actions.include?(:destroy) + get :new, :as => resource.name if resource.actions.include?(:new) + get :edit, :as => resource.name if resource.actions.include?(:edit) + end end end @@ -510,17 +539,21 @@ def resources(*resources, &block) yield if block_given? with_scope_level(:collection) do - get :index if resource.actions.include?(:index) - post :create if resource.actions.include?(:create) - get :new, :as => resource.singular if resource.actions.include?(:new) + scope(resource.collection_options) do + get :index if resource.actions.include?(:index) + post :create if resource.actions.include?(:create) + get :new, :as => resource.singular if resource.actions.include?(:new) + end end with_scope_level(:member) do scope(':id') do - get :show if resource.actions.include?(:show) - put :update if resource.actions.include?(:update) - delete :destroy if resource.actions.include?(:destroy) - get :edit, :as => resource.singular if resource.actions.include?(:edit) + scope(resource.options) do + get :show if resource.actions.include?(:show) + put :update if resource.actions.include?(:update) + delete :destroy if resource.actions.include?(:destroy) + get :edit, :as => resource.singular if resource.actions.include?(:edit) + end end end end @@ -559,7 +592,7 @@ def nested end with_scope_level(:nested) do - scope(parent_resource.id_segment, :name_prefix => parent_resource.member_name) do + scope(parent_resource.id_segment, parent_resource.nested_options) do yield end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index c4e71a868969c..b1de96cee5513 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -171,6 +171,12 @@ def self.matches?(request) resources :descriptions root :to => 'projects#index' end + + resources :products, :constraints => { :id => /\d{4}/ } do + resources :images + end + + resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ } end end @@ -794,6 +800,26 @@ def test_default_params end end + def test_resource_constraints + with_test_routes do + assert_raise(ActionController::RoutingError) { get '/products/1' } + get '/products' + assert_equal 'products#index', @response.body + get '/products/0001' + assert_equal 'products#show', @response.body + + assert_raise(ActionController::RoutingError) { get '/products/1/images' } + get '/products/0001/images' + assert_equal 'images#index', @response.body + get '/products/0001/images/1' + assert_equal 'images#show', @response.body + + assert_raise(ActionController::RoutingError) { get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'} } + get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'} + assert_equal 'dashboards#show', @response.body + end + end + private def with_test_routes yield