mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add routes --unused
option to detect extraneous routes.
Routes take a long time to draw. Over time, a Rails app can become slow to boot simply because of how many routes it has. This script can be used to detect routes that are drawn, but aren't actually valid. Removing routes this script detects can help speed up your app and remove dead code. Example: ``` > bin/rails routes --unused Found 2 unused routes: Prefix Verb URI Pattern Controller#Action one GET /one(.:format) action#one two GET /two(.:format) action#two ```
This commit is contained in:
parent
bfa3a5baab
commit
5613b1240a
7 changed files with 291 additions and 5 deletions
|
@ -9,8 +9,8 @@ module ActionDispatch
|
|||
|
||||
attr_reader :routes, :custom_routes, :anchored_routes
|
||||
|
||||
def initialize
|
||||
@routes = []
|
||||
def initialize(routes = [])
|
||||
@routes = routes
|
||||
@ast = nil
|
||||
@anchored_routes = []
|
||||
@custom_routes = []
|
||||
|
|
|
@ -237,6 +237,27 @@ module ActionDispatch
|
|||
"--[ Route #{index} ]".ljust(@width, "-")
|
||||
end
|
||||
end
|
||||
|
||||
class Unused < Sheet
|
||||
def header(routes)
|
||||
@buffer << <<~MSG
|
||||
Found #{routes.count} unused #{"route".pluralize(routes.count)}:
|
||||
MSG
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def no_routes(routes, filter)
|
||||
@buffer <<
|
||||
if filter.none?
|
||||
"No unused routes found."
|
||||
elsif filter.key?(:controller)
|
||||
"No unused routes found for this controller."
|
||||
elsif filter.key?(:grep)
|
||||
"No unused routes found for this grep pattern."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HtmlTableFormatter
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
* Add `routes --unused` option to detect extraneous routes.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
> bin/rails rails --unused
|
||||
|
||||
Found 2 unused routes:
|
||||
|
||||
Prefix Verb URI Pattern Controller#Action
|
||||
one GET /one(.:format) action#one
|
||||
two GET /two(.:format) action#two
|
||||
```
|
||||
|
||||
*Gannon McGibbon*
|
||||
|
||||
* Add `--parent` option to controller generator to specify parent class of job.
|
||||
|
||||
Example:
|
||||
|
|
|
@ -8,6 +8,15 @@ module Rails
|
|||
class_option :controller, aliases: "-c", desc: "Filter by a specific controller, e.g. PostsController or Admin::PostsController."
|
||||
class_option :grep, aliases: "-g", desc: "Grep routes by a specific pattern."
|
||||
class_option :expanded, type: :boolean, aliases: "-E", desc: "Print routes expanded vertically with parts explained."
|
||||
class_option :unused, type: :boolean, aliases: "-u", desc: "Print unused routes."
|
||||
|
||||
def invoke_command(*)
|
||||
if options.key?("unused")
|
||||
Rails::Command.invoke "unused_routes", ARGV
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def perform(*)
|
||||
require_application_and_environment!
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails/commands/routes/routes_command"
|
||||
|
||||
module Rails
|
||||
module Command
|
||||
class UnusedRoutesCommand < Rails::Command::Base # :nodoc:
|
||||
hide_command!
|
||||
class_option :controller, aliases: "-c", desc: "Filter by a specific controller, e.g. PostsController or Admin::PostsController."
|
||||
class_option :grep, aliases: "-g", desc: "Grep routes by a specific pattern."
|
||||
|
||||
class RouteInfo
|
||||
def initialize(route)
|
||||
requirements = route.requirements
|
||||
@controller_name = requirements[:controller]
|
||||
@action_name = requirements[:action]
|
||||
@controller_class = (@controller_name.to_s.camelize + "Controller").safe_constantize
|
||||
end
|
||||
|
||||
def unused?
|
||||
controller_class_missing? || (action_missing? && template_missing?)
|
||||
end
|
||||
|
||||
private
|
||||
def view_path(root)
|
||||
File.join(root.path, @controller_name, @action_name)
|
||||
end
|
||||
|
||||
def controller_class_missing?
|
||||
@controller_name && @controller_class.nil?
|
||||
end
|
||||
|
||||
def template_missing?
|
||||
@controller_class && @controller_class.try(:view_paths).to_a.flat_map { |path| Dir["#{view_path(path)}.*"] }.none?
|
||||
end
|
||||
|
||||
def action_missing?
|
||||
@controller_class && @controller_class.instance_methods.exclude?(@action_name.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
def perform(*)
|
||||
require_application_and_environment!
|
||||
require "action_dispatch/routing/inspector"
|
||||
|
||||
say(inspector.format(formatter, routes_filter))
|
||||
|
||||
exit(1) if routes.any?
|
||||
end
|
||||
|
||||
private
|
||||
def inspector
|
||||
ActionDispatch::Routing::RoutesInspector.new(routes)
|
||||
end
|
||||
|
||||
def routes
|
||||
@routes ||= begin
|
||||
routes = Rails.application.routes.routes.select do |route|
|
||||
RouteInfo.new(route).unused?
|
||||
end
|
||||
|
||||
ActionDispatch::Journey::Routes.new(routes)
|
||||
end
|
||||
end
|
||||
|
||||
def formatter
|
||||
ActionDispatch::Routing::ConsoleFormatter::Unused.new
|
||||
end
|
||||
|
||||
def routes_filter
|
||||
options.symbolize_keys.slice(:controller, :grep)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -251,7 +251,7 @@ rails_conductor_inbound_email_incinerate POST /rails/conductor/action_mailbox/:i
|
|||
URI | /rails/conductor/action_mailbox/inbound_emails(.:format)
|
||||
Controller#Action | rails/conductor/action_mailbox/inbound_emails#index
|
||||
--[ Route 9 ]--------------
|
||||
Prefix |
|
||||
Prefix |#{" "}
|
||||
Verb | POST
|
||||
URI | /rails/conductor/action_mailbox/inbound_emails(.:format)
|
||||
Controller#Action | rails/conductor/action_mailbox/inbound_emails#create
|
||||
|
@ -296,7 +296,7 @@ rails_conductor_inbound_email_incinerate POST /rails/conductor/action_mailbox/:i
|
|||
URI | /rails/active_storage/blobs/proxy/:signed_id/*filename(.:format)
|
||||
Controller#Action | active_storage/blobs/proxy#show
|
||||
--[ Route 18 ]-------------
|
||||
Prefix |
|
||||
Prefix |#{" "}
|
||||
Verb | GET
|
||||
URI | /rails/active_storage/blobs/:signed_id/*filename(.:format)
|
||||
Controller#Action | active_storage/blobs/redirect#show
|
||||
|
@ -311,7 +311,7 @@ rails_conductor_inbound_email_incinerate POST /rails/conductor/action_mailbox/:i
|
|||
URI | /rails/active_storage/representations/proxy/:signed_blob_id/:variation_key/*filename(.:format)
|
||||
Controller#Action | active_storage/representations/proxy#show
|
||||
--[ Route 21 ]-------------
|
||||
Prefix |
|
||||
Prefix |#{" "}
|
||||
Verb | GET
|
||||
URI | /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format)
|
||||
Controller#Action | active_storage/representations/redirect#show
|
||||
|
@ -334,6 +334,17 @@ rails_conductor_inbound_email_incinerate POST /rails/conductor/action_mailbox/:i
|
|||
# rubocop:enable Layout/TrailingWhitespace
|
||||
end
|
||||
|
||||
test "rails routes with unused option" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
end
|
||||
RUBY
|
||||
|
||||
output = run_routes_command([ "--unused" ])
|
||||
|
||||
assert_equal(output, "No unused routes found.\n")
|
||||
end
|
||||
|
||||
private
|
||||
def run_routes_command(args = [])
|
||||
rails "routes", args
|
||||
|
|
154
railties/test/commands/unused_routes_test.rb
Normal file
154
railties/test/commands/unused_routes_test.rb
Normal file
|
@ -0,0 +1,154 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "isolation/abstract_unit"
|
||||
require "rails/command"
|
||||
require "rails/commands/routes/routes_command"
|
||||
require "io/console/size"
|
||||
|
||||
class Rails::Command::UnusedRoutesTest < ActiveSupport::TestCase
|
||||
setup :build_app
|
||||
teardown :teardown_app
|
||||
|
||||
test "no results" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command
|
||||
No unused routes found.
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
test "no controller" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get "/", to: "my#index", as: :my_route
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command(allow_failure: true)
|
||||
Found 1 unused route:
|
||||
|
||||
Prefix Verb URI Pattern Controller#Action
|
||||
my_route GET / my#index
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
test "no action" do
|
||||
app_file "app/controllers/my_controller.rb", <<-RUBY
|
||||
class MyController < ActionController::Base
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get "/", to: "my#index", as: :my_route
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command(allow_failure: true)
|
||||
Found 1 unused route:
|
||||
|
||||
Prefix Verb URI Pattern Controller#Action
|
||||
my_route GET / my#index
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
test "implicit render" do
|
||||
app_file "app/controllers/my_controller.rb", <<-RUBY
|
||||
class MyController < ActionController::Base
|
||||
end
|
||||
RUBY
|
||||
|
||||
app_file "app/views/my/index.html.erb", <<-HTML
|
||||
<h1>Hello world</h1>
|
||||
HTML
|
||||
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get "/", to: "my#index", as: :my_route
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command
|
||||
No unused routes found.
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
test "multiple unused routes" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get "/one", to: "action#one"
|
||||
get "/two", to: "action#two"
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command(allow_failure: true)
|
||||
Found 2 unused routes:
|
||||
|
||||
Prefix Verb URI Pattern Controller#Action
|
||||
one GET /one(.:format) action#one
|
||||
two GET /two(.:format) action#two
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
test "filter by grep" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get "/one", to: "posts#one"
|
||||
get "/two", to: "users#two"
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command(["-g", "one"], allow_failure: true)
|
||||
Found 1 unused route:
|
||||
|
||||
Prefix Verb URI Pattern Controller#Action
|
||||
one GET /one(.:format) posts#one
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
test "filter by grep no results" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command(["-g", "one"])
|
||||
No unused routes found for this grep pattern.
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
test "filter by controller" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
get "/one", to: "posts#one"
|
||||
get "/two", to: "users#two"
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command(["-c", "posts"], allow_failure: true)
|
||||
Found 1 unused route:
|
||||
|
||||
Prefix Verb URI Pattern Controller#Action
|
||||
one GET /one(.:format) posts#one
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
test "filter by controller no results" do
|
||||
app_file "config/routes.rb", <<-RUBY
|
||||
Rails.application.routes.draw do
|
||||
end
|
||||
RUBY
|
||||
|
||||
assert_equal <<~OUTPUT, run_unused_routes_command(["-c", "posts"])
|
||||
No unused routes found for this controller.
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
private
|
||||
def run_unused_routes_command(args = [], allow_failure: false)
|
||||
rails "unused_routes", args, allow_failure: allow_failure
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue