-
Notifications
You must be signed in to change notification settings - Fork 21.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add routes --unused
option to detect extraneous routes.
#45701
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this should be checking There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. action_missing I'm a little hesitant about because we don't know what action action_missing is handling, so I wouldn't call a route valid if the controller has that defined. We can definitely try to denote this in the output though! |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't this?