diff options
author | Gannon McGibbon <gannon.mcgibbon@gmail.com> | 2019-05-22 20:15:18 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-22 20:15:18 -0400 |
commit | eba859d867454cd441f29ff15d8dbf442e51919c (patch) | |
tree | 4e59668361d15dd110bce665105d1f60325f2ae7 /actionpack | |
parent | d5a2f7ec148726d7547e367d7a968e3b4be9b509 (diff) | |
parent | 20104ba13daae81184a339b8949054aecbbd7655 (diff) | |
download | rails-eba859d867454cd441f29ff15d8dbf442e51919c.tar.gz rails-eba859d867454cd441f29ff15d8dbf442e51919c.tar.bz2 rails-eba859d867454cd441f29ff15d8dbf442e51919c.zip |
Merge pull request #34656 from albertoalmagro/path_helper_without_params_does_not_loose_scope
Keep part when scope option has value
Diffstat (limited to 'actionpack')
-rw-r--r-- | actionpack/CHANGELOG.md | 11 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/journey/formatter.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/journey/route.rb | 12 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/mapper.rb | 40 | ||||
-rw-r--r-- | actionpack/test/dispatch/routing_test.rb | 41 | ||||
-rw-r--r-- | actionpack/test/journey/route_test.rb | 41 |
6 files changed, 100 insertions, 47 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index a84f72eca9..4bcf313ecc 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,14 @@ +* Keep part when scope option has value + + When a route was defined within an optional scope, if that route didn't + take parameters the scope was lost when using path helpers. This commit + ensures scope is kept both when the route takes parameters or when it + doesn't. + + Fixes #33219 + + *Alberto Almagro* + * Added `deep_transform_keys` and `deep_transform_keys!` methods to ActionController::Parameters. *Gustavo Gutierrez* diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 52396ec901..53c404ee7c 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -67,7 +67,7 @@ module ActionDispatch parameterized_parts = recall.merge(options) keys_to_keep = route.parts.reverse_each.drop_while { |part| - !options.key?(part) || (options[part] || recall[part]).nil? + !(options.key?(part) || route.scope_options.key?(part)) || (options[part] || recall[part]).nil? } | route.required_parts parameterized_parts.delete_if do |bad_key, _| diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 8165709a3d..4aee7a6f83 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -4,9 +4,9 @@ module ActionDispatch # :stopdoc: module Journey class Route - attr_reader :app, :path, :defaults, :name, :precedence + attr_reader :app, :path, :defaults, :name, :precedence, :constraints, + :internal, :scope_options - attr_reader :constraints, :internal alias :conditions :constraints module VerbMatchers @@ -49,15 +49,10 @@ module ActionDispatch end end - def self.build(name, app, path, constraints, required_defaults, defaults) - request_method_match = verb_matcher(constraints.delete(:request_method)) - new name, app, path, constraints, required_defaults, defaults, request_method_match, 0 - end - ## # +path+ is a path constraint. # +constraints+ is a hash of constraints to be applied to this route. - def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false) + def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false) @name = name @app = app @path = path @@ -72,6 +67,7 @@ module ActionDispatch @decorated_ast = nil @precedence = precedence @path_formatter = @path.build_formatter + @scope_options = scope_options @internal = internal end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f29f66990d..0aba484a45 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -70,17 +70,21 @@ module ActionDispatch ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z} - attr_reader :requirements, :defaults - attr_reader :to, :default_controller, :default_action - attr_reader :required_defaults, :ast + attr_reader :requirements, :defaults, :to, :default_controller, + :default_action, :required_defaults, :ast, :scope_options def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) - options = scope[:options].merge(options) if scope[:options] - - defaults = (scope[:defaults] || {}).dup - scope_constraints = scope[:constraints] || {} + scope_params = { + blocks: scope[:blocks] || [], + constraints: scope[:constraints] || {}, + defaults: (scope[:defaults] || {}).dup, + module: scope[:module], + options: scope[:options] || {} + } - new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options + new set: set, ast: ast, controller: controller, default_action: default_action, + to: to, formatted: formatted, via: via, options_constraints: options_constraints, + anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options) end def self.check_via(via) @@ -111,10 +115,9 @@ module ActionDispatch format != false && path !~ OPTIONAL_FORMAT_REGEX end - def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options) - @defaults = defaults - @set = set - + def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:) + @defaults = scope_params[:defaults] + @set = set @to = intern(to) @default_controller = intern(controller) @default_action = intern(default_action) @@ -122,22 +125,23 @@ module ActionDispatch @anchor = anchor @via = via @internal = options.delete(:internal) + @scope_options = scope_params[:options] path_params = ast.find_all(&:symbol?).map(&:to_sym) options = add_wildcard_options(options, formatted, ast) - options = normalize_options!(options, path_params, modyoule) + options = normalize_options!(options, path_params, scope_params[:module]) split_options = constraints(options, path_params) - constraints = scope_constraints.merge Hash[split_options[:constraints] || []] + constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []] if options_constraints.is_a?(Hash) @defaults = Hash[options_constraints.find_all { |key, default| URL_OPTIONS.include?(key) && (String === default || Integer === default) }].merge @defaults - @blocks = blocks + @blocks = scope_params[:blocks] constraints.merge! options_constraints else @blocks = blocks(options_constraints) @@ -160,8 +164,10 @@ module ActionDispatch end def make_route(name, precedence) - Journey::Route.new(name, application, path, conditions, required_defaults, - defaults, request_method, precedence, @internal) + Journey::Route.new(name: name, app: application, path: path, constraints: conditions, + required_defaults: required_defaults, defaults: defaults, + request_method_match: request_method, precedence: precedence, + scope_options: scope_options, internal: @internal) end def application diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 362488d585..0070d7af72 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -4959,6 +4959,47 @@ class TestPartialDynamicPathSegments < ActionDispatch::IntegrationTest end end +class TestOptionalScopesWithOrWithoutParams < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + scope module: "test_optional_scopes_with_or_without_params" do + scope "(:locale)", locale: /en|es/ do + get "home", to: "home#index" + get "with_param/:foo", to: "home#with_param", as: "with_param" + get "without_param", to: "home#without_param" + end + end + end + end + + class HomeController < ActionController::Base + include Routes.url_helpers + + def index + render inline: "<%= with_param_path(foo: 'bar') %> | <%= without_param_path %>" + end + + def with_param; end + def without_param; end + end + + APP = build_app Routes + + def app + APP + end + + def test_stays_unscoped_with_or_without_params + get "/home" + assert_equal "/with_param/bar | /without_param", response.body + end + + def test_preserves_scope_with_or_without_params + get "/es/home" + assert_equal "/es/with_param/bar | /es/without_param", response.body + end +end + class TestPathParameters < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb index a8bf4a11e2..8828201e4f 100644 --- a/actionpack/test/journey/route_test.rb +++ b/actionpack/test/journey/route_test.rb @@ -9,7 +9,7 @@ module ActionDispatch app = Object.new path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" defaults = {} - route = Route.build("name", app, path, {}, [], defaults) + route = Route.new(name: "name", app: app, path: path, defaults: defaults) assert_equal app, route.app assert_equal path, route.path @@ -17,10 +17,9 @@ module ActionDispatch end def test_route_adds_itself_as_memo - app = Object.new - path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" - defaults = {} - route = Route.build("name", app, path, {}, [], defaults) + app = Object.new + path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" + route = Route.new(name: "name", app: app, path: path) route.ast.grep(Nodes::Terminal).each do |node| assert_equal route, node.memo @@ -28,30 +27,30 @@ module ActionDispatch end def test_path_requirements_override_defaults - path = Path::Pattern.build(":name", { name: /love/ }, "/", true) - defaults = { name: "tender" } - route = Route.build("name", nil, path, {}, [], defaults) + path = Path::Pattern.build(":name", { name: /love/ }, "/", true) + defaults = { name: "tender" } + route = Route.new(name: "name", path: path, defaults: defaults) assert_equal(/love/, route.requirements[:name]) end def test_ip_address path = Path::Pattern.from_string "/messages/:id(.:format)" - route = Route.build("name", nil, path, { ip: "192.168.1.1" }, [], - controller: "foo", action: "bar") + route = Route.new(name: "name", path: path, constraints: { ip: "192.168.1.1" }, + defaults: { controller: "foo", action: "bar" }) assert_equal "192.168.1.1", route.ip end def test_default_ip path = Path::Pattern.from_string "/messages/:id(.:format)" - route = Route.build("name", nil, path, {}, [], - controller: "foo", action: "bar") + route = Route.new(name: "name", path: path, + defaults: { controller: "foo", action: "bar" }) assert_equal(//, route.ip) end def test_format_with_star path = Path::Pattern.from_string "/:controller/*extra" - route = Route.build("name", nil, path, {}, [], - controller: "foo", action: "bar") + route = Route.new(name: "name", path: path, + defaults: { controller: "foo", action: "bar" }) assert_equal "/foo/himom", route.format( controller: "foo", extra: "himom") @@ -59,7 +58,8 @@ module ActionDispatch def test_connects_all_match path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" - route = Route.build("name", nil, path, { action: "bar" }, [], controller: "foo") + route = Route.new(name: "name", path: path, constraints: { action: "bar" }, + defaults: { controller: "foo" }) assert_equal "/foo/bar/10", route.format( controller: "foo", @@ -69,34 +69,33 @@ module ActionDispatch def test_extras_are_not_included_if_optional path = Path::Pattern.from_string "/page/:id(/:action)" - route = Route.build("name", nil, path, {}, [], action: "show") + route = Route.new(name: "name", path: path, defaults: { action: "show" }) assert_equal "/page/10", route.format(id: 10) end def test_extras_are_not_included_if_optional_with_parameter path = Path::Pattern.from_string "(/sections/:section)/pages/:id" - route = Route.build("name", nil, path, {}, [], action: "show") + route = Route.new(name: "name", path: path, defaults: { action: "show" }) assert_equal "/pages/10", route.format(id: 10) end def test_extras_are_not_included_if_optional_parameter_is_nil path = Path::Pattern.from_string "(/sections/:section)/pages/:id" - route = Route.build("name", nil, path, {}, [], action: "show") + route = Route.new(name: "name", path: path, defaults: { action: "show" }) assert_equal "/pages/10", route.format(id: 10, section: nil) end def test_score - constraints = {} defaults = { controller: "pages", action: "show" } path = Path::Pattern.from_string "/page/:id(/:action)(.:format)" - specific = Route.build "name", nil, path, constraints, [:controller, :action], defaults + specific = Route.new name: "name", path: path, required_defaults: [:controller, :action], defaults: defaults path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)" - generic = Route.build "name", nil, path, constraints, [], {} + generic = Route.new name: "name", path: path knowledge = { "id" => true, "controller" => true, "action" => true } |