1
0
Fork 0
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:
Andrew White 2017-03-17 17:07:09 +00:00 committed by David Heinemeier Hansson
parent 7413be0d31
commit 35afd2c53b
4 changed files with 47 additions and 31 deletions

View file

@ -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)

View file

@ -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

View file

@ -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?

View file

@ -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