mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
From now on, parameters defined in default_url_options can be absent from named routes.
This allows the following setup to work: # app/controllers/application_controller.rb class ApplicationController def default_url_options(options=nil) { :locale => I18n.locale } end end # From your views and controllers: I18n.locale #=> :en users_url #=> "/en/users" users_url(:pl) #=> "/pl/users" user_url(1) #=> "/en/users/1" user_url(:pl, 1) #=> "/pl/users/1" user_url(1, :locale => :pl) #=> "/pl/users/1" If you provide all expected parameters, it still works as previously. But if any parameter is missing, it tries to assign all possible ones with the hash returned in default_url_options or the one passed straight to the named route method. Beware that default_url_options in ApplicationController is not shared with ActionMailer, so you are required to always give the locale in your email views.
This commit is contained in:
parent
f564f947d9
commit
f149eb19d4
4 changed files with 93 additions and 65 deletions
|
@ -74,9 +74,8 @@ module ActionDispatch
|
||||||
@routes = {}
|
@routes = {}
|
||||||
@helpers = []
|
@helpers = []
|
||||||
|
|
||||||
@module ||= Module.new
|
@module ||= Module.new do
|
||||||
@module.instance_methods.each do |selector|
|
instance_methods.each { |selector| remove_method(selector) }
|
||||||
@module.class_eval { remove_method selector }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -168,25 +167,56 @@ module ActionDispatch
|
||||||
selector = url_helper_name(name, kind)
|
selector = url_helper_name(name, kind)
|
||||||
hash_access_method = hash_access_name(name, kind)
|
hash_access_method = hash_access_name(name, kind)
|
||||||
|
|
||||||
# We use module_eval to avoid leaks
|
# We use module_eval to avoid leaks.
|
||||||
|
#
|
||||||
|
# def users_url(*args)
|
||||||
|
# if args.empty? || Hash === args.first
|
||||||
|
# options = hash_for_users_url(args.first || {})
|
||||||
|
# else
|
||||||
|
# options = hash_for_users_url(args.extract_options!)
|
||||||
|
# default = default_url_options(options) if self.respond_to?(:default_url_options, true)
|
||||||
|
# options = (default ||= {}).merge(options)
|
||||||
|
#
|
||||||
|
# keys = []
|
||||||
|
# keys -= options.keys unless keys.size == args.size
|
||||||
|
#
|
||||||
|
# args = args.zip(keys).inject({}) do |h, (v, k)|
|
||||||
|
# h[k] = v
|
||||||
|
# h
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # Tell url_for to skip default_url_options
|
||||||
|
# options[:use_defaults] = false
|
||||||
|
# options.merge!(args)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# url_for(options)
|
||||||
|
# end
|
||||||
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
||||||
def #{selector}(*args) # def users_url(*args)
|
def #{selector}(*args)
|
||||||
#
|
if args.empty? || Hash === args.first
|
||||||
opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
|
options = #{hash_access_method}(args.first || {})
|
||||||
args.first || {} # args.first || {}
|
else
|
||||||
else # else
|
options = #{hash_access_method}(args.extract_options!)
|
||||||
options = args.extract_options! # options = args.extract_options!
|
default = default_url_options(options) if self.respond_to?(:default_url_options, true)
|
||||||
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
|
options = (default ||= {}).merge(options)
|
||||||
h[k] = v # h[k] = v
|
|
||||||
h # h
|
keys = #{route.segment_keys.inspect}
|
||||||
end # end
|
keys -= options.keys unless keys.size == args.size
|
||||||
options.merge(args) # options.merge(args)
|
|
||||||
end # end
|
args = args.zip(keys).inject({}) do |h, (v, k)|
|
||||||
#
|
h[k] = v
|
||||||
url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
|
h
|
||||||
#
|
end
|
||||||
end # end
|
|
||||||
protected :#{selector} # protected :users_url
|
# Tell url_for to skip default_url_options
|
||||||
|
options[:use_defaults] = false
|
||||||
|
options.merge!(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
url_for(options)
|
||||||
|
end
|
||||||
|
protected :#{selector}
|
||||||
END_EVAL
|
END_EVAL
|
||||||
helpers << selector
|
helpers << selector
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,11 @@ module ActionView
|
||||||
module UrlHelper
|
module UrlHelper
|
||||||
include JavaScriptHelper
|
include JavaScriptHelper
|
||||||
|
|
||||||
|
# Need to map default url options to controller one.
|
||||||
|
def default_url_options(*args) #:nodoc:
|
||||||
|
@controller.send(:default_url_options, *args)
|
||||||
|
end
|
||||||
|
|
||||||
# Returns the URL for the set of +options+ provided. This takes the
|
# Returns the URL for the set of +options+ provided. This takes the
|
||||||
# same options as +url_for+ in Action Controller (see the
|
# same options as +url_for+ in Action Controller (see the
|
||||||
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
|
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Series < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
class PolymorphicRoutesTest < ActionController::TestCase
|
class PolymorphicRoutesTest < ActionController::TestCase
|
||||||
include ActionController::UrlWriter
|
include ActionController::UrlFor
|
||||||
self.default_url_options[:host] = 'example.com'
|
self.default_url_options[:host] = 'example.com'
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
|
|
|
@ -6,6 +6,7 @@ require 'pp' # require 'pp' early to prevent hidden_methods from not picking up
|
||||||
module Submodule
|
module Submodule
|
||||||
class ContainedEmptyController < ActionController::Base
|
class ContainedEmptyController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
class ContainedNonEmptyController < ActionController::Base
|
class ContainedNonEmptyController < ActionController::Base
|
||||||
def public_action
|
def public_action
|
||||||
render :nothing => true
|
render :nothing => true
|
||||||
|
@ -20,12 +21,15 @@ module Submodule
|
||||||
end
|
end
|
||||||
hide_action :another_hidden_action
|
hide_action :another_hidden_action
|
||||||
end
|
end
|
||||||
|
|
||||||
class SubclassedController < ContainedNonEmptyController
|
class SubclassedController < ContainedNonEmptyController
|
||||||
hide_action :public_action # Hiding it here should not affect the superclass.
|
hide_action :public_action # Hiding it here should not affect the superclass.
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class EmptyController < ActionController::Base
|
class EmptyController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
class NonEmptyController < ActionController::Base
|
class NonEmptyController < ActionController::Base
|
||||||
def public_action
|
def public_action
|
||||||
render :nothing => true
|
render :nothing => true
|
||||||
|
@ -37,7 +41,6 @@ class NonEmptyController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
class MethodMissingController < ActionController::Base
|
class MethodMissingController < ActionController::Base
|
||||||
|
|
||||||
hide_action :shouldnt_be_called
|
hide_action :shouldnt_be_called
|
||||||
def shouldnt_be_called
|
def shouldnt_be_called
|
||||||
raise "NO WAY!"
|
raise "NO WAY!"
|
||||||
|
@ -48,16 +51,15 @@ protected
|
||||||
def method_missing(selector)
|
def method_missing(selector)
|
||||||
render :text => selector.to_s
|
render :text => selector.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class DefaultUrlOptionsController < ActionController::Base
|
class DefaultUrlOptionsController < ActionController::Base
|
||||||
def default_url_options_action
|
def from_view
|
||||||
render :nothing => true
|
render :inline => "<%= #{params[:route]} %>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_url_options(options = nil)
|
def default_url_options(options = nil)
|
||||||
{ :host => 'www.override.com', :action => 'new', :bacon => 'chunky' }
|
{ :host => 'www.override.com', :action => 'new', :locale => 'en' }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -68,6 +70,7 @@ class ControllerClassTests < Test::Unit::TestCase
|
||||||
assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
|
assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
|
||||||
assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
|
assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_controller_name
|
def test_controller_name
|
||||||
assert_equal 'empty', EmptyController.controller_name
|
assert_equal 'empty', EmptyController.controller_name
|
||||||
assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
|
assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
|
||||||
|
@ -86,41 +89,16 @@ class ControllerInstanceTests < Test::Unit::TestCase
|
||||||
|
|
||||||
def test_action_methods
|
def test_action_methods
|
||||||
@empty_controllers.each do |c|
|
@empty_controllers.each do |c|
|
||||||
hide_mocha_methods_from_controller(c)
|
|
||||||
assert_equal Set.new, c.class.__send__(:action_methods), "#{c.controller_path} should be empty!"
|
assert_equal Set.new, c.class.__send__(:action_methods), "#{c.controller_path} should be empty!"
|
||||||
end
|
end
|
||||||
|
|
||||||
@non_empty_controllers.each do |c|
|
@non_empty_controllers.each do |c|
|
||||||
hide_mocha_methods_from_controller(c)
|
|
||||||
assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!"
|
assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
# Mocha adds some public instance methods to Object that would be
|
|
||||||
# considered actions, so explicitly hide_action them.
|
|
||||||
def hide_mocha_methods_from_controller(controller)
|
|
||||||
mocha_methods = [
|
|
||||||
:expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object,
|
|
||||||
:stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher,
|
|
||||||
]
|
|
||||||
controller.class.__send__(:hide_action, *mocha_methods)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class PerformActionTest < ActionController::TestCase
|
class PerformActionTest < ActionController::TestCase
|
||||||
class MockLogger
|
|
||||||
attr_reader :logged
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@logged = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(method, *args)
|
|
||||||
@logged << args.first.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def use_controller(controller_class)
|
def use_controller(controller_class)
|
||||||
@controller = controller_class.new
|
@controller = controller_class.new
|
||||||
|
|
||||||
|
@ -128,9 +106,8 @@ class PerformActionTest < ActionController::TestCase
|
||||||
# a more accurate simulation of what happens in "real life".
|
# a more accurate simulation of what happens in "real life".
|
||||||
@controller.logger = Logger.new(nil)
|
@controller.logger = Logger.new(nil)
|
||||||
|
|
||||||
@request = ActionController::TestRequest.new
|
@request = ActionController::TestRequest.new
|
||||||
@response = ActionController::TestResponse.new
|
@response = ActionController::TestResponse.new
|
||||||
|
|
||||||
@request.host = "www.nextangle.com"
|
@request.host = "www.nextangle.com"
|
||||||
|
|
||||||
rescue_action_in_public!
|
rescue_action_in_public!
|
||||||
|
@ -145,8 +122,7 @@ class PerformActionTest < ActionController::TestCase
|
||||||
|
|
||||||
def test_method_missing_is_not_an_action_name
|
def test_method_missing_is_not_an_action_name
|
||||||
use_controller MethodMissingController
|
use_controller MethodMissingController
|
||||||
|
assert !@controller.__send__(:action_method?, 'method_missing')
|
||||||
assert ! @controller.__send__(:action_method?, 'method_missing')
|
|
||||||
|
|
||||||
get :method_missing
|
get :method_missing
|
||||||
assert_response :success
|
assert_response :success
|
||||||
|
@ -172,16 +148,35 @@ class DefaultUrlOptionsTest < ActionController::TestCase
|
||||||
def test_default_url_options_are_used_if_set
|
def test_default_url_options_are_used_if_set
|
||||||
with_routing do |set|
|
with_routing do |set|
|
||||||
set.draw do |map|
|
set.draw do |map|
|
||||||
match 'default_url_options', :to => 'default_url_options#default_url_options_action', :as => :default_url_options
|
match 'from_view', :to => 'default_url_options#from_view', :as => :from_view
|
||||||
match ':controller/:action'
|
match ':controller/:action'
|
||||||
end
|
end
|
||||||
|
|
||||||
get :default_url_options_action # Make a dummy request so that the controller is initialized properly.
|
get :from_view, :route => "from_view_url"
|
||||||
|
|
||||||
assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options')
|
assert_equal 'http://www.override.com/from_view?locale=en', @response.body
|
||||||
assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url)
|
assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
|
||||||
|
assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_default_url_options_are_used_in_non_positional_parameters
|
||||||
|
with_routing do |set|
|
||||||
|
set.draw do |map|
|
||||||
|
scope("/:locale") do
|
||||||
|
resources :descriptions
|
||||||
|
end
|
||||||
|
match ':controller/:action'
|
||||||
|
end
|
||||||
|
|
||||||
|
get :from_view, :route => "description_path(1)"
|
||||||
|
|
||||||
|
assert_equal '/en/descriptions/1', @response.body
|
||||||
|
assert_equal '/en/descriptions/1', @controller.send(:description_path, 1)
|
||||||
|
assert_equal '/pl/descriptions/1', @controller.send(:description_path, 1, :locale => "pl")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class EmptyUrlOptionsTest < ActionController::TestCase
|
class EmptyUrlOptionsTest < ActionController::TestCase
|
||||||
|
@ -197,10 +192,8 @@ class EmptyUrlOptionsTest < ActionController::TestCase
|
||||||
get :public_action
|
get :public_action
|
||||||
assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for
|
assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
class EnsureNamedRoutesWorksTicket22BugTest < ActionController::TestCase
|
def test_named_routes_with_path_without_doing_a_request_first
|
||||||
def test_named_routes_still_work
|
|
||||||
with_routing do |set|
|
with_routing do |set|
|
||||||
set.draw do |map|
|
set.draw do |map|
|
||||||
resources :things
|
resources :things
|
||||||
|
|
Loading…
Reference in a new issue