@@ -52,7 +52,7 @@ class RedundantPresenceValidationOnBelongsTo < Base
52
52
# @example source that matches - by a foreign key
53
53
# validates :user_id, presence: true
54
54
def_node_matcher :presence_validation? , <<~PATTERN
55
- $ (
55
+ (
56
56
send nil? :validates
57
57
(sym $_)+
58
58
$(hash <$(pair (sym :presence) {true hash}) ...>)
@@ -152,27 +152,44 @@ class RedundantPresenceValidationOnBelongsTo < Base
152
152
PATTERN
153
153
154
154
def on_send ( node )
155
- validation , all_keys , options , presence = presence_validation? ( node )
156
- return unless validation
155
+ presence_validation? ( node ) do |all_keys , options , presence |
156
+ keys = non_optional_belongs_to ( node . parent , all_keys )
157
+ return if keys . none?
157
158
158
- keys = all_keys . select do |key |
159
- belongs_to = belongs_to_for ( node . parent , key )
160
- belongs_to && !optional? ( belongs_to )
159
+ add_offense_and_correct ( node , all_keys , keys , options , presence )
161
160
end
162
- return if keys . none?
161
+ end
163
162
163
+ private
164
+
165
+ def add_offense_and_correct ( node , all_keys , keys , options , presence )
164
166
add_offense ( presence , message : message_for ( keys ) ) do |corrector |
165
- remove_presence_validation ( corrector , node , options , presence )
167
+ if options . children . one? # `presence: true` is the only option
168
+ if keys == all_keys
169
+ remove_validation ( corrector , node )
170
+ else
171
+ remove_keys_from_validation ( corrector , node , keys )
172
+ end
173
+ elsif keys == all_keys
174
+ remove_presence_option ( corrector , presence )
175
+ else
176
+ extract_validation_for_keys ( corrector , node , keys , options )
177
+ end
166
178
end
167
179
end
168
180
169
- private
170
-
171
181
def message_for ( keys )
172
182
display_keys = keys . map { |key | "`#{ key } `" } . join ( '/' )
173
183
format ( MSG , association : display_keys )
174
184
end
175
185
186
+ def non_optional_belongs_to ( node , keys )
187
+ keys . select do |key |
188
+ belongs_to = belongs_to_for ( node , key )
189
+ belongs_to && !optional? ( belongs_to )
190
+ end
191
+ end
192
+
176
193
def belongs_to_for ( model_class_node , key )
177
194
if key . to_s . end_with? ( '_id' )
178
195
normalized_key = key . to_s . delete_suffix ( '_id' ) . to_sym
@@ -182,17 +199,48 @@ def belongs_to_for(model_class_node, key)
182
199
end
183
200
end
184
201
185
- def remove_presence_validation ( corrector , node , options , presence )
186
- if options . children . one?
187
- corrector . remove ( range_by_whole_lines ( node . source_range , include_final_newline : true ) )
188
- else
189
- range = range_with_surrounding_comma (
190
- range_with_surrounding_space ( range : presence . source_range , side : :left ) ,
191
- :left
202
+ def remove_validation ( corrector , node )
203
+ corrector . remove ( validation_range ( node ) )
204
+ end
205
+
206
+ def remove_keys_from_validation ( corrector , node , keys )
207
+ keys . each do |key |
208
+ key_node = node . arguments . find { |arg | arg . value == key }
209
+ key_range = range_with_surrounding_space (
210
+ range : range_with_surrounding_comma ( key_node . source_range , :right ) ,
211
+ side : :right
192
212
)
193
- corrector . remove ( range )
213
+ corrector . remove ( key_range )
194
214
end
195
215
end
216
+
217
+ def remove_presence_option ( corrector , presence )
218
+ range = range_with_surrounding_comma (
219
+ range_with_surrounding_space ( range : presence . source_range , side : :left ) ,
220
+ :left
221
+ )
222
+ corrector . remove ( range )
223
+ end
224
+
225
+ def extract_validation_for_keys ( corrector , node , keys , options )
226
+ indentation = ' ' * node . source_range . column
227
+ options_without_presence = options . children . reject { |pair | pair . key . value == :presence }
228
+ source = [
229
+ indentation ,
230
+ 'validates ' ,
231
+ keys . map ( &:inspect ) . join ( ', ' ) ,
232
+ ', ' ,
233
+ options_without_presence . map ( &:source ) . join ( ', ' ) ,
234
+ "\n "
235
+ ] . join
236
+
237
+ remove_keys_from_validation ( corrector , node , keys )
238
+ corrector . insert_after ( validation_range ( node ) , source )
239
+ end
240
+
241
+ def validation_range ( node )
242
+ range_by_whole_lines ( node . source_range , include_final_newline : true )
243
+ end
196
244
end
197
245
end
198
246
end
0 commit comments