Merge pull request #23138 from rails/custom-url-helpers-and-polymorphic-urls

Implement custom url helpers and polymorphic mapping
This commit is contained in:
Andrew White 2017-02-21 20:00:31 +00:00 committed by GitHub
commit f3d729f753
8 changed files with 788 additions and 17 deletions

@ -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,10 +271,14 @@ def handle_model(record)
[named_route, args]
end
def handle_model_call(target, model)
method, args = handle_model model
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)
record_list = list.dup
@ -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)
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
class << self
def url_for(options)
@_routes.url_for(options)
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

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