mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add support for defining custom url helpers in routes.rb
Allow the definition of custom url helpers that will be available automatically wherever standard url helpers are available. The current solution is to create helper methods in ApplicationHelper or some other helper module and this isn't a great solution since the url helper module can be called directly or included in another class which doesn't include the normal helper modules. Reference #22512.
This commit is contained in:
parent
31dc46cb9c
commit
ce7d5fb2e6
4 changed files with 270 additions and 2 deletions
|
@ -2020,6 +2020,46 @@ module ActionDispatch
|
|||
end
|
||||
end
|
||||
|
||||
module UrlHelpers
|
||||
# Define a custom url helper that will be added to the url helpers
|
||||
# module. This allows you override and/or replace the default behavior
|
||||
# of routing helpers, e.g:
|
||||
#
|
||||
# url_helper :homepage do
|
||||
# "http://www.rubyonrails.org"
|
||||
# end
|
||||
#
|
||||
# url_helper :commentable do |model|
|
||||
# [ model, anchor: model.dom_id ]
|
||||
# end
|
||||
#
|
||||
# url_helper :main do
|
||||
# { controller: 'pages', action: 'index', subdomain: 'www' }
|
||||
# end
|
||||
#
|
||||
# The return value must be a valid set of arguments for `url_for` which
|
||||
# will actually build the url string. This can be one of the following:
|
||||
#
|
||||
# * A string, which is treated as a generated url
|
||||
# * A hash, e.g. { controller: 'pages', action: 'index' }
|
||||
# * An array, which is passed to `polymorphic_url`
|
||||
# * An Active Model instance
|
||||
# * An Active Model class
|
||||
#
|
||||
# You can also specify default options that will be passed through to
|
||||
# your url helper definition, e.g:
|
||||
#
|
||||
# url_helper :browse, page: 1, size: 10 do |options|
|
||||
# [ :products, options.merge(params.permit(:page, :size)) ]
|
||||
# end
|
||||
#
|
||||
# NOTE: It is the url helper's responsibility to return the correct
|
||||
# set of options to be passed to the `url_for` call.
|
||||
def url_helper(name, options = {}, &block)
|
||||
@set.add_url_helper(name, options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class Scope # :nodoc:
|
||||
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
||||
:controller, :action, :path_names, :constraints,
|
||||
|
@ -2113,6 +2153,7 @@ module ActionDispatch
|
|||
include Scoping
|
||||
include Concerns
|
||||
include Resources
|
||||
include UrlHelpers
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -73,6 +73,7 @@ module ActionDispatch
|
|||
@routes = {}
|
||||
@path_helpers = Set.new
|
||||
@url_helpers = Set.new
|
||||
@custom_helpers = Set.new
|
||||
@url_helpers_module = Module.new
|
||||
@path_helpers_module = Module.new
|
||||
end
|
||||
|
@ -95,9 +96,23 @@ module ActionDispatch
|
|||
@url_helpers_module.send :undef_method, helper
|
||||
end
|
||||
|
||||
@custom_helpers.each do |helper|
|
||||
path_name = :"#{helper}_path"
|
||||
url_name = :"#{helper}_url"
|
||||
|
||||
if @path_helpers_module.method_defined?(path_name)
|
||||
@path_helpers_module.send :undef_method, path_name
|
||||
end
|
||||
|
||||
if @url_helpers_module.method_defined?(url_name)
|
||||
@url_helpers_module.send :undef_method, url_name
|
||||
end
|
||||
end
|
||||
|
||||
@routes.clear
|
||||
@path_helpers.clear
|
||||
@url_helpers.clear
|
||||
@custom_helpers.clear
|
||||
end
|
||||
|
||||
def add(name, route)
|
||||
|
@ -143,6 +158,62 @@ module ActionDispatch
|
|||
routes.length
|
||||
end
|
||||
|
||||
def add_url_helper(name, defaults, &block)
|
||||
@custom_helpers << name
|
||||
helper = CustomUrlHelper.new(name, defaults, &block)
|
||||
|
||||
@path_helpers_module.module_eval do
|
||||
define_method(:"#{name}_path") do |*args|
|
||||
options = args.extract_options!
|
||||
helper.call(self, args, options, only_path: true)
|
||||
end
|
||||
end
|
||||
|
||||
@url_helpers_module.module_eval do
|
||||
define_method(:"#{name}_url") do |*args|
|
||||
options = args.extract_options!
|
||||
helper.call(self, args, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CustomUrlHelper
|
||||
attr_reader :name, :defaults, :block
|
||||
|
||||
def initialize(name, defaults, &block)
|
||||
@name = name
|
||||
@defaults = defaults
|
||||
@block = block
|
||||
end
|
||||
|
||||
def call(t, args, options, outer_options = {})
|
||||
url_options = 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 an 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)))
|
||||
else
|
||||
t.url_for([url_options, outer_options])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def eval_block(t, args, options)
|
||||
t.instance_exec(*args, defaults.merge(options), &block)
|
||||
end
|
||||
end
|
||||
|
||||
class UrlHelper
|
||||
def self.create(route, options, route_name, url_strategy)
|
||||
if optimize_helper?(route)
|
||||
|
@ -554,6 +625,10 @@ module ActionDispatch
|
|||
route
|
||||
end
|
||||
|
||||
def add_url_helper(name, options, &block)
|
||||
named_routes.add_url_helper(name, options, &block)
|
||||
end
|
||||
|
||||
class Generator
|
||||
PARAMETERIZE = lambda do |name, value|
|
||||
if name == :controller
|
||||
|
|
121
actionpack/test/dispatch/routing/custom_url_helpers_test.rb
Normal file
121
actionpack/test/dispatch/routing/custom_url_helpers_test.rb
Normal file
|
@ -0,0 +1,121 @@
|
|||
require "abstract_unit"
|
||||
|
||||
class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
|
||||
class Linkable
|
||||
attr_reader :id
|
||||
|
||||
def initialize(id)
|
||||
@id = id
|
||||
end
|
||||
|
||||
def linkable_type
|
||||
self.class.name.demodulize.underscore
|
||||
end
|
||||
end
|
||||
|
||||
class Category < Linkable; end
|
||||
class Collection < Linkable; end
|
||||
class Product < Linkable; end
|
||||
|
||||
Routes = ActionDispatch::Routing::RouteSet.new
|
||||
Routes.draw do
|
||||
default_url_options host: "www.example.com"
|
||||
|
||||
root to: "pages#index"
|
||||
get "/basket", to: "basket#show", as: :basket
|
||||
|
||||
resources :categories, :collections, :products
|
||||
|
||||
namespace :admin do
|
||||
get "/dashboard", to: "dashboard#index"
|
||||
end
|
||||
|
||||
url_helper(:website) { "http://www.rubyonrails.org" }
|
||||
url_helper(:linkable) { |linkable| [:"#{linkable.linkable_type}", { id: linkable.id }] }
|
||||
url_helper(:params) { |params| params }
|
||||
url_helper(:symbol) { :basket }
|
||||
url_helper(:hash) { { controller: "basket", action: "show" } }
|
||||
url_helper(:array) { [:admin, :dashboard] }
|
||||
url_helper(:options) { |options| [:products, options] }
|
||||
url_helper(:defaults, size: 10) { |options| [:products, options] }
|
||||
end
|
||||
|
||||
APP = build_app Routes
|
||||
|
||||
def app
|
||||
APP
|
||||
end
|
||||
|
||||
include Routes.url_helpers
|
||||
|
||||
def setup
|
||||
@category = Category.new("1")
|
||||
@collection = Collection.new("2")
|
||||
@product = Product.new("3")
|
||||
@path_params = { "controller" => "pages", "action" => "index" }
|
||||
@unsafe_params = ActionController::Parameters.new(@path_params)
|
||||
@safe_params = ActionController::Parameters.new(@path_params).permit(:controller, :action)
|
||||
end
|
||||
|
||||
def test_custom_path_helper
|
||||
assert_equal "http://www.rubyonrails.org", website_path
|
||||
assert_equal "http://www.rubyonrails.org", Routes.url_helpers.website_path
|
||||
|
||||
assert_equal "/categories/1", linkable_path(@category)
|
||||
assert_equal "/categories/1", Routes.url_helpers.linkable_path(@category)
|
||||
assert_equal "/collections/2", linkable_path(@collection)
|
||||
assert_equal "/collections/2", Routes.url_helpers.linkable_path(@collection)
|
||||
assert_equal "/products/3", linkable_path(@product)
|
||||
assert_equal "/products/3", Routes.url_helpers.linkable_path(@product)
|
||||
|
||||
assert_equal "/", params_path(@safe_params)
|
||||
assert_equal "/", Routes.url_helpers.params_path(@safe_params)
|
||||
assert_raises(ArgumentError) { params_path(@unsafe_params) }
|
||||
assert_raises(ArgumentError) { Routes.url_helpers.params_path(@unsafe_params) }
|
||||
|
||||
assert_equal "/basket", symbol_path
|
||||
assert_equal "/basket", Routes.url_helpers.symbol_path
|
||||
assert_equal "/basket", hash_path
|
||||
assert_equal "/basket", Routes.url_helpers.hash_path
|
||||
assert_equal "/admin/dashboard", array_path
|
||||
assert_equal "/admin/dashboard", Routes.url_helpers.array_path
|
||||
|
||||
assert_equal "/products?page=2", options_path(page: 2)
|
||||
assert_equal "/products?page=2", Routes.url_helpers.options_path(page: 2)
|
||||
assert_equal "/products?size=10", defaults_path
|
||||
assert_equal "/products?size=10", Routes.url_helpers.defaults_path
|
||||
assert_equal "/products?size=20", defaults_path(size: 20)
|
||||
assert_equal "/products?size=20", Routes.url_helpers.defaults_path(size: 20)
|
||||
end
|
||||
|
||||
def test_custom_url_helper
|
||||
assert_equal "http://www.rubyonrails.org", website_url
|
||||
assert_equal "http://www.rubyonrails.org", Routes.url_helpers.website_url
|
||||
|
||||
assert_equal "http://www.example.com/categories/1", linkable_url(@category)
|
||||
assert_equal "http://www.example.com/categories/1", Routes.url_helpers.linkable_url(@category)
|
||||
assert_equal "http://www.example.com/collections/2", linkable_url(@collection)
|
||||
assert_equal "http://www.example.com/collections/2", Routes.url_helpers.linkable_url(@collection)
|
||||
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/", params_url(@safe_params)
|
||||
assert_equal "http://www.example.com/", Routes.url_helpers.params_url(@safe_params)
|
||||
assert_raises(ArgumentError) { params_url(@unsafe_params) }
|
||||
assert_raises(ArgumentError) { Routes.url_helpers.params_url(@unsafe_params) }
|
||||
|
||||
assert_equal "http://www.example.com/basket", symbol_url
|
||||
assert_equal "http://www.example.com/basket", Routes.url_helpers.symbol_url
|
||||
assert_equal "http://www.example.com/basket", hash_url
|
||||
assert_equal "http://www.example.com/basket", Routes.url_helpers.hash_url
|
||||
assert_equal "/admin/dashboard", array_path
|
||||
assert_equal "/admin/dashboard", Routes.url_helpers.array_path
|
||||
|
||||
assert_equal "http://www.example.com/products?page=2", options_url(page: 2)
|
||||
assert_equal "http://www.example.com/products?page=2", Routes.url_helpers.options_url(page: 2)
|
||||
assert_equal "http://www.example.com/products?size=10", defaults_url
|
||||
assert_equal "http://www.example.com/products?size=10", Routes.url_helpers.defaults_url
|
||||
assert_equal "http://www.example.com/products?size=20", defaults_url(size: 20)
|
||||
assert_equal "http://www.example.com/products?size=20", Routes.url_helpers.defaults_url(size: 20)
|
||||
end
|
||||
end
|
|
@ -263,7 +263,10 @@ module ApplicationTests
|
|||
assert_equal "WIN", last_response.body
|
||||
end
|
||||
|
||||
{ "development" => "baz", "production" => "bar" }.each do |mode, expected|
|
||||
{
|
||||
"development" => ["baz", "http://www.apple.com"],
|
||||
"production" => ["bar", "http://www.microsoft.com"]
|
||||
}.each do |mode, (expected_action, expected_url)|
|
||||
test "reloads routes when configuration is changed in #{mode}" do
|
||||
controller :foo, <<-RUBY
|
||||
class FooController < ApplicationController
|
||||
|
@ -274,12 +277,19 @@ module ApplicationTests
|
|||
def baz
|
||||
render plain: "baz"
|
||||
end
|
||||
|
||||
def custom
|
||||
render plain: custom_url
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#bar'
|
||||
get 'custom', to: 'foo#custom'
|
||||
|
||||
url_helper(:custom) { "http://www.microsoft.com" }
|
||||
end
|
||||
RUBY
|
||||
|
||||
|
@ -288,16 +298,25 @@ module ApplicationTests
|
|||
get "/foo"
|
||||
assert_equal "bar", last_response.body
|
||||
|
||||
get "/custom"
|
||||
assert_equal "http://www.microsoft.com", last_response.body
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#baz'
|
||||
get 'custom', to: 'foo#custom'
|
||||
|
||||
url_helper(:custom) { "http://www.apple.com" }
|
||||
end
|
||||
RUBY
|
||||
|
||||
sleep 0.1
|
||||
|
||||
get "/foo"
|
||||
assert_equal expected, last_response.body
|
||||
assert_equal expected_action, last_response.body
|
||||
|
||||
get "/custom"
|
||||
assert_equal expected_url, last_response.body
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -358,6 +377,10 @@ module ApplicationTests
|
|||
def index
|
||||
render plain: "foo"
|
||||
end
|
||||
|
||||
def custom
|
||||
render text: custom_url
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
|
@ -443,16 +466,19 @@ module ApplicationTests
|
|||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get ':locale/foo', to: 'foo#index', as: 'foo'
|
||||
url_helper(:microsoft) { 'http://www.microsoft.com' }
|
||||
end
|
||||
RUBY
|
||||
|
||||
get "/en/foo"
|
||||
assert_equal "foo", last_response.body
|
||||
assert_equal "/en/foo", Rails.application.routes.url_helpers.foo_path(locale: "en")
|
||||
assert_equal "http://www.microsoft.com", Rails.application.routes.url_helpers.microsoft_url
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get ':locale/bar', to: 'bar#index', as: 'foo'
|
||||
url_helper(:apple) { 'http://www.apple.com' }
|
||||
end
|
||||
RUBY
|
||||
|
||||
|
@ -464,6 +490,11 @@ module ApplicationTests
|
|||
get "/en/bar"
|
||||
assert_equal "bar", last_response.body
|
||||
assert_equal "/en/bar", Rails.application.routes.url_helpers.foo_path(locale: "en")
|
||||
assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.apple_url
|
||||
|
||||
assert_raises NoMethodError do
|
||||
assert_equal "http://www.microsoft.com", Rails.application.routes.url_helpers.microsoft_url
|
||||
end
|
||||
end
|
||||
|
||||
test "resource routing with irregular inflection" do
|
||||
|
|
Loading…
Reference in a new issue