Skip to content

Commit aa9f790

Browse files
committed
Fix a NoMethodError for errors.keys in a model
Fixes for Rails/DeprecatedActiveModelErrorsMethods: - Fixed a NoMethodError on nil for `errors.keys` in a model. - Made the range calculation more exact so the replacement is easier. - Added missing correction assertions to most test cases. - Added missing test cases. - Improved test structure for future changes.
1 parent 990a786 commit aa9f790

File tree

3 files changed

+161
-12
lines changed

3 files changed

+161
-12
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#740](https://github.com/rubocop/rubocop-rails/pull/740): Fix a NoMethodError on nil for `errors.keys` in a model in Rails/DeprecatedActiveModelErrorsMethods. ([@BrianHawley][])

lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,23 +121,19 @@ def on_send(node)
121121
def autocorrect(corrector, node)
122122
receiver = node.receiver
123123

124-
if receiver.receiver.send_type? && receiver.receiver.method?(:messages)
125-
corrector.remove(receiver.receiver.loc.dot)
126-
corrector.remove(receiver.receiver.loc.selector)
127-
end
128-
129124
range = offense_range(node, receiver)
130125
replacement = replacement(node, receiver)
131126

132127
corrector.replace(range, replacement)
133128
end
134129

135130
def offense_range(node, receiver)
136-
range_between(receiver.receiver.source_range.end_pos, node.source_range.end_pos)
131+
receiver = receiver.receiver while receiver.send_type? && !receiver.method?(:errors) && receiver.receiver
132+
range_between(receiver.source_range.end_pos, node.source_range.end_pos)
137133
end
138134

139135
def replacement(node, receiver)
140-
return '.errors.attribute_names' if node.method?(:keys)
136+
return '.attribute_names' if node.method?(:keys)
141137

142138
key = receiver.first_argument.source
143139

spec/rubocop/cop/rails/deprecated_active_model_errors_methods_spec.rb

Lines changed: 157 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
RSpec.describe RuboCop::Cop::Rails::DeprecatedActiveModelErrorsMethods, :config do
44
shared_examples 'errors call with explicit receiver' do
5-
context 'when modifying errors' do
5+
context 'when accessing errors' do
66
it 'registers and corrects an offense' do
77
expect_offense(<<~RUBY, file_path)
88
user.errors[:name] << 'msg'
@@ -20,6 +20,8 @@
2020
user.errors[:name] = []
2121
^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
2222
RUBY
23+
24+
expect_no_corrections
2325
end
2426
end
2527

@@ -36,8 +38,8 @@
3638
end
3739
end
3840

39-
context 'when using `keys` method' do
40-
context 'Rails >= 6.1', :rails61 do
41+
context 'Rails >= 6.1', :rails61 do
42+
context 'when using `keys` method' do
4143
it 'registers and corrects an offense when root receiver is a variable' do
4244
expect_offense(<<~RUBY, file_path)
4345
user = create_user
@@ -62,8 +64,10 @@
6264
RUBY
6365
end
6466
end
67+
end
6568

66-
context 'Rails <= 6.0', :rails60 do
69+
context 'Rails <= 6.0', :rails60 do
70+
context 'when using `keys` method' do
6771
it 'does not register an offense when root receiver is a variable' do
6872
expect_no_offenses(<<~RUBY, file_path)
6973
user = create_user
@@ -98,6 +102,29 @@
98102
user.errors.messages[:name] = []
99103
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
100104
RUBY
105+
106+
expect_no_corrections
107+
end
108+
end
109+
110+
context 'when using `clear` method' do
111+
it 'registers and corrects an offense' do
112+
expect_offense(<<~RUBY, file_path)
113+
user.errors.messages[:name].clear
114+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
115+
RUBY
116+
117+
expect_correction(<<~RUBY)
118+
user.errors.delete(:name)
119+
RUBY
120+
end
121+
end
122+
123+
context 'when calling non-manipulative methods' do
124+
it 'does not register an offense' do
125+
expect_no_offenses(<<~RUBY, file_path)
126+
errors.messages[:name].present?
127+
RUBY
101128
end
102129
end
103130
end
@@ -116,6 +143,29 @@
116143
user.errors.details[:name] = []
117144
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
118145
RUBY
146+
147+
expect_no_corrections
148+
end
149+
end
150+
151+
context 'when using `clear` method' do
152+
it 'registers and corrects an offense' do
153+
expect_offense(<<~RUBY, file_path)
154+
user.errors.details[:name].clear
155+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
156+
RUBY
157+
158+
expect_correction(<<~RUBY)
159+
user.errors.delete(:name)
160+
RUBY
161+
end
162+
end
163+
164+
context 'when calling non-manipulative methods' do
165+
it 'does not register an offense' do
166+
expect_no_offenses(<<~RUBY, file_path)
167+
errors.details[:name].present?
168+
RUBY
119169
end
120170
end
121171
end
@@ -131,12 +181,24 @@ def expect_offense_if_model_file(code, file_path)
131181
end
132182
end
133183

134-
context 'when modifying errors' do
184+
def expect_correction_if_model_file(code, file_path)
185+
expect_correction(code) if file_path.include?('/models/')
186+
end
187+
188+
def expect_no_corrections_if_model_file(file_path)
189+
expect_no_corrections if file_path.include?('/models/')
190+
end
191+
192+
context 'when accessing errors' do
135193
it 'registers an offense for model file' do
136194
expect_offense_if_model_file(<<~RUBY, file_path)
137195
errors[:name] << 'msg'
138196
^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
139197
RUBY
198+
199+
expect_correction_if_model_file(<<~RUBY, file_path)
200+
errors.add(:name, 'msg')
201+
RUBY
140202
end
141203

142204
context 'when assigning' do
@@ -145,6 +207,46 @@ def expect_offense_if_model_file(code, file_path)
145207
errors[:name] = []
146208
^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
147209
RUBY
210+
211+
expect_no_corrections_if_model_file(file_path)
212+
end
213+
end
214+
215+
context 'when using `clear` method' do
216+
it 'registers and corrects an offense' do
217+
expect_offense_if_model_file(<<~RUBY, file_path)
218+
errors[:name].clear
219+
^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
220+
RUBY
221+
222+
expect_correction_if_model_file(<<~RUBY, file_path)
223+
errors.delete(:name)
224+
RUBY
225+
end
226+
end
227+
228+
context 'Rails >= 6.1', :rails61 do
229+
context 'when using `keys` method' do
230+
it 'registers and corrects an offense when root receiver is a variable' do
231+
expect_offense_if_model_file(<<~RUBY, file_path)
232+
errors.keys
233+
^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
234+
RUBY
235+
236+
expect_correction_if_model_file(<<~RUBY, file_path)
237+
errors.attribute_names
238+
RUBY
239+
end
240+
end
241+
end
242+
243+
context 'Rails <= 6.0', :rails60 do
244+
context 'when using `keys` method' do
245+
it 'does not register an offense' do
246+
expect_no_offenses(<<~RUBY, file_path)
247+
errors.keys
248+
RUBY
249+
end
148250
end
149251
end
150252

@@ -163,6 +265,10 @@ def expect_offense_if_model_file(code, file_path)
163265
errors.messages[:name] << 'msg'
164266
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
165267
RUBY
268+
269+
expect_correction_if_model_file(<<~RUBY, file_path)
270+
errors.add(:name, 'msg')
271+
RUBY
166272
end
167273

168274
context 'when assigning' do
@@ -171,6 +277,29 @@ def expect_offense_if_model_file(code, file_path)
171277
errors.messages[:name] = []
172278
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
173279
RUBY
280+
281+
expect_no_corrections_if_model_file(file_path)
282+
end
283+
end
284+
285+
context 'when using `clear` method' do
286+
it 'registers and corrects an offense' do
287+
expect_offense_if_model_file(<<~RUBY, file_path)
288+
errors.messages[:name].clear
289+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
290+
RUBY
291+
292+
expect_correction_if_model_file(<<~RUBY, file_path)
293+
errors.delete(:name)
294+
RUBY
295+
end
296+
end
297+
298+
context 'when using `keys` method' do
299+
it 'does not register an offense' do
300+
expect_no_offenses(<<~RUBY, file_path)
301+
errors.messages[:name].keys
302+
RUBY
174303
end
175304
end
176305

@@ -197,6 +326,29 @@ def expect_offense_if_model_file(code, file_path)
197326
errors.details[:name] = []
198327
^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
199328
RUBY
329+
330+
expect_no_corrections_if_model_file(file_path)
331+
end
332+
end
333+
334+
context 'when using `clear` method' do
335+
it 'registers and corrects an offense' do
336+
expect_offense_if_model_file(<<~RUBY, file_path)
337+
errors.details[:name].clear
338+
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid manipulating ActiveModel errors as hash directly.
339+
RUBY
340+
341+
expect_correction_if_model_file(<<~RUBY, file_path)
342+
errors.delete(:name)
343+
RUBY
344+
end
345+
end
346+
347+
context 'when using `keys` method' do
348+
it 'does not register an offense' do
349+
expect_no_offenses(<<~RUBY, file_path)
350+
errors.details[:name].keys
351+
RUBY
200352
end
201353
end
202354

0 commit comments

Comments
 (0)