mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
move route_inspector to actionpack
this is so we can show route output in the development when we get a routing error. Railties can use features of ActionDispatch, but ActionDispatch should not depend on Railties.
This commit is contained in:
parent
fa714ec7df
commit
ef91cddb48
6 changed files with 304 additions and 10 deletions
|
@ -1,6 +1,6 @@
|
|||
require 'action_dispatch/http/request'
|
||||
require 'action_dispatch/middleware/exception_wrapper'
|
||||
require 'rails/application/route_inspector'
|
||||
require 'action_dispatch/routing/inspector'
|
||||
|
||||
|
||||
module ActionDispatch
|
||||
|
@ -9,8 +9,9 @@ module ActionDispatch
|
|||
class DebugExceptions
|
||||
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
|
||||
|
||||
def initialize(app)
|
||||
def initialize(app, routes_app = nil)
|
||||
@app = app
|
||||
@routes_app = routes_app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
|
@ -84,9 +85,10 @@ module ActionDispatch
|
|||
|
||||
private
|
||||
def formatted_routes(exception)
|
||||
return false unless @routes_app.respond_to?(:routes)
|
||||
if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)
|
||||
inspector = Rails::Application::RouteInspector.new
|
||||
inspector.format(Rails.application.routes.routes).join("\n")
|
||||
inspector = ActionDispatch::Routing::RouteInspector.new
|
||||
inspector.format(@routes_app.routes.routes).join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
121
actionpack/lib/action_dispatch/routing/inspector.rb
Normal file
121
actionpack/lib/action_dispatch/routing/inspector.rb
Normal file
|
@ -0,0 +1,121 @@
|
|||
require 'delegate'
|
||||
|
||||
module ActionDispatch
|
||||
module Routing
|
||||
class RouteWrapper < SimpleDelegator
|
||||
def endpoint
|
||||
rack_app ? rack_app.inspect : "#{controller}##{action}"
|
||||
end
|
||||
|
||||
def constraints
|
||||
requirements.except(:controller, :action)
|
||||
end
|
||||
|
||||
def rack_app(app = self.app)
|
||||
@rack_app ||= begin
|
||||
class_name = app.class.name.to_s
|
||||
if class_name == "ActionDispatch::Routing::Mapper::Constraints"
|
||||
rack_app(app.app)
|
||||
elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
|
||||
app
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def verb
|
||||
super.source.gsub(/[$^]/, '')
|
||||
end
|
||||
|
||||
def path
|
||||
super.spec.to_s
|
||||
end
|
||||
|
||||
def name
|
||||
super.to_s
|
||||
end
|
||||
|
||||
def reqs
|
||||
@reqs ||= begin
|
||||
reqs = endpoint
|
||||
reqs += " #{constraints.inspect}" unless constraints.empty?
|
||||
reqs
|
||||
end
|
||||
end
|
||||
|
||||
def controller
|
||||
requirements[:controller] || ':controller'
|
||||
end
|
||||
|
||||
def action
|
||||
requirements[:action] || ':action'
|
||||
end
|
||||
|
||||
def internal?
|
||||
path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}}
|
||||
end
|
||||
|
||||
def engine?
|
||||
rack_app && rack_app.respond_to?(:routes)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# This class is just used for displaying route information when someone
|
||||
# executes `rake routes`. People should not use this class.
|
||||
class RouteInspector # :nodoc:
|
||||
def initialize
|
||||
@engines = Hash.new
|
||||
end
|
||||
|
||||
def format(all_routes, filter = nil)
|
||||
if filter
|
||||
all_routes = all_routes.select{ |route| route.defaults[:controller] == filter }
|
||||
end
|
||||
|
||||
routes = collect_routes(all_routes)
|
||||
|
||||
formatted_routes(routes) +
|
||||
formatted_routes_for_engines
|
||||
end
|
||||
|
||||
def collect_routes(routes)
|
||||
routes = routes.collect do |route|
|
||||
RouteWrapper.new(route)
|
||||
end.reject do |route|
|
||||
route.internal?
|
||||
end.collect do |route|
|
||||
collect_engine_routes(route)
|
||||
|
||||
{:name => route.name, :verb => route.verb, :path => route.path, :reqs => route.reqs }
|
||||
end
|
||||
end
|
||||
|
||||
def collect_engine_routes(route)
|
||||
name = route.endpoint
|
||||
return unless route.engine?
|
||||
return if @engines[name]
|
||||
|
||||
routes = route.rack_app.routes
|
||||
if routes.is_a?(ActionDispatch::Routing::RouteSet)
|
||||
@engines[name] = collect_routes(routes.routes)
|
||||
end
|
||||
end
|
||||
|
||||
def formatted_routes_for_engines
|
||||
@engines.map do |name, routes|
|
||||
["\nRoutes for #{name}:"] + formatted_routes(routes)
|
||||
end.flatten
|
||||
end
|
||||
|
||||
def formatted_routes(routes)
|
||||
name_width = routes.map{ |r| r[:name].length }.max
|
||||
verb_width = routes.map{ |r| r[:verb].length }.max
|
||||
path_width = routes.map{ |r| r[:path].length }.max
|
||||
|
||||
routes.map do |r|
|
||||
"#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
170
actionpack/test/dispatch/routing/inspector_test.rb
Normal file
170
actionpack/test/dispatch/routing/inspector_test.rb
Normal file
|
@ -0,0 +1,170 @@
|
|||
require 'minitest/autorun'
|
||||
require 'action_controller'
|
||||
require 'rails/engine'
|
||||
require 'action_dispatch/routing/inspector'
|
||||
|
||||
module ActionDispatch
|
||||
module Routing
|
||||
class RouteInspectTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@set = ActionDispatch::Routing::RouteSet.new
|
||||
@inspector = ActionDispatch::Routing::RouteInspector.new
|
||||
app = ActiveSupport::OrderedOptions.new
|
||||
app.config = ActiveSupport::OrderedOptions.new
|
||||
app.config.assets = ActiveSupport::OrderedOptions.new
|
||||
app.config.assets.prefix = '/sprockets'
|
||||
Rails.stubs(:application).returns(app)
|
||||
Rails.stubs(:env).returns("development")
|
||||
end
|
||||
|
||||
def draw(&block)
|
||||
@set.draw(&block)
|
||||
@inspector.format(@set.routes)
|
||||
end
|
||||
|
||||
def test_displaying_routes_for_engines
|
||||
engine = Class.new(Rails::Engine) do
|
||||
def self.to_s
|
||||
"Blog::Engine"
|
||||
end
|
||||
end
|
||||
engine.routes.draw do
|
||||
get '/cart', :to => 'cart#show'
|
||||
end
|
||||
|
||||
output = draw do
|
||||
get '/custom/assets', :to => 'custom_assets#show'
|
||||
mount engine => "/blog", :as => "blog"
|
||||
end
|
||||
|
||||
expected = [
|
||||
"custom_assets GET /custom/assets(.:format) custom_assets#show",
|
||||
" blog /blog Blog::Engine",
|
||||
"\nRoutes for Blog::Engine:",
|
||||
"cart GET /cart(.:format) cart#show"
|
||||
]
|
||||
assert_equal expected, output
|
||||
end
|
||||
|
||||
def test_cart_inspect
|
||||
output = draw do
|
||||
get '/cart', :to => 'cart#show'
|
||||
end
|
||||
assert_equal ["cart GET /cart(.:format) cart#show"], output
|
||||
end
|
||||
|
||||
def test_inspect_shows_custom_assets
|
||||
output = draw do
|
||||
get '/custom/assets', :to => 'custom_assets#show'
|
||||
end
|
||||
assert_equal ["custom_assets GET /custom/assets(.:format) custom_assets#show"], output
|
||||
end
|
||||
|
||||
def test_inspect_routes_shows_resources_route
|
||||
output = draw do
|
||||
resources :articles
|
||||
end
|
||||
expected = [
|
||||
" articles GET /articles(.:format) articles#index",
|
||||
" POST /articles(.:format) articles#create",
|
||||
" new_article GET /articles/new(.:format) articles#new",
|
||||
"edit_article GET /articles/:id/edit(.:format) articles#edit",
|
||||
" article GET /articles/:id(.:format) articles#show",
|
||||
" PATCH /articles/:id(.:format) articles#update",
|
||||
" PUT /articles/:id(.:format) articles#update",
|
||||
" DELETE /articles/:id(.:format) articles#destroy" ]
|
||||
assert_equal expected, output
|
||||
end
|
||||
|
||||
def test_inspect_routes_shows_root_route
|
||||
output = draw do
|
||||
root :to => 'pages#main'
|
||||
end
|
||||
assert_equal ["root GET / pages#main"], output
|
||||
end
|
||||
|
||||
def test_inspect_routes_shows_dynamic_action_route
|
||||
output = draw do
|
||||
get 'api/:action' => 'api'
|
||||
end
|
||||
assert_equal [" GET /api/:action(.:format) api#:action"], output
|
||||
end
|
||||
|
||||
def test_inspect_routes_shows_controller_and_action_only_route
|
||||
output = draw do
|
||||
get ':controller/:action'
|
||||
end
|
||||
assert_equal [" GET /:controller/:action(.:format) :controller#:action"], output
|
||||
end
|
||||
|
||||
def test_inspect_routes_shows_controller_and_action_route_with_constraints
|
||||
output = draw do
|
||||
get ':controller(/:action(/:id))', :id => /\d+/
|
||||
end
|
||||
assert_equal [" GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output
|
||||
end
|
||||
|
||||
def test_rake_routes_shows_route_with_defaults
|
||||
output = draw do
|
||||
get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
|
||||
end
|
||||
assert_equal [%Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]], output
|
||||
end
|
||||
|
||||
def test_rake_routes_shows_route_with_constraints
|
||||
output = draw do
|
||||
get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
|
||||
end
|
||||
assert_equal [" GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output
|
||||
end
|
||||
|
||||
class RackApp
|
||||
def self.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
def test_rake_routes_shows_route_with_rack_app
|
||||
output = draw do
|
||||
get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
|
||||
end
|
||||
assert_equal [" GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output
|
||||
end
|
||||
|
||||
def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints
|
||||
constraint = Class.new do
|
||||
def to_s
|
||||
"( my custom constraint )"
|
||||
end
|
||||
end
|
||||
|
||||
output = draw do
|
||||
scope :constraint => constraint.new do
|
||||
mount RackApp => '/foo'
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal [" /foo #{RackApp.name} {:constraint=>( my custom constraint )}"], output
|
||||
end
|
||||
|
||||
def test_rake_routes_dont_show_app_mounted_in_assets_prefix
|
||||
output = draw do
|
||||
get '/sprockets' => RackApp
|
||||
end
|
||||
assert_no_match(/RackApp/, output.first)
|
||||
assert_no_match(/\/sprockets/, output.first)
|
||||
end
|
||||
|
||||
def test_redirect
|
||||
output = draw do
|
||||
get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" }
|
||||
get "/bar" => redirect(path: "/foo/bar", status: 307)
|
||||
get "/foobar" => redirect{ "/foo/bar" }
|
||||
end
|
||||
|
||||
assert_equal " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}", output[0]
|
||||
assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1]
|
||||
assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -267,6 +267,7 @@ module Rails
|
|||
|
||||
def default_middleware_stack #:nodoc:
|
||||
ActionDispatch::MiddlewareStack.new.tap do |middleware|
|
||||
app = self
|
||||
if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache
|
||||
require "action_dispatch/http/rack_cache"
|
||||
middleware.use ::Rack::Cache, rack_cache
|
||||
|
@ -290,11 +291,10 @@ module Rails
|
|||
middleware.use ::ActionDispatch::RequestId
|
||||
middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods
|
||||
middleware.use ::ActionDispatch::ShowExceptions, config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path)
|
||||
middleware.use ::ActionDispatch::DebugExceptions
|
||||
middleware.use ::ActionDispatch::DebugExceptions, app
|
||||
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
|
||||
|
||||
unless config.cache_classes
|
||||
app = self
|
||||
middleware.use ::ActionDispatch::Reloader, lambda { app.reload_dependencies? }
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require 'rails/application/routes_inspector'
|
||||
require 'action_dispatch/routing/inspector'
|
||||
|
||||
class Rails::InfoController < ActionController::Base
|
||||
self.view_paths = File.join(File.dirname(__FILE__), 'templates')
|
||||
|
@ -16,6 +16,7 @@ class Rails::InfoController < ActionController::Base
|
|||
|
||||
def routes
|
||||
inspector = Rails::Application::RoutesInspector.new
|
||||
inspector = ActionDispatch::Routing::RouteInspector.new
|
||||
@info = inspector.format(_routes.routes).join("\n")
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
|
||||
task :routes => :environment do
|
||||
all_routes = Rails.application.routes.routes
|
||||
require 'rails/application/routes_inspector'
|
||||
inspector = Rails::Application::RoutesInspector.new
|
||||
require 'action_dispatch/routing/inspector'
|
||||
inspector = ActionDispatch::Routing::RouteInspector.new
|
||||
puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n"
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue