Skip to content

Commit bf55de4

Browse files
authored
Merge pull request #759 from americodls/flash-before-render
Add `Rails/ActionControllerFlashBeforeRender` cop
2 parents 9414eb9 + 469025e commit bf55de4

File tree

5 files changed

+239
-0
lines changed

5 files changed

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

config/default.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ Rails:
6565
Enabled: true
6666
DocumentationBaseURL: https://docs.rubocop.org/rubocop-rails
6767

68+
Rails/ActionControllerFlashBeforeRender:
69+
Description: 'Use `flash.now` instead of `flash` before `render`.'
70+
StyleGuide: 'https://rails.rubystyle.guide/#flash-before-render'
71+
Reference: 'https://api.rubyonrails.org/classes/ActionController/FlashBeforeRender.html'
72+
Enabled: 'pending'
73+
SafeAutocorrect: false
74+
VersionAdded: '<<next>>'
75+
6876
Rails/ActionControllerTestCase:
6977
Description: 'Use `ActionDispatch::IntegrationTest` instead of `ActionController::TestCase`.'
7078
StyleGuide: 'https://rails.rubystyle.guide/#integration-testing'
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# Using `flash` assignment before `render` in Rails controllers will persist the message for too long.
7+
# Check https://guides.rubyonrails.org/action_controller_overview.html#flash-now
8+
#
9+
# @safety
10+
# This cop's autocorrection is unsafe because it replaces `flash` by `flash.now`.
11+
# Even though it is usually a mistake, it might be used intentionally.
12+
#
13+
# @example
14+
#
15+
# # bad
16+
# class HomeController < ApplicationController
17+
# def create
18+
# flash[:alert] = "msg"
19+
# render :index
20+
# end
21+
# end
22+
#
23+
# # good
24+
# class HomeController < ApplicationController
25+
# def create
26+
# flash.now[:alert] = "msg"
27+
# render :index
28+
# end
29+
# end
30+
#
31+
class ActionControllerFlashBeforeRender < Base
32+
extend AutoCorrector
33+
34+
MSG = 'Use `flash.now` before `render`.'
35+
36+
def_node_search :flash_assignment?, <<~PATTERN
37+
^(send (send nil? :flash) :[]= ...)
38+
PATTERN
39+
40+
def_node_search :render?, <<~PATTERN
41+
(send nil? :render ...)
42+
PATTERN
43+
44+
def_node_search :action_controller?, <<~PATTERN
45+
{
46+
(const nil? :ApplicationController)
47+
(const (const nil? :ActionController) :Base)
48+
}
49+
PATTERN
50+
51+
RESTRICT_ON_SEND = [:flash].freeze
52+
53+
def on_send(flash_node)
54+
return unless flash_assignment?(flash_node)
55+
56+
return unless followed_by_render?(flash_node)
57+
58+
return unless instance_method_or_block?(flash_node)
59+
60+
return unless inherit_action_controller_base?(flash_node)
61+
62+
add_offense(flash_node) do |corrector|
63+
corrector.replace(flash_node, 'flash.now')
64+
end
65+
end
66+
67+
private
68+
69+
def followed_by_render?(flash_node)
70+
flash_assigment_node = find_ancestor(flash_node, type: :send)
71+
context = flash_assigment_node.parent
72+
73+
context.each_child_node.any? do |node|
74+
render?(node)
75+
end
76+
end
77+
78+
def inherit_action_controller_base?(node)
79+
class_node = find_ancestor(node, type: :class)
80+
return unless class_node
81+
82+
action_controller?(class_node)
83+
end
84+
85+
def instance_method_or_block?(node)
86+
def_node = find_ancestor(node, type: :def)
87+
block_node = find_ancestor(node, type: :block)
88+
89+
def_node || block_node
90+
end
91+
92+
def find_ancestor(node, type:)
93+
node.each_ancestor(type).first
94+
end
95+
end
96+
end
97+
end
98+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require_relative 'mixin/migrations_helper'
99
require_relative 'mixin/target_rails_version'
1010

11+
require_relative 'rails/action_controller_flash_before_render'
1112
require_relative 'rails/action_controller_test_case'
1213
require_relative 'rails/action_filter'
1314
require_relative 'rails/active_record_aliases'
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::ActionControllerFlashBeforeRender, :config do
4+
context 'when using `flash` before `render`' do
5+
context 'within an instance method' do
6+
%w[ActionController::Base ApplicationController].each do |parent_class|
7+
context "within a class inherited from #{parent_class}" do
8+
it 'registers an offense and corrects' do
9+
expect_offense(<<~RUBY)
10+
class HomeController < #{parent_class}
11+
def create
12+
flash[:alert] = "msg"
13+
^^^^^ Use `flash.now` before `render`.
14+
render :index
15+
end
16+
end
17+
RUBY
18+
19+
expect_correction(<<~RUBY)
20+
class HomeController < #{parent_class}
21+
def create
22+
flash.now[:alert] = "msg"
23+
render :index
24+
end
25+
end
26+
RUBY
27+
end
28+
end
29+
end
30+
31+
context 'within a non Rails controller class' do
32+
it 'does not register an offense' do
33+
expect_no_offenses(<<~RUBY)
34+
class NonController < ApplicationRecord
35+
def create
36+
flash[:alert] = "msg"
37+
render :index
38+
end
39+
end
40+
RUBY
41+
end
42+
end
43+
end
44+
45+
context 'within a block' do
46+
%w[ActionController::Base ApplicationController].each do |parent_class|
47+
context "within a class inherited from #{parent_class}" do
48+
it 'registers an offense and corrects' do
49+
expect_offense(<<~RUBY)
50+
class HomeController < #{parent_class}
51+
before_action do
52+
flash[:alert] = "msg"
53+
^^^^^ Use `flash.now` before `render`.
54+
render :index
55+
end
56+
end
57+
RUBY
58+
59+
expect_correction(<<~RUBY)
60+
class HomeController < #{parent_class}
61+
before_action do
62+
flash.now[:alert] = "msg"
63+
render :index
64+
end
65+
end
66+
RUBY
67+
end
68+
end
69+
end
70+
71+
context 'within a non Rails controller class' do
72+
it 'does not register an offense' do
73+
expect_no_offenses(<<~RUBY)
74+
class NonController < ApplicationRecord
75+
before_action do
76+
flash[:alert] = "msg"
77+
render :index
78+
end
79+
end
80+
RUBY
81+
end
82+
end
83+
end
84+
85+
context 'within a class method' do
86+
it 'does not register an offense' do
87+
expect_no_offenses(<<~RUBY)
88+
class HomeController < ApplicationController
89+
def self.create
90+
flash[:alert] = "msg"
91+
render :index
92+
end
93+
end
94+
RUBY
95+
end
96+
end
97+
98+
context 'within a class body' do
99+
it 'does not register an offense' do
100+
expect_no_offenses(<<~RUBY)
101+
class HomeController < ApplicationController
102+
flash[:alert] = "msg"
103+
render :index
104+
end
105+
RUBY
106+
end
107+
end
108+
109+
context 'with no context' do
110+
it 'does not register an offense' do
111+
expect_no_offenses(<<~RUBY)
112+
flash[:alert] = "msg"
113+
render :index
114+
RUBY
115+
end
116+
end
117+
end
118+
119+
context 'when using `flash` before `redirect_to`' do
120+
it 'does not register an offense' do
121+
expect_no_offenses(<<~RUBY)
122+
class HomeController < ApplicationController
123+
def create
124+
flash[:alert] = "msg"
125+
redirect_to "https://www.example.com/"
126+
end
127+
end
128+
RUBY
129+
end
130+
end
131+
end

0 commit comments

Comments
 (0)