Skip to content

Commit b1fbd49

Browse files
authored
Merge pull request #1407 from koic/add_new_rails_multiple_route_paths_cop
Add new `Rails/MultipleRoutePaths` cop
2 parents 574e4ef + f305297 commit b1fbd49

File tree

7 files changed

+192
-9
lines changed

7 files changed

+192
-9
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#1407](https://github.com/rubocop/rubocop-rails/pull/1407): Add new `Rails/MultipleRoutePaths` cop. ([@koic][])

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,12 @@ Rails/MigrationClassName:
698698
Include:
699699
- db/**/*.rb
700700

701+
Rails/MultipleRoutePaths:
702+
Description: 'Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.'
703+
Enabled: pending
704+
Severity: warning
705+
VersionAdded: '<<next>>'
706+
701707
Rails/NegateInclude:
702708
Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.'
703709
StyleGuide: 'https://rails.rubystyle.guide#exclude'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
# Common functionality for cops working with routes.
6+
module RoutesHelper
7+
extend NodePattern::Macros
8+
9+
HTTP_METHODS = %i[get post put patch delete].freeze
10+
11+
def_node_matcher :routes_draw?, <<~PATTERN
12+
(send (send _ :routes) :draw)
13+
PATTERN
14+
15+
def within_routes?(node)
16+
node.each_ancestor(:block).any? { |block| routes_draw?(block.send_node) }
17+
end
18+
end
19+
end
20+
end

lib/rubocop/cop/rails/match_route.rb

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ module Rails
2121
# match 'photos/:id', to: 'photos#show', via: :all
2222
#
2323
class MatchRoute < Base
24+
include RoutesHelper
2425
extend AutoCorrector
2526

2627
MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
2728
RESTRICT_ON_SEND = %i[match].freeze
28-
HTTP_METHODS = %i[get post put patch delete].freeze
2929

3030
def_node_matcher :match_method_call?, <<~PATTERN
3131
(send nil? :match $_ $(hash ...) ?)
@@ -60,14 +60,6 @@ def register_offense(node, http_method)
6060
end
6161
end
6262

63-
def_node_matcher :routes_draw?, <<~PATTERN
64-
(send (send _ :routes) :draw)
65-
PATTERN
66-
67-
def within_routes?(node)
68-
node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
69-
end
70-
7163
def extract_via(node)
7264
via_pair = via_pair(node)
7365
return %i[get] unless via_pair
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.
7+
#
8+
# @example
9+
#
10+
# # bad
11+
# get '/users', '/other_path', to: 'users#index'
12+
#
13+
# # good
14+
# get '/users', to: 'users#index'
15+
# get '/other_path', to: 'users#index'
16+
#
17+
class MultipleRoutePaths < Base
18+
include RoutesHelper
19+
extend AutoCorrector
20+
21+
MSG = 'Use separate routes instead of combining multiple route paths in a single route.'
22+
RESTRICT_ON_SEND = HTTP_METHODS
23+
24+
IGNORED_ARGUMENT_TYPES = %i[array hash].freeze
25+
26+
def on_send(node)
27+
return unless within_routes?(node)
28+
29+
route_paths = node.arguments.reject { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
30+
return if route_paths.count < 2
31+
32+
add_offense(node) do |corrector|
33+
corrector.replace(node, migrate_to_multiple_routes(node, route_paths))
34+
end
35+
end
36+
37+
private
38+
39+
def migrate_to_multiple_routes(node, route_paths)
40+
rest = route_paths.last.source_range.end.join(node.source_range.end).source
41+
indentation = ' ' * node.source_range.column
42+
43+
route_paths.map do |route_path|
44+
"#{node.method_name} #{route_path.source}#{rest}"
45+
end.join("\n#{indentation}")
46+
end
47+
end
48+
end
49+
end
50+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require_relative 'mixin/enforce_superclass'
88
require_relative 'mixin/index_method'
99
require_relative 'mixin/migrations_helper'
10+
require_relative 'mixin/routes_helper'
1011
require_relative 'mixin/target_rails_version'
1112

1213
require_relative 'rails/action_controller_flash_before_render'
@@ -77,6 +78,7 @@
7778
require_relative 'rails/mailer_name'
7879
require_relative 'rails/match_route'
7980
require_relative 'rails/migration_class_name'
81+
require_relative 'rails/multiple_route_paths'
8082
require_relative 'rails/negate_include'
8183
require_relative 'rails/not_null_column'
8284
require_relative 'rails/order_by_id'
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::MultipleRoutePaths, :config do
4+
it 'registers an offense when using a route with multiple string route paths' do
5+
expect_offense(<<~RUBY)
6+
Rails.application.routes.draw do
7+
get '/users', '/other_path/users', '/another_path/users'
8+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use separate routes instead of combining multiple route paths in a single route.
9+
end
10+
RUBY
11+
12+
expect_correction(<<~RUBY)
13+
Rails.application.routes.draw do
14+
get '/users'
15+
get '/other_path/users'
16+
get '/another_path/users'
17+
end
18+
RUBY
19+
end
20+
21+
it 'registers an offense when using a route with multiple route paths with option' do
22+
expect_offense(<<~RUBY)
23+
Rails.application.routes.draw do
24+
get '/users', '/other_path/users', '/another_path/users', to: 'users#index'
25+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use separate routes instead of combining multiple route paths in a single route.
26+
end
27+
RUBY
28+
29+
expect_correction(<<~RUBY)
30+
Rails.application.routes.draw do
31+
get '/users', to: 'users#index'
32+
get '/other_path/users', to: 'users#index'
33+
get '/another_path/users', to: 'users#index'
34+
end
35+
RUBY
36+
end
37+
38+
it 'registers an offense when using a route with multiple route paths with splat option' do
39+
expect_offense(<<~RUBY)
40+
Rails.application.routes.draw do
41+
get '/users', '/other_path/users', '/another_path/users', **options
42+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use separate routes instead of combining multiple route paths in a single route.
43+
end
44+
RUBY
45+
46+
expect_correction(<<~RUBY)
47+
Rails.application.routes.draw do
48+
get '/users', **options
49+
get '/other_path/users', **options
50+
get '/another_path/users', **options
51+
end
52+
RUBY
53+
end
54+
55+
it 'registers an offense when using a route with multiple symbol route paths' do
56+
expect_offense(<<~RUBY)
57+
Rails.application.routes.draw do
58+
get :resend, :generate_new_password
59+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use separate routes instead of combining multiple route paths in a single route.
60+
end
61+
RUBY
62+
63+
expect_correction(<<~RUBY)
64+
Rails.application.routes.draw do
65+
get :resend
66+
get :generate_new_password
67+
end
68+
RUBY
69+
end
70+
71+
it 'does not register an offense when using single string path method calls' do
72+
expect_no_offenses(<<~RUBY)
73+
Rails.application.routes.draw do
74+
get '/users'
75+
get '/other_path/users'
76+
get '/another_path/users'
77+
end
78+
RUBY
79+
end
80+
81+
it 'does not register an offense when using single string path with option method calls' do
82+
expect_no_offenses(<<~RUBY)
83+
Rails.application.routes.draw do
84+
get '/users', to: 'users#index'
85+
get '/other_path/users', to: 'users#index'
86+
get '/another_path/users', to: 'users#index'
87+
end
88+
RUBY
89+
end
90+
91+
it 'does not register an offense when using single string path with array literal' do
92+
expect_no_offenses(<<~RUBY)
93+
Rails.application.routes.draw do
94+
get '/other_path/users', []
95+
end
96+
RUBY
97+
end
98+
99+
it 'does not register an offense when using single route path with no arguments' do
100+
expect_no_offenses(<<~RUBY)
101+
Rails.application.routes.draw do
102+
get
103+
end
104+
RUBY
105+
end
106+
107+
it 'does not register an offense when not within routes' do
108+
expect_no_offenses(<<~RUBY)
109+
get '/users', '/other_path/users', '/another_path/users'
110+
RUBY
111+
end
112+
end

0 commit comments

Comments
 (0)