Skip to content

Commit 0ef065a

Browse files
authored
Merge pull request rubocop#547 from mollerhoj/action-order-cop
Action Order Cop
2 parents 7220b5c + effdb0a commit 0ef065a

File tree

7 files changed

+319
-0
lines changed

7 files changed

+319
-0
lines changed

changelog/new_action_order_cop.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#547](https://github.com/rubocop/rubocop/pull/547): action order cop. ([@mollerhoj][])

config/default.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,21 @@ Rails/ActionFilter:
9595
- app/controllers/**/*.rb
9696
- app/mailers/**/*.rb
9797

98+
Rails/ActionOrder:
99+
Description: 'Enforce consistent ordering of controller actions.'
100+
Enabled: pending
101+
VersionAdded: '<<next>>'
102+
ExpectedOrder:
103+
- index
104+
- show
105+
- new
106+
- edit
107+
- create
108+
- update
109+
- destroy
110+
Include:
111+
- app/controllers/**/*.rb
112+
98113
Rails/ActiveRecordAliases:
99114
Description: >-
100115
Avoid Active Record aliases:

docs/modules/ROOT/pages/cops.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ based on the https://rails.rubystyle.guide/[Rails Style Guide].
1919
* xref:cops_rails.adoc#railsactioncontrollerflashbeforerender[Rails/ActionControllerFlashBeforeRender]
2020
* xref:cops_rails.adoc#railsactioncontrollertestcase[Rails/ActionControllerTestCase]
2121
* xref:cops_rails.adoc#railsactionfilter[Rails/ActionFilter]
22+
* xref:cops_rails.adoc#railsactionorder[Rails/ActionOrder]
2223
* xref:cops_rails.adoc#railsactiverecordaliases[Rails/ActiveRecordAliases]
2324
* xref:cops_rails.adoc#railsactiverecordcallbacksorder[Rails/ActiveRecordCallbacksOrder]
2425
* xref:cops_rails.adoc#railsactiverecordoverride[Rails/ActiveRecordOverride]

docs/modules/ROOT/pages/cops_rails.adoc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,66 @@ skip_after_filter :do_stuff
172172
| Array
173173
|===
174174

175+
== Rails/ActionOrder
176+
177+
|===
178+
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
179+
180+
| Disabled
181+
| Yes
182+
| Yes
183+
| 2.13
184+
| -
185+
|===
186+
187+
This cop enforces consistent ordering of the standard Rails RESTful
188+
controller actions.
189+
190+
The cop is configurable and can enforce any ordering of the standard
191+
actions. All other methods are ignored.
192+
193+
[source,yaml]
194+
----
195+
Rails/ActionOrder:
196+
ExpectedOrder:
197+
- index
198+
- show
199+
- new
200+
- edit
201+
- create
202+
- update
203+
- destroy
204+
----
205+
206+
=== Examples
207+
208+
[source,ruby]
209+
----
210+
# bad
211+
def index; end
212+
def destroy; end
213+
def show; end
214+
215+
# good
216+
def index; end
217+
def show; end
218+
def destroy; end
219+
----
220+
221+
=== Configurable attributes
222+
223+
|===
224+
| Name | Default value | Configurable values
225+
226+
| ExpectedOrder
227+
| `index`, `show`, `new`, `edit`, `create`, `update`, `destroy`
228+
| Array
229+
230+
| Include
231+
| `app/controllers/**/*.rb`
232+
| Array
233+
|===
234+
175235
== Rails/ActiveRecordAliases
176236

177237
|===

lib/rubocop/cop/rails/action_order.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# This cop enforces consistent ordering of the standard Rails RESTful
7+
# controller actions.
8+
#
9+
# The cop is configurable and can enforce any ordering of the standard
10+
# actions. All other methods are ignored.
11+
#
12+
# [source,yaml]
13+
# ----
14+
# Rails/ActionOrder:
15+
# ExpectedOrder:
16+
# - index
17+
# - show
18+
# - new
19+
# - edit
20+
# - create
21+
# - update
22+
# - destroy
23+
# ----
24+
#
25+
# @example
26+
# # bad
27+
# def index; end
28+
# def destroy; end
29+
# def show; end
30+
#
31+
# # good
32+
# def index; end
33+
# def show; end
34+
# def destroy; end
35+
class ActionOrder < Base
36+
extend AutoCorrector
37+
include VisibilityHelp
38+
include DefNode
39+
40+
MSG = 'Action `%<current>s` should appear before `%<previous>s`.'
41+
42+
def_node_search :action_declarations, '(def {%1} ...)'
43+
44+
def on_class(node)
45+
action_declarations(node, actions).each_cons(2) do |previous, current|
46+
next if node_visibility(current) != :public || non_public?(current)
47+
next if find_index(current) >= find_index(previous)
48+
49+
register_offense(previous, current)
50+
end
51+
end
52+
53+
private
54+
55+
def expected_order
56+
cop_config['ExpectedOrder'].map(&:to_sym)
57+
end
58+
59+
def actions
60+
@actions ||= Set.new(expected_order)
61+
end
62+
63+
def find_index(node)
64+
expected_order.find_index(node.method_name)
65+
end
66+
67+
def register_offense(previous, current)
68+
message = format(
69+
MSG,
70+
expected_order: expected_order.join(', '),
71+
previous: previous.method_name,
72+
current: current.method_name
73+
)
74+
add_offense(current, message: message) do |corrector|
75+
corrector.replace(current, previous.source)
76+
corrector.replace(previous, current.source)
77+
end
78+
end
79+
end
80+
end
81+
end
82+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
require_relative 'rails/action_controller_flash_before_render'
1212
require_relative 'rails/action_controller_test_case'
1313
require_relative 'rails/action_filter'
14+
require_relative 'rails/action_order'
1415
require_relative 'rails/active_record_aliases'
1516
require_relative 'rails/active_record_callbacks_order'
1617
require_relative 'rails/active_record_override'
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::ActionOrder, :config do
4+
it 'detects unconventional order of actions' do
5+
expect_offense(<<~RUBY)
6+
class UserController < ApplicationController
7+
def show; end
8+
def index; end
9+
^^^^^^^^^^^^^^ Action `index` should appear before `show`.
10+
end
11+
RUBY
12+
13+
expect_correction(<<~RUBY)
14+
class UserController < ApplicationController
15+
def index; end
16+
def show; end
17+
end
18+
RUBY
19+
end
20+
21+
it 'supports methods with content' do
22+
expect_offense(<<~RUBY)
23+
class UserController < ApplicationController
24+
def show
25+
@user = User.find(params[:id])
26+
end
27+
28+
def index; end
29+
^^^^^^^^^^^^^^ Action `index` should appear before `show`.
30+
end
31+
RUBY
32+
33+
expect_correction(<<~RUBY)
34+
class UserController < ApplicationController
35+
def index; end
36+
37+
def show
38+
@user = User.find(params[:id])
39+
end
40+
end
41+
RUBY
42+
end
43+
44+
it 'respects order of duplicate methods' do
45+
expect_offense(<<~RUBY)
46+
class UserController < ApplicationController
47+
def edit; end
48+
def index # first
49+
^^^^^^^^^^^^^^^^^ Action `index` should appear before `edit`.
50+
end
51+
def show; end
52+
def index # second
53+
^^^^^^^^^^^^^^^^^^ Action `index` should appear before `show`.
54+
end
55+
end
56+
RUBY
57+
58+
expect_correction(<<~RUBY)
59+
class UserController < ApplicationController
60+
def index # first
61+
end
62+
def index # second
63+
end
64+
def show; end
65+
def edit; end
66+
end
67+
RUBY
68+
end
69+
70+
it 'ignores non standard controller actions' do
71+
expect_no_offenses(<<~RUBY)
72+
class UserController < ApplicationController
73+
def index; end
74+
def commit; end
75+
def show; end
76+
end
77+
RUBY
78+
end
79+
80+
it 'does not touch protected actions' do
81+
expect_no_offenses(<<~RUBY)
82+
class UserController < ApplicationController
83+
def show; end
84+
protected
85+
def index; end
86+
end
87+
RUBY
88+
end
89+
90+
it 'does not touch inline protected actions' do
91+
expect_no_offenses(<<~RUBY)
92+
class UserController < ApplicationController
93+
def show; end
94+
protected def index; end
95+
end
96+
RUBY
97+
end
98+
99+
it 'does not touch private actions' do
100+
expect_no_offenses(<<~RUBY)
101+
class UserController < ApplicationController
102+
def show; end
103+
private
104+
def index; end
105+
end
106+
RUBY
107+
end
108+
109+
it 'does not touch inline private actions' do
110+
expect_no_offenses(<<~RUBY)
111+
class UserController < ApplicationController
112+
def show; end
113+
private def index; end
114+
end
115+
RUBY
116+
end
117+
118+
context 'with custom ordering' do
119+
it 'enforces custom order' do
120+
cop_config['ExpectedOrder'] = %w[show index new edit create update destroy]
121+
122+
expect_offense(<<~RUBY)
123+
class UserController < ApplicationController
124+
def index; end
125+
def show; end
126+
^^^^^^^^^^^^^ Action `show` should appear before `index`.
127+
end
128+
RUBY
129+
130+
expect_correction(<<~RUBY)
131+
class UserController < ApplicationController
132+
def show; end
133+
def index; end
134+
end
135+
RUBY
136+
end
137+
138+
it 'does not require all actions to be specified' do
139+
cop_config['ExpectedOrder'] = %w[show index]
140+
141+
expect_offense(<<~RUBY)
142+
class UserController < ApplicationController
143+
def index; end
144+
def edit; end
145+
def show; end
146+
^^^^^^^^^^^^^ Action `show` should appear before `index`.
147+
end
148+
RUBY
149+
150+
expect_correction(<<~RUBY)
151+
class UserController < ApplicationController
152+
def show; end
153+
def edit; end
154+
def index; end
155+
end
156+
RUBY
157+
end
158+
end
159+
end

0 commit comments

Comments
 (0)