mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add support for calling nested direct routes (#28462)
Not all requirements can be expressed in terms of polymorphic url options so add a `route_for` method that allows calling another direct route (or regular named route) which a set of arguments, e.g: resources :buckets direct :recordable do |recording| route_for(:bucket, recording.bucket) end direct :threadable do |threadable| route_for(:recordable, threadable.parent) end This maintains the context of the original caller, e.g. threadable_path(threadable) # => /buckets/1 threadable_url(threadable) # => http://example.com/buckets/1
This commit is contained in:
parent
7413be0d31
commit
35afd2c53b
4 changed files with 47 additions and 31 deletions
|
@ -40,7 +40,7 @@ module ActionDispatch
|
|||
#
|
||||
# Example usage:
|
||||
#
|
||||
# edit_polymorphic_path(@post) # => "/posts/1/edit"
|
||||
# edit_polymorphic_path(@post) # => "/posts/1/edit"
|
||||
# polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf"
|
||||
#
|
||||
# == Usage with mounted engines
|
||||
|
@ -104,7 +104,7 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
if mapping = polymorphic_mapping(record_or_hash_or_array)
|
||||
return mapping.call(self, [record_or_hash_or_array, options])
|
||||
return mapping.call(self, [record_or_hash_or_array, options], false)
|
||||
end
|
||||
|
||||
opts = options.dup
|
||||
|
@ -128,7 +128,7 @@ module ActionDispatch
|
|||
end
|
||||
|
||||
if mapping = polymorphic_mapping(record_or_hash_or_array)
|
||||
return mapping.call(self, [record_or_hash_or_array, options], only_path: true)
|
||||
return mapping.call(self, [record_or_hash_or_array, options], true)
|
||||
end
|
||||
|
||||
opts = options.dup
|
||||
|
@ -273,7 +273,7 @@ module ActionDispatch
|
|||
|
||||
def handle_model_call(target, record)
|
||||
if mapping = polymorphic_mapping(target, record)
|
||||
mapping.call(target, [record], only_path: suffix == "path")
|
||||
mapping.call(target, [record], suffix == "path")
|
||||
else
|
||||
method, args = handle_model(record)
|
||||
target.send(method, *args)
|
||||
|
|
|
@ -164,13 +164,13 @@ module ActionDispatch
|
|||
|
||||
@path_helpers_module.module_eval do
|
||||
define_method(:"#{name}_path") do |*args|
|
||||
helper.call(self, args, only_path: true)
|
||||
helper.call(self, args, true)
|
||||
end
|
||||
end
|
||||
|
||||
@url_helpers_module.module_eval do
|
||||
define_method(:"#{name}_url") do |*args|
|
||||
helper.call(self, args)
|
||||
helper.call(self, args, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -509,6 +509,10 @@ module ActionDispatch
|
|||
@_proxy.url_for(options)
|
||||
end
|
||||
|
||||
def route_for(name, *args)
|
||||
@_proxy.route_for(name, *args)
|
||||
end
|
||||
|
||||
def optimize_routes_generation?
|
||||
@_proxy.optimize_routes_generation?
|
||||
end
|
||||
|
@ -613,26 +617,14 @@ module ActionDispatch
|
|||
@block = block
|
||||
end
|
||||
|
||||
def call(t, args, outer_options = {})
|
||||
def call(t, args, only_path = false)
|
||||
options = args.extract_options!
|
||||
url_options = eval_block(t, args, options)
|
||||
url = t.url_for(eval_block(t, args, options))
|
||||
|
||||
case url_options
|
||||
when String
|
||||
t.url_for(url_options)
|
||||
when Hash
|
||||
t.url_for(url_options.merge(outer_options))
|
||||
when ActionController::Parameters
|
||||
if url_options.permitted?
|
||||
t.url_for(url_options.to_h.merge(outer_options))
|
||||
else
|
||||
raise ArgumentError, "Generating a URL from non sanitized request parameters is insecure!"
|
||||
end
|
||||
when Array
|
||||
opts = url_options.extract_options!
|
||||
t.url_for(url_options.push(opts.merge(outer_options)))
|
||||
if only_path
|
||||
"/" + url.partition(%r{(?<!/)/(?!/)}).last
|
||||
else
|
||||
t.url_for([url_options, outer_options])
|
||||
url
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -192,6 +192,10 @@ module ActionDispatch
|
|||
end
|
||||
end
|
||||
|
||||
def route_for(name, *args) # :nodoc:
|
||||
public_send(:"#{name}_url", *args)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def optimize_routes_generation?
|
||||
|
|
|
@ -4,18 +4,23 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
class Linkable
|
||||
attr_reader :id
|
||||
|
||||
def self.name
|
||||
super.demodulize
|
||||
end
|
||||
|
||||
def initialize(id)
|
||||
@id = id
|
||||
end
|
||||
|
||||
def linkable_type
|
||||
self.class.name.demodulize.underscore
|
||||
self.class.name.underscore
|
||||
end
|
||||
end
|
||||
|
||||
class Category < Linkable; end
|
||||
class Collection < Linkable; end
|
||||
class Product < Linkable; end
|
||||
class Manufacturer < Linkable; end
|
||||
|
||||
class Model
|
||||
extend ActiveModel::Naming
|
||||
|
@ -79,7 +84,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
get "/media/:id", to: "media#show", as: :media
|
||||
get "/pages/:id", to: "pages#show", as: :page
|
||||
|
||||
resources :categories, :collections, :products
|
||||
resources :categories, :collections, :products, :manufacturers
|
||||
|
||||
namespace :admin do
|
||||
get "/dashboard", to: "dashboard#index"
|
||||
|
@ -89,6 +94,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
direct("string") { "http://www.rubyonrails.org" }
|
||||
direct(:helper) { basket_url }
|
||||
direct(:linkable) { |linkable| [:"#{linkable.linkable_type}", { id: linkable.id }] }
|
||||
direct(:nested) { |linkable| route_for(:linkable, linkable) }
|
||||
direct(:params) { |params| params }
|
||||
direct(:symbol) { :basket }
|
||||
direct(:hash) { { controller: "basket", action: "show" } }
|
||||
|
@ -102,6 +108,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
|
||||
resolve("Article") { |article| [:post, { id: article.id }] }
|
||||
resolve("Basket") { |basket| [:basket] }
|
||||
resolve("Manufacturer") { |manufacturer| route_for(:linkable, manufacturer) }
|
||||
resolve("User", anchor: "details") { |user, options| [:profile, options] }
|
||||
resolve("Video") { |video| [:media, { id: video.id }] }
|
||||
resolve(%w[Page CategoryPage ProductPage]) { |page| [:page, { id: page.id }] }
|
||||
|
@ -119,6 +126,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
@category = Category.new("1")
|
||||
@collection = Collection.new("2")
|
||||
@product = Product.new("3")
|
||||
@manufacturer = Manufacturer.new("apple")
|
||||
@basket = Basket.new
|
||||
@user = User.new
|
||||
@video = Video.new("4")
|
||||
|
@ -136,14 +144,14 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
end
|
||||
|
||||
def test_direct_paths
|
||||
assert_equal "http://www.rubyonrails.org", website_path
|
||||
assert_equal "http://www.rubyonrails.org", Routes.url_helpers.website_path
|
||||
assert_equal "/", website_path
|
||||
assert_equal "/", Routes.url_helpers.website_path
|
||||
|
||||
assert_equal "http://www.rubyonrails.org", string_path
|
||||
assert_equal "http://www.rubyonrails.org", Routes.url_helpers.string_path
|
||||
assert_equal "/", string_path
|
||||
assert_equal "/", Routes.url_helpers.string_path
|
||||
|
||||
assert_equal "http://www.example.com/basket", helper_url
|
||||
assert_equal "http://www.example.com/basket", Routes.url_helpers.helper_url
|
||||
assert_equal "/basket", helper_path
|
||||
assert_equal "/basket", Routes.url_helpers.helper_path
|
||||
|
||||
assert_equal "/categories/1", linkable_path(@category)
|
||||
assert_equal "/categories/1", Routes.url_helpers.linkable_path(@category)
|
||||
|
@ -152,6 +160,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
assert_equal "/products/3", linkable_path(@product)
|
||||
assert_equal "/products/3", Routes.url_helpers.linkable_path(@product)
|
||||
|
||||
assert_equal "/categories/1", nested_path(@category)
|
||||
assert_equal "/categories/1", Routes.url_helpers.nested_path(@category)
|
||||
|
||||
assert_equal "/", params_path(@safe_params)
|
||||
assert_equal "/", Routes.url_helpers.params_path(@safe_params)
|
||||
assert_raises(ArgumentError) { params_path(@unsafe_params) }
|
||||
|
@ -192,6 +203,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
assert_equal "http://www.example.com/products/3", linkable_url(@product)
|
||||
assert_equal "http://www.example.com/products/3", Routes.url_helpers.linkable_url(@product)
|
||||
|
||||
assert_equal "http://www.example.com/categories/1", nested_url(@category)
|
||||
assert_equal "http://www.example.com/categories/1", Routes.url_helpers.nested_url(@category)
|
||||
|
||||
assert_equal "http://www.example.com/", params_url(@safe_params)
|
||||
assert_equal "http://www.example.com/", Routes.url_helpers.params_url(@safe_params)
|
||||
assert_raises(ArgumentError) { params_url(@unsafe_params) }
|
||||
|
@ -244,6 +258,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
assert_equal "/pages/8", polymorphic_path(@product_page)
|
||||
assert_equal "/pages/8", Routes.url_helpers.polymorphic_path(@product_page)
|
||||
assert_equal "/pages/8", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @product_page)
|
||||
|
||||
assert_equal "/manufacturers/apple", polymorphic_path(@manufacturer)
|
||||
assert_equal "/manufacturers/apple", Routes.url_helpers.polymorphic_path(@manufacturer)
|
||||
end
|
||||
|
||||
def test_resolve_urls
|
||||
|
@ -277,6 +294,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
|||
assert_equal "http://www.example.com/pages/8", polymorphic_url(@product_page)
|
||||
assert_equal "http://www.example.com/pages/8", Routes.url_helpers.polymorphic_url(@product_page)
|
||||
assert_equal "http://www.example.com/pages/8", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @product_page)
|
||||
|
||||
assert_equal "http://www.example.com/manufacturers/apple", polymorphic_url(@manufacturer)
|
||||
assert_equal "http://www.example.com/manufacturers/apple", Routes.url_helpers.polymorphic_url(@manufacturer)
|
||||
end
|
||||
|
||||
def test_defining_direct_inside_a_scope_raises_runtime_error
|
||||
|
|
Loading…
Reference in a new issue