Merge pull request #23138 from rails/custom-url-helpers-and-polymorphic-urls
Implement custom url helpers and polymorphic mapping
This commit is contained in:
commit
f3d729f753
@ -1,3 +1,48 @@
|
||||
* Prefer `remove_method` over `undef_method` when reloading routes
|
||||
|
||||
When `undef_method` is used it prevents access to other implementations of that
|
||||
url helper in the ancestor chain so use `remove_method` instead to restores access.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Add the `resolve` method to the routing DSL
|
||||
|
||||
This new method allows customization of the polymorphic mapping of models:
|
||||
|
||||
``` ruby
|
||||
resource :basket
|
||||
direct(class: "Basket") { [:basket] }
|
||||
```
|
||||
|
||||
``` erb
|
||||
<%= form_for @basket do |form| %>
|
||||
<!-- basket form -->
|
||||
<% end %>
|
||||
```
|
||||
|
||||
This generates the correct singular URL for the form instead of the default
|
||||
resources member url, e.g. `/basket` vs. `/basket/:id`.
|
||||
|
||||
Fixes #1769.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Add the `direct` method to the routing DSL
|
||||
|
||||
This new method allows creation of custom url helpers, e.g:
|
||||
|
||||
``` ruby
|
||||
direct(:apple) { "http://www.apple.com" }
|
||||
|
||||
>> apple_url
|
||||
=> "http://www.apple.com"
|
||||
```
|
||||
|
||||
This has the advantage of being available everywhere url helpers are available
|
||||
unlike custom url helpers defined in helper modules, etc.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Add `ActionDispatch::SystemTestCase` to Action Pack
|
||||
|
||||
Adds Capybara integration directly into Rails through Action Pack!
|
||||
|
@ -2020,6 +2020,111 @@ def concerns(*args)
|
||||
end
|
||||
end
|
||||
|
||||
module CustomUrls
|
||||
# Define custom url helpers that will be added to the application's
|
||||
# routes. This allows you override and/or replace the default behavior
|
||||
# of routing helpers, e.g:
|
||||
#
|
||||
# direct :homepage do
|
||||
# "http://www.rubyonrails.org"
|
||||
# end
|
||||
#
|
||||
# direct :commentable do |model|
|
||||
# [ model, anchor: model.dom_id ]
|
||||
# end
|
||||
#
|
||||
# direct :main do
|
||||
# { controller: 'pages', action: 'index', subdomain: 'www' }
|
||||
# end
|
||||
#
|
||||
# The return value from the block passed to `direct` 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
|
||||
#
|
||||
# NOTE: Other url helpers can be called in the block but be careful not to invoke
|
||||
# your custom url helper again otherwise it will result in a stack overflow error
|
||||
#
|
||||
# You can also specify default options that will be passed through to
|
||||
# your url helper definition, e.g:
|
||||
#
|
||||
# direct :browse, page: 1, size: 10 do |options|
|
||||
# [ :products, options.merge(params.permit(:page, :size)) ]
|
||||
# end
|
||||
#
|
||||
# NOTE: The `direct` methodn can't be used inside of a scope block such as
|
||||
# `namespace` or `scope` and will raise an error if it detects that it is.
|
||||
def direct(name, options = {}, &block)
|
||||
unless @scope.root?
|
||||
raise RuntimeError, "The direct method can't be used inside a routes scope block"
|
||||
end
|
||||
|
||||
@set.add_url_helper(name, options, &block)
|
||||
end
|
||||
|
||||
# Define custom polymorphic mappings of models to urls. This alters the
|
||||
# behavior of `polymorphic_url` and consequently the behavior of
|
||||
# `link_to` and `form_for` when passed a model instance, e.g:
|
||||
#
|
||||
# resource :basket
|
||||
#
|
||||
# resolve "Basket" do
|
||||
# [:basket]
|
||||
# end
|
||||
#
|
||||
# This will now generate '/basket' when a `Basket` instance is passed to
|
||||
# `link_to` or `form_for` instead of the standard '/baskets/:id'.
|
||||
#
|
||||
# NOTE: This custom behavior only applies to simple polymorphic urls where
|
||||
# a single model instance is passed and not more complicated forms, e.g:
|
||||
#
|
||||
# # config/routes.rb
|
||||
# resource :profile
|
||||
# namespace :admin do
|
||||
# resources :users
|
||||
# end
|
||||
#
|
||||
# resolve("User") { [:profile] }
|
||||
#
|
||||
# # app/views/application/_menu.html.erb
|
||||
# link_to 'Profile', @current_user
|
||||
# link_to 'Profile', [:admin, @current_user]
|
||||
#
|
||||
# The first `link_to` will generate '/profile' but the second will generate
|
||||
# the standard polymorphic url of '/admin/users/1'.
|
||||
#
|
||||
# You can pass options to a polymorphic mapping - the arity for the block
|
||||
# needs to be two as the instance is passed as the first argument, e.g:
|
||||
#
|
||||
# direct class: 'Basket', anchor: 'items' do |basket, options|
|
||||
# [:basket, options]
|
||||
# end
|
||||
#
|
||||
# This generates the url '/basket#items' because when the last item in an
|
||||
# array passed to `polymorphic_url` is a hash then it's treated as options
|
||||
# to the url helper that gets called.
|
||||
#
|
||||
# NOTE: The `resolve` methodn can't be used inside of a scope block such as
|
||||
# `namespace` or `scope` and will raise an error if it detects that it is.
|
||||
def resolve(*args, &block)
|
||||
unless @scope.root?
|
||||
raise RuntimeError, "The resolve method can't be used inside a routes scope block"
|
||||
end
|
||||
|
||||
options = args.extract_options!
|
||||
args = args.flatten(1)
|
||||
|
||||
args.each do |klass|
|
||||
@set.add_polymorphic_mapping(klass, options, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Scope # :nodoc:
|
||||
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
||||
:controller, :action, :path_names, :constraints,
|
||||
@ -2040,6 +2145,14 @@ def nested?
|
||||
scope_level == :nested
|
||||
end
|
||||
|
||||
def null?
|
||||
@hash.nil? && @parent.nil?
|
||||
end
|
||||
|
||||
def root?
|
||||
@parent.null?
|
||||
end
|
||||
|
||||
def resources?
|
||||
scope_level == :resources
|
||||
end
|
||||
@ -2113,6 +2226,7 @@ def initialize(set) #:nodoc:
|
||||
include Scoping
|
||||
include Concerns
|
||||
include Resources
|
||||
include CustomUrls
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -103,6 +103,10 @@ def polymorphic_url(record_or_hash_or_array, options = {})
|
||||
return polymorphic_url record, options
|
||||
end
|
||||
|
||||
if mapping = polymorphic_mapping(record_or_hash_or_array)
|
||||
return mapping.call(self, [record_or_hash_or_array, options])
|
||||
end
|
||||
|
||||
opts = options.dup
|
||||
action = opts.delete :action
|
||||
type = opts.delete(:routing_type) || :url
|
||||
@ -123,6 +127,10 @@ def polymorphic_path(record_or_hash_or_array, options = {})
|
||||
return polymorphic_path record, options
|
||||
end
|
||||
|
||||
if mapping = polymorphic_mapping(record_or_hash_or_array)
|
||||
return mapping.call(self, [record_or_hash_or_array, options], only_path: true)
|
||||
end
|
||||
|
||||
opts = options.dup
|
||||
action = opts.delete :action
|
||||
type = :path
|
||||
@ -156,6 +164,14 @@ def polymorphic_path_for_action(action, record_or_hash, options)
|
||||
polymorphic_path(record_or_hash, options.merge(action: action))
|
||||
end
|
||||
|
||||
def polymorphic_mapping(record)
|
||||
if record.respond_to?(:to_model)
|
||||
_routes.polymorphic_mappings[record.to_model.model_name.name]
|
||||
else
|
||||
_routes.polymorphic_mappings[record.class.name]
|
||||
end
|
||||
end
|
||||
|
||||
class HelperMethodBuilder # :nodoc:
|
||||
CACHE = { "path" => {}, "url" => {} }
|
||||
|
||||
@ -255,9 +271,13 @@ def handle_model(record)
|
||||
[named_route, args]
|
||||
end
|
||||
|
||||
def handle_model_call(target, model)
|
||||
method, args = handle_model model
|
||||
target.send(method, *args)
|
||||
def handle_model_call(target, record)
|
||||
if mapping = polymorphic_mapping(target, record)
|
||||
mapping.call(target, [record], only_path: suffix == "path")
|
||||
else
|
||||
method, args = handle_model(record)
|
||||
target.send(method, *args)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_list(list)
|
||||
@ -303,6 +323,14 @@ def handle_list(list)
|
||||
|
||||
private
|
||||
|
||||
def polymorphic_mapping(target, record)
|
||||
if record.respond_to?(:to_model)
|
||||
target._routes.polymorphic_mappings[record.to_model.model_name.name]
|
||||
else
|
||||
target._routes.polymorphic_mappings[record.class.name]
|
||||
end
|
||||
end
|
||||
|
||||
def get_method_for_class(klass)
|
||||
name = @key_strategy.call klass.model_name
|
||||
get_method_for_string name
|
||||
|
@ -73,6 +73,7 @@ def initialize
|
||||
@routes = {}
|
||||
@path_helpers = Set.new
|
||||
@url_helpers = Set.new
|
||||
@custom_helpers = Set.new
|
||||
@url_helpers_module = Module.new
|
||||
@path_helpers_module = Module.new
|
||||
end
|
||||
@ -88,16 +89,30 @@ def helper_names
|
||||
|
||||
def clear!
|
||||
@path_helpers.each do |helper|
|
||||
@path_helpers_module.send :undef_method, helper
|
||||
@path_helpers_module.send :remove_method, helper
|
||||
end
|
||||
|
||||
@url_helpers.each do |helper|
|
||||
@url_helpers_module.send :undef_method, helper
|
||||
@url_helpers_module.send :remove_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 :remove_method, path_name
|
||||
end
|
||||
|
||||
if @url_helpers_module.method_defined?(url_name)
|
||||
@url_helpers_module.send :remove_method, url_name
|
||||
end
|
||||
end
|
||||
|
||||
@routes.clear
|
||||
@path_helpers.clear
|
||||
@url_helpers.clear
|
||||
@custom_helpers.clear
|
||||
end
|
||||
|
||||
def add(name, route)
|
||||
@ -143,6 +158,23 @@ def length
|
||||
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|
|
||||
helper.call(self, args, only_path: true)
|
||||
end
|
||||
end
|
||||
|
||||
@url_helpers_module.module_eval do
|
||||
define_method(:"#{name}_url") do |*args|
|
||||
helper.call(self, args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class UrlHelper
|
||||
def self.create(route, options, route_name, url_strategy)
|
||||
if optimize_helper?(route)
|
||||
@ -305,7 +337,7 @@ def define_url_helper(mod, route, name, opts, route_key, url_strategy)
|
||||
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
|
||||
attr_accessor :disable_clear_and_finalize, :resources_path_names
|
||||
attr_accessor :default_url_options
|
||||
attr_reader :env_key
|
||||
attr_reader :env_key, :polymorphic_mappings
|
||||
|
||||
alias :routes :set
|
||||
|
||||
@ -347,6 +379,7 @@ def initialize(config = DEFAULT_CONFIG)
|
||||
@set = Journey::Routes.new
|
||||
@router = Journey::Router.new @set
|
||||
@formatter = Journey::Formatter.new self
|
||||
@polymorphic_mappings = {}
|
||||
end
|
||||
|
||||
def eager_load!
|
||||
@ -408,6 +441,7 @@ def clear!
|
||||
named_routes.clear
|
||||
set.clear
|
||||
formatter.clear
|
||||
@polymorphic_mappings.clear
|
||||
@prepend.each { |blk| eval_block(blk) }
|
||||
end
|
||||
|
||||
@ -452,17 +486,42 @@ def url_helpers(supports_path = true)
|
||||
|
||||
# Define url_for in the singleton level so one can do:
|
||||
# Rails.application.routes.url_helpers.url_for(args)
|
||||
@_routes = routes
|
||||
class << self
|
||||
def url_for(options)
|
||||
@_routes.url_for(options)
|
||||
proxy_class = Class.new do
|
||||
include UrlFor
|
||||
include routes.named_routes.path_helpers_module
|
||||
include routes.named_routes.url_helpers_module
|
||||
|
||||
attr_reader :_routes
|
||||
|
||||
def initialize(routes)
|
||||
@_routes = routes
|
||||
end
|
||||
|
||||
def optimize_routes_generation?
|
||||
@_routes.optimize_routes_generation?
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :_routes
|
||||
@_proxy = proxy_class.new(routes)
|
||||
|
||||
class << self
|
||||
def url_for(options)
|
||||
@_proxy.url_for(options)
|
||||
end
|
||||
|
||||
def optimize_routes_generation?
|
||||
@_proxy.optimize_routes_generation?
|
||||
end
|
||||
|
||||
def polymorphic_url(record_or_hash_or_array, options = {})
|
||||
@_proxy.polymorphic_url(record_or_hash_or_array, options)
|
||||
end
|
||||
|
||||
def polymorphic_path(record_or_hash_or_array, options = {})
|
||||
@_proxy.polymorphic_path(record_or_hash_or_array, options)
|
||||
end
|
||||
|
||||
def _routes; @_proxy._routes; end
|
||||
def url_options; {}; end
|
||||
end
|
||||
|
||||
@ -537,6 +596,56 @@ def add_route(mapping, path_ast, name, anchor)
|
||||
route
|
||||
end
|
||||
|
||||
def add_polymorphic_mapping(klass, options, &block)
|
||||
@polymorphic_mappings[klass] = CustomUrlHelper.new(klass, options, &block)
|
||||
end
|
||||
|
||||
def add_url_helper(name, options, &block)
|
||||
named_routes.add_url_helper(name, options, &block)
|
||||
end
|
||||
|
||||
class CustomUrlHelper
|
||||
attr_reader :name, :defaults, :block
|
||||
|
||||
def initialize(name, defaults, &block)
|
||||
@name = name
|
||||
@defaults = defaults
|
||||
@block = block
|
||||
end
|
||||
|
||||
def call(t, args, outer_options = {})
|
||||
options = args.extract_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 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)))
|
||||
else
|
||||
t.url_for([url_options, outer_options])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def eval_block(t, args, options)
|
||||
t.instance_exec(*args, merge_defaults(options), &block)
|
||||
end
|
||||
|
||||
def merge_defaults(options)
|
||||
defaults ? defaults.merge(options) : options
|
||||
end
|
||||
end
|
||||
|
||||
class Generator
|
||||
PARAMETERIZE = lambda do |name, value|
|
||||
if name == :controller
|
||||
|
291
actionpack/test/dispatch/routing/custom_url_helpers_test.rb
Normal file
291
actionpack/test/dispatch/routing/custom_url_helpers_test.rb
Normal file
@ -0,0 +1,291 @@
|
||||
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
|
||||
|
||||
class Model
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
|
||||
attr_reader :id
|
||||
|
||||
def initialize(id = nil)
|
||||
@id = id
|
||||
end
|
||||
|
||||
remove_method :model_name
|
||||
def model_name
|
||||
@_model_name ||= ActiveModel::Name.new(self.class, nil, self.class.name.demodulize)
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class Basket < Model; end
|
||||
class User < Model; end
|
||||
class Video < Model; end
|
||||
|
||||
class Article
|
||||
attr_reader :id
|
||||
|
||||
def self.name
|
||||
"Article"
|
||||
end
|
||||
|
||||
def initialize(id)
|
||||
@id = id
|
||||
end
|
||||
end
|
||||
|
||||
class Page
|
||||
attr_reader :id
|
||||
|
||||
def self.name
|
||||
super.demodulize
|
||||
end
|
||||
|
||||
def initialize(id)
|
||||
@id = id
|
||||
end
|
||||
end
|
||||
|
||||
class CategoryPage < Page; end
|
||||
class ProductPage < Page; 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
|
||||
get "/posts/:id", to: "posts#show", as: :post
|
||||
get "/profile", to: "users#profile", as: :profile
|
||||
get "/media/:id", to: "media#show", as: :media
|
||||
get "/pages/:id", to: "pages#show", as: :page
|
||||
|
||||
resources :categories, :collections, :products
|
||||
|
||||
namespace :admin do
|
||||
get "/dashboard", to: "dashboard#index"
|
||||
end
|
||||
|
||||
direct(:website) { "http://www.rubyonrails.org" }
|
||||
direct("string") { "http://www.rubyonrails.org" }
|
||||
direct(:helper) { basket_url }
|
||||
direct(:linkable) { |linkable| [:"#{linkable.linkable_type}", { id: linkable.id }] }
|
||||
direct(:params) { |params| params }
|
||||
direct(:symbol) { :basket }
|
||||
direct(:hash) { { controller: "basket", action: "show" } }
|
||||
direct(:array) { [:admin, :dashboard] }
|
||||
direct(:options) { |options| [:products, options] }
|
||||
direct(:defaults, size: 10) { |options| [:products, options] }
|
||||
|
||||
resolve("Article") { |article| [:post, { id: article.id }] }
|
||||
resolve("Basket") { |basket| [:basket] }
|
||||
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 }] }
|
||||
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")
|
||||
@basket = Basket.new
|
||||
@user = User.new
|
||||
@video = Video.new("4")
|
||||
@article = Article.new("5")
|
||||
@page = Page.new("6")
|
||||
@category_page = CategoryPage.new("7")
|
||||
@product_page = ProductPage.new("8")
|
||||
@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_direct_paths
|
||||
assert_equal "http://www.rubyonrails.org", website_path
|
||||
assert_equal "http://www.rubyonrails.org", 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 "http://www.example.com/basket", helper_url
|
||||
assert_equal "http://www.example.com/basket", Routes.url_helpers.helper_url
|
||||
|
||||
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_direct_urls
|
||||
assert_equal "http://www.rubyonrails.org", website_url
|
||||
assert_equal "http://www.rubyonrails.org", Routes.url_helpers.website_url
|
||||
|
||||
assert_equal "http://www.rubyonrails.org", string_url
|
||||
assert_equal "http://www.rubyonrails.org", Routes.url_helpers.string_url
|
||||
|
||||
assert_equal "http://www.example.com/basket", helper_url
|
||||
assert_equal "http://www.example.com/basket", Routes.url_helpers.helper_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 "http://www.example.com/admin/dashboard", array_url
|
||||
assert_equal "http://www.example.com/admin/dashboard", Routes.url_helpers.array_url
|
||||
|
||||
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
|
||||
|
||||
def test_resolve_paths
|
||||
assert_equal "/basket", polymorphic_path(@basket)
|
||||
assert_equal "/basket", Routes.url_helpers.polymorphic_path(@basket)
|
||||
|
||||
assert_equal "/profile#details", polymorphic_path(@user)
|
||||
assert_equal "/profile#details", Routes.url_helpers.polymorphic_path(@user)
|
||||
|
||||
assert_equal "/profile#password", polymorphic_path(@user, anchor: "password")
|
||||
assert_equal "/profile#password", Routes.url_helpers.polymorphic_path(@user, anchor: "password")
|
||||
|
||||
assert_equal "/media/4", polymorphic_path(@video)
|
||||
assert_equal "/media/4", Routes.url_helpers.polymorphic_path(@video)
|
||||
assert_equal "/media/4", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @video)
|
||||
|
||||
assert_equal "/posts/5", polymorphic_path(@article)
|
||||
assert_equal "/posts/5", Routes.url_helpers.polymorphic_path(@article)
|
||||
assert_equal "/posts/5", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @article)
|
||||
|
||||
assert_equal "/pages/6", polymorphic_path(@page)
|
||||
assert_equal "/pages/6", Routes.url_helpers.polymorphic_path(@page)
|
||||
assert_equal "/pages/6", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @page)
|
||||
|
||||
assert_equal "/pages/7", polymorphic_path(@category_page)
|
||||
assert_equal "/pages/7", Routes.url_helpers.polymorphic_path(@category_page)
|
||||
assert_equal "/pages/7", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @category_page)
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
def test_resolve_urls
|
||||
assert_equal "http://www.example.com/basket", polymorphic_url(@basket)
|
||||
assert_equal "http://www.example.com/basket", Routes.url_helpers.polymorphic_url(@basket)
|
||||
assert_equal "http://www.example.com/basket", polymorphic_url(@basket)
|
||||
assert_equal "http://www.example.com/basket", Routes.url_helpers.polymorphic_url(@basket)
|
||||
|
||||
assert_equal "http://www.example.com/profile#details", polymorphic_url(@user)
|
||||
assert_equal "http://www.example.com/profile#details", Routes.url_helpers.polymorphic_url(@user)
|
||||
|
||||
assert_equal "http://www.example.com/profile#password", polymorphic_url(@user, anchor: "password")
|
||||
assert_equal "http://www.example.com/profile#password", Routes.url_helpers.polymorphic_url(@user, anchor: "password")
|
||||
|
||||
assert_equal "http://www.example.com/media/4", polymorphic_url(@video)
|
||||
assert_equal "http://www.example.com/media/4", Routes.url_helpers.polymorphic_url(@video)
|
||||
assert_equal "http://www.example.com/media/4", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @video)
|
||||
|
||||
assert_equal "http://www.example.com/posts/5", polymorphic_url(@article)
|
||||
assert_equal "http://www.example.com/posts/5", Routes.url_helpers.polymorphic_url(@article)
|
||||
assert_equal "http://www.example.com/posts/5", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @article)
|
||||
|
||||
assert_equal "http://www.example.com/pages/6", polymorphic_url(@page)
|
||||
assert_equal "http://www.example.com/pages/6", Routes.url_helpers.polymorphic_url(@page)
|
||||
assert_equal "http://www.example.com/pages/6", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @page)
|
||||
|
||||
assert_equal "http://www.example.com/pages/7", polymorphic_url(@category_page)
|
||||
assert_equal "http://www.example.com/pages/7", Routes.url_helpers.polymorphic_url(@category_page)
|
||||
assert_equal "http://www.example.com/pages/7", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @category_page)
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
def test_defining_direct_inside_a_scope_raises_runtime_error
|
||||
routes = ActionDispatch::Routing::RouteSet.new
|
||||
|
||||
assert_raises RuntimeError do
|
||||
routes.draw do
|
||||
namespace :admin do
|
||||
direct(:rubyonrails) { "http://www.rubyonrails.org" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_defining_resolve_inside_a_scope_raises_runtime_error
|
||||
routes = ActionDispatch::Routing::RouteSet.new
|
||||
|
||||
assert_raises RuntimeError do
|
||||
routes.draw do
|
||||
namespace :admin do
|
||||
resolve("User") { "/profile" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -124,6 +124,10 @@ def rendered_views
|
||||
@_rendered_views ||= RenderedViewsCollection.new
|
||||
end
|
||||
|
||||
def _routes
|
||||
@controller._routes if @controller.respond_to?(:_routes)
|
||||
end
|
||||
|
||||
# Need to experiment if this priority is the best one: rendered => output_buffer
|
||||
class RenderedViewsCollection
|
||||
def initialize
|
||||
@ -258,10 +262,6 @@ def view_assigns
|
||||
end]
|
||||
end
|
||||
|
||||
def _routes
|
||||
@controller._routes if @controller.respond_to?(:_routes)
|
||||
end
|
||||
|
||||
def method_missing(selector, *args)
|
||||
begin
|
||||
routes = @controller.respond_to?(:_routes) && @controller._routes
|
||||
|
@ -577,6 +577,7 @@ def test_field_named_field
|
||||
end
|
||||
ensure
|
||||
ActiveRecord::Base.connection.drop_table :testings rescue nil
|
||||
ActiveRecord::Base.clear_cache!
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -263,7 +263,10 @@ def index
|
||||
assert_equal "WIN", last_response.body
|
||||
end
|
||||
|
||||
{ "development" => "baz", "production" => "bar" }.each do |mode, expected|
|
||||
{
|
||||
"development" => ["baz", "http://www.apple.com", "/dashboard"],
|
||||
"production" => ["bar", "http://www.microsoft.com", "/profile"]
|
||||
}.each do |mode, (expected_action, expected_url, expected_mapping)|
|
||||
test "reloads routes when configuration is changed in #{mode}" do
|
||||
controller :foo, <<-RUBY
|
||||
class FooController < ApplicationController
|
||||
@ -274,12 +277,40 @@ def bar
|
||||
def baz
|
||||
render plain: "baz"
|
||||
end
|
||||
|
||||
def custom
|
||||
render plain: custom_url
|
||||
end
|
||||
|
||||
def mapping
|
||||
render plain: url_for(User.new)
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "app/models/user.rb", <<-RUBY
|
||||
class User
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
|
||||
def model_name
|
||||
@_model_name ||= ActiveModel::Name.new(self.class, nil, "User")
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#bar'
|
||||
get 'custom', to: 'foo#custom'
|
||||
get 'mapping', to: 'foo#mapping'
|
||||
|
||||
direct(:custom) { "http://www.microsoft.com" }
|
||||
resolve("User") { "/profile" }
|
||||
end
|
||||
RUBY
|
||||
|
||||
@ -288,16 +319,33 @@ def baz
|
||||
get "/foo"
|
||||
assert_equal "bar", last_response.body
|
||||
|
||||
get "/custom"
|
||||
assert_equal "http://www.microsoft.com", last_response.body
|
||||
|
||||
get "/mapping"
|
||||
assert_equal "/profile", last_response.body
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#baz'
|
||||
get 'custom', to: 'foo#custom'
|
||||
get 'mapping', to: 'foo#mapping'
|
||||
|
||||
direct(:custom) { "http://www.apple.com" }
|
||||
resolve("User") { "/dashboard" }
|
||||
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
|
||||
|
||||
get "/mapping"
|
||||
assert_equal expected_mapping, last_response.body
|
||||
end
|
||||
end
|
||||
|
||||
@ -358,6 +406,14 @@ class FooController < ApplicationController
|
||||
def index
|
||||
render plain: "foo"
|
||||
end
|
||||
|
||||
def custom
|
||||
render plain: custom_url
|
||||
end
|
||||
|
||||
def mapping
|
||||
render plain: url_for(User.new)
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
@ -369,6 +425,21 @@ def index
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "app/models/user.rb", <<-RUBY
|
||||
class User
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
|
||||
def model_name
|
||||
@_model_name ||= ActiveModel::Name.new(self.class, nil, "User")
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#index'
|
||||
@ -389,6 +460,12 @@ def index
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#index'
|
||||
get 'bar', to: 'bar#index'
|
||||
|
||||
get 'custom', to: 'foo#custom'
|
||||
direct(:custom) { 'http://www.apple.com' }
|
||||
|
||||
get 'mapping', to: 'foo#mapping'
|
||||
resolve('User') { '/profile' }
|
||||
end
|
||||
RUBY
|
||||
|
||||
@ -402,6 +479,14 @@ def index
|
||||
assert_equal "bar", last_response.body
|
||||
assert_equal "/bar", Rails.application.routes.url_helpers.bar_path
|
||||
|
||||
get "/custom"
|
||||
assert_equal "http://www.apple.com", last_response.body
|
||||
assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.custom_url
|
||||
|
||||
get "/mapping"
|
||||
assert_equal "/profile", last_response.body
|
||||
assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new)
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get 'foo', to: 'foo#index'
|
||||
@ -419,6 +504,18 @@ def index
|
||||
assert_raises NoMethodError do
|
||||
assert_equal "/bar", Rails.application.routes.url_helpers.bar_path
|
||||
end
|
||||
|
||||
get "/custom"
|
||||
assert_equal 404, last_response.status
|
||||
assert_raises NoMethodError do
|
||||
assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.custom_url
|
||||
end
|
||||
|
||||
get "/mapping"
|
||||
assert_equal 404, last_response.status
|
||||
assert_raises NoMethodError do
|
||||
assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new)
|
||||
end
|
||||
end
|
||||
|
||||
test "named routes are cleared when reloading" do
|
||||
@ -440,19 +537,41 @@ def index
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "app/models/user.rb", <<-RUBY
|
||||
class User
|
||||
extend ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
|
||||
def model_name
|
||||
@_model_name ||= ActiveModel::Name.new(self.class, nil, "User")
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get ':locale/foo', to: 'foo#index', as: 'foo'
|
||||
get 'users', to: 'foo#users', as: 'users'
|
||||
direct(:microsoft) { 'http://www.microsoft.com' }
|
||||
resolve('User') { '/profile' }
|
||||
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
|
||||
assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new)
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get ':locale/bar', to: 'bar#index', as: 'foo'
|
||||
get 'users', to: 'foo#users', as: 'users'
|
||||
direct(:apple) { 'http://www.apple.com' }
|
||||
end
|
||||
RUBY
|
||||
|
||||
@ -464,6 +583,12 @@ def index
|
||||
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_equal "/users", Rails.application.routes.url_helpers.polymorphic_path(User.new)
|
||||
|
||||
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
|
||||
@ -493,5 +618,63 @@ def index
|
||||
get "/yazilar"
|
||||
assert_equal 200, last_response.status
|
||||
end
|
||||
|
||||
test "reloading routes removes methods and doesn't undefine them" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get '/url', to: 'url#index'
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "app/models/url_helpers.rb", <<-RUBY
|
||||
module UrlHelpers
|
||||
def foo_path
|
||||
"/foo"
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "app/models/context.rb", <<-RUBY
|
||||
class Context
|
||||
include UrlHelpers
|
||||
include Rails.application.routes.url_helpers
|
||||
end
|
||||
RUBY
|
||||
|
||||
controller "url", <<-RUBY
|
||||
class UrlController < ApplicationController
|
||||
def index
|
||||
context = Context.new
|
||||
render plain: context.foo_path
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
|
||||
get "/url"
|
||||
assert_equal "/foo", last_response.body
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get '/url', to: 'url#index'
|
||||
get '/bar', to: 'foo#index', as: 'foo'
|
||||
end
|
||||
RUBY
|
||||
|
||||
Rails.application.reload_routes!
|
||||
|
||||
get "/url"
|
||||
assert_equal "/bar", last_response.body
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get '/url', to: 'url#index'
|
||||
end
|
||||
RUBY
|
||||
|
||||
Rails.application.reload_routes!
|
||||
|
||||
get "/url"
|
||||
assert_equal "/foo", last_response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user