sevencop 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/Gemfile.lock +13 -1
- data/README.md +17 -4
- data/config/default.yml +134 -0
- data/lib/rubocop/cop/sevencop/factory_bot_association_option.rb +1 -1
- data/lib/rubocop/cop/sevencop/factory_bot_association_style.rb +31 -31
- data/lib/rubocop/cop/sevencop/rails_inferred_spec_type.rb +1 -1
- data/lib/rubocop/cop/sevencop/rails_migration_add_check_constraint.rb +111 -0
- data/lib/rubocop/cop/sevencop/rails_migration_add_column_with_default_value.rb +229 -0
- data/lib/rubocop/cop/sevencop/rails_migration_add_foreign_key.rb +166 -0
- data/lib/rubocop/cop/sevencop/rails_migration_add_index_concurrently.rb +164 -0
- data/lib/rubocop/cop/sevencop/rails_migration_batch_in_batches.rb +95 -0
- data/lib/rubocop/cop/sevencop/rails_migration_batch_in_transaction.rb +83 -0
- data/lib/rubocop/cop/sevencop/rails_migration_batch_with_throttling.rb +108 -0
- data/lib/rubocop/cop/sevencop/rails_migration_change_column.rb +113 -0
- data/lib/rubocop/cop/sevencop/rails_migration_change_column_null.rb +128 -0
- data/lib/rubocop/cop/sevencop/rails_migration_create_table_force.rb +89 -0
- data/lib/rubocop/cop/sevencop/rails_migration_jsonb.rb +131 -0
- data/lib/rubocop/cop/sevencop/rails_migration_remove_column.rb +258 -0
- data/lib/rubocop/cop/sevencop/rails_migration_rename_column.rb +81 -0
- data/lib/rubocop/cop/sevencop/rails_migration_rename_table.rb +79 -0
- data/lib/rubocop/cop/sevencop/rails_migration_reserved_word_mysql.rb +2 -19
- data/lib/rubocop/cop/sevencop/rails_migration_unique_index_columns_count.rb +92 -0
- data/lib/sevencop/config_loader.rb +11 -10
- data/lib/sevencop/cop_concerns/batch_processing.rb +32 -0
- data/lib/sevencop/cop_concerns/column_type_method.rb +26 -0
- data/lib/sevencop/cop_concerns/disable_ddl_transaction.rb +49 -0
- data/lib/sevencop/cop_concerns.rb +3 -0
- data/lib/sevencop/rubocop_extension.rb +6 -1
- data/lib/sevencop/version.rb +1 -1
- data/lib/sevencop.rb +15 -0
- data/sevencop.gemspec +1 -0
- metadata +34 -2
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sevencop
|
6
|
+
# Add the column without a default value then change the default.
|
7
|
+
#
|
8
|
+
# In earlier versions of Postgres, MySQL, and MariaDB,
|
9
|
+
# adding a column with a default value to an existing table causes the entire table to be rewritten.
|
10
|
+
# During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
11
|
+
#
|
12
|
+
# @safety
|
13
|
+
# Only meaningful in earlier versions of Postgres, MySQL, and MariaDB.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# # bad
|
17
|
+
# class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
|
18
|
+
# def change
|
19
|
+
# add_column :users, :some_column, :string, default: 'some value'
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
|
25
|
+
# def change
|
26
|
+
# add_column :users, :some_column, :string
|
27
|
+
# change_column_default :users, :some_column, 'some value'
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
class RailsMigrationAddColumnWithDefaultValue < RuboCop::Cop::Base
|
31
|
+
extend AutoCorrector
|
32
|
+
|
33
|
+
include RangeHelp
|
34
|
+
include ::Sevencop::CopConcerns::ColumnTypeMethod
|
35
|
+
|
36
|
+
MSG = 'Add the column without a default value then change the default.'
|
37
|
+
|
38
|
+
RESTRICT_ON_SEND = [
|
39
|
+
:add_column,
|
40
|
+
*COLUMN_TYPE_METHOD_NAMES
|
41
|
+
].freeze
|
42
|
+
|
43
|
+
# @param node [RuboCop::AST::SendNode]
|
44
|
+
# @return [void]
|
45
|
+
def on_send(node)
|
46
|
+
return unless target_method?(node)
|
47
|
+
|
48
|
+
default_option_node = non_nil_default_option_node_from(node)
|
49
|
+
return unless default_option_node
|
50
|
+
|
51
|
+
add_offense(default_option_node) do |corrector|
|
52
|
+
autocorrect(
|
53
|
+
corrector,
|
54
|
+
default_option_node: default_option_node,
|
55
|
+
send_node: node
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @!method add_column?(node)
|
63
|
+
# @param node [RuboCop::AST::SendNode]
|
64
|
+
# @return [Boolean]
|
65
|
+
def_node_matcher :add_column?, <<~PATTERN
|
66
|
+
(send
|
67
|
+
nil?
|
68
|
+
:add_column
|
69
|
+
...
|
70
|
+
)
|
71
|
+
PATTERN
|
72
|
+
|
73
|
+
# @!method column_type_method?(node)
|
74
|
+
# @param node [RuboCop::AST::SendNode]
|
75
|
+
# @return [Boolean]
|
76
|
+
def_node_matcher :column_type_method?, <<~PATTERN
|
77
|
+
(send
|
78
|
+
lvar
|
79
|
+
COLUMN_TYPE_METHOD_NAMES
|
80
|
+
...
|
81
|
+
)
|
82
|
+
PATTERN
|
83
|
+
|
84
|
+
# @!method non_nil_default_option_node_from(node)
|
85
|
+
# @param node [RuboCop::AST::SendNode]
|
86
|
+
# @return [RuboCop::AST::PairNode, nil]
|
87
|
+
def_node_matcher :non_nil_default_option_node_from, <<~PATTERN
|
88
|
+
(send
|
89
|
+
_
|
90
|
+
_
|
91
|
+
...
|
92
|
+
(hash
|
93
|
+
<
|
94
|
+
$(pair
|
95
|
+
(sym :default)
|
96
|
+
!nil
|
97
|
+
)
|
98
|
+
>
|
99
|
+
...
|
100
|
+
)
|
101
|
+
)
|
102
|
+
PATTERN
|
103
|
+
|
104
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
105
|
+
# @param default_option_node [RuboCop::AST::PairNode]
|
106
|
+
# @param send_node [RuboCop::AST::SendNode]
|
107
|
+
# @return [void]
|
108
|
+
def autocorrect(
|
109
|
+
corrector,
|
110
|
+
default_option_node:,
|
111
|
+
send_node:
|
112
|
+
)
|
113
|
+
remove_pair(
|
114
|
+
corrector,
|
115
|
+
default_option_node
|
116
|
+
)
|
117
|
+
insert_change_column_default(
|
118
|
+
corrector,
|
119
|
+
default_option_node: default_option_node,
|
120
|
+
send_node: send_node
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
# @param node [RuboCop::AST::SendNode]
|
125
|
+
# @return [RuboCop::AST::SymNode]
|
126
|
+
def find_column_node_from(node)
|
127
|
+
case node.method_name
|
128
|
+
when :add_column
|
129
|
+
node.arguments[1]
|
130
|
+
else
|
131
|
+
node.first_argument
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param node [RuboCop::AST::SendNode]
|
136
|
+
# @return [RuboCop::AST::SendNode]
|
137
|
+
def find_insertion_target_node_from(node)
|
138
|
+
case node.method_name
|
139
|
+
when :add_column
|
140
|
+
node
|
141
|
+
else
|
142
|
+
node.each_ancestor(:block).first
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @param node [RuboCop::AST::SendNode]
|
147
|
+
# @return [RuboCop::AST::SymNode]
|
148
|
+
def find_table_node_from(node)
|
149
|
+
case node.method_name
|
150
|
+
when :add_column
|
151
|
+
node.first_argument
|
152
|
+
else
|
153
|
+
node.each_ancestor(:block).first.send_node.first_argument
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# @param node [RuboCop::AST::SendNode]
|
158
|
+
# @return [Boolean]
|
159
|
+
def in_change_table?(node)
|
160
|
+
node.each_ancestor(:block).first&.method?(:change_table)
|
161
|
+
end
|
162
|
+
|
163
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
164
|
+
# @param node [RuboCop::AST::Node]
|
165
|
+
# @param string [String]
|
166
|
+
def insert_after_with_same_indentation(
|
167
|
+
corrector,
|
168
|
+
node,
|
169
|
+
string
|
170
|
+
)
|
171
|
+
corrector.insert_after(
|
172
|
+
node,
|
173
|
+
format(
|
174
|
+
"\n%<indentation>s%<string>s",
|
175
|
+
indentation: ' ' * node.location.column,
|
176
|
+
string: string
|
177
|
+
)
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
182
|
+
# @param default_option_node [RuboCop::AST::PairNode]
|
183
|
+
# @param send_node [RuboCop::AST::SendNode]
|
184
|
+
# @return [void]
|
185
|
+
def insert_change_column_default(
|
186
|
+
corrector,
|
187
|
+
default_option_node:,
|
188
|
+
send_node:
|
189
|
+
)
|
190
|
+
insert_after_with_same_indentation(
|
191
|
+
corrector,
|
192
|
+
find_insertion_target_node_from(send_node),
|
193
|
+
format(
|
194
|
+
'change_column_default %<table>s, %<column>s, %<default>s',
|
195
|
+
column: find_column_node_from(send_node).source,
|
196
|
+
default: default_option_node.value.source,
|
197
|
+
table: find_table_node_from(send_node).source
|
198
|
+
)
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
203
|
+
# @param node [RuboCop::AST::Node]
|
204
|
+
# @return [void]
|
205
|
+
def remove_pair(
|
206
|
+
corrector,
|
207
|
+
node
|
208
|
+
)
|
209
|
+
corrector.remove(
|
210
|
+
range_with_surrounding_comma(
|
211
|
+
range_with_surrounding_space(
|
212
|
+
node.location.expression,
|
213
|
+
side: :left
|
214
|
+
),
|
215
|
+
:left
|
216
|
+
)
|
217
|
+
)
|
218
|
+
end
|
219
|
+
|
220
|
+
# @param node [RuboCop::AST::SendNode]
|
221
|
+
# @return [Boolean]
|
222
|
+
def target_method?(node)
|
223
|
+
add_column?(node) ||
|
224
|
+
(column_type_method?(node) && in_change_table?(node))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sevencop
|
6
|
+
# Activate foreign key validation in a separate migration in PostgreSQL.
|
7
|
+
#
|
8
|
+
# To avoid blocking writes on both tables.
|
9
|
+
#
|
10
|
+
# @safety
|
11
|
+
# Only meaningful in PostgreSQL.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# class AddForeignKeyFromArticlesToUsers < ActiveRecord::Migration[7.0]
|
16
|
+
# def change
|
17
|
+
# add_foreign_key :articles, :users
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# class AddForeignKeyFromArticlesToUsersWithoutValidation < ActiveRecord::Migration[7.0]
|
23
|
+
# def change
|
24
|
+
# add_foreign_key :articles, :users, validate: false
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# class ActivateForeignKeyValidationFromArticlesToUsers < ActiveRecord::Migration[7.0]
|
29
|
+
# def change
|
30
|
+
# validate_foreign_key :articles, :users
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
class RailsMigrationAddForeignKey < RuboCop::Cop::Base
|
34
|
+
extend AutoCorrector
|
35
|
+
|
36
|
+
include RangeHelp
|
37
|
+
|
38
|
+
MSG = 'Activate foreign key validation in a separate migration in PostgreSQL.'
|
39
|
+
|
40
|
+
RESTRICT_ON_SEND = %i[
|
41
|
+
add_foreign_key
|
42
|
+
add_reference
|
43
|
+
].freeze
|
44
|
+
|
45
|
+
# @param node [RuboCop::AST::SendNode]
|
46
|
+
# @return [void]
|
47
|
+
def on_send(node)
|
48
|
+
return unless bad?(node)
|
49
|
+
|
50
|
+
add_offense(node) do |corrector|
|
51
|
+
autocorrect(corrector, node)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @!method add_foreign_key_without_validate_option?(node)
|
58
|
+
# @param node [RuboCop::AST::SendNode]
|
59
|
+
# @return [Boolean]
|
60
|
+
def_node_matcher :add_foreign_key_without_validate_option?, <<~PATTERN
|
61
|
+
(send
|
62
|
+
nil?
|
63
|
+
:add_foreign_key
|
64
|
+
_
|
65
|
+
_
|
66
|
+
(hash
|
67
|
+
(pair
|
68
|
+
!(sym :validate)
|
69
|
+
_
|
70
|
+
)*
|
71
|
+
)?
|
72
|
+
)
|
73
|
+
PATTERN
|
74
|
+
|
75
|
+
# @!method option_validate_true_value_node_from_add_foreign_key(node)
|
76
|
+
# @param node [RuboCop::AST::SendNode]
|
77
|
+
# @return [RuboCop::AST::PairNode]
|
78
|
+
def_node_matcher :option_validate_true_value_node_from_add_foreign_key, <<~PATTERN
|
79
|
+
(send
|
80
|
+
nil?
|
81
|
+
:add_foreign_key
|
82
|
+
_
|
83
|
+
_
|
84
|
+
(hash
|
85
|
+
<
|
86
|
+
(pair
|
87
|
+
(sym :validate)
|
88
|
+
$true
|
89
|
+
)
|
90
|
+
...
|
91
|
+
>
|
92
|
+
)
|
93
|
+
)
|
94
|
+
PATTERN
|
95
|
+
|
96
|
+
# @!method option_foreign_key_true_node_from_add_reference(node)
|
97
|
+
# @param node [RuboCop::AST::SendNode]
|
98
|
+
# @return [RuboCop::AST::PairNode]
|
99
|
+
def_node_matcher :option_foreign_key_true_node_from_add_reference, <<~PATTERN
|
100
|
+
(send
|
101
|
+
nil?
|
102
|
+
:add_reference
|
103
|
+
_
|
104
|
+
_
|
105
|
+
(hash
|
106
|
+
<
|
107
|
+
$(pair
|
108
|
+
(sym :foreign_key)
|
109
|
+
true
|
110
|
+
)
|
111
|
+
...
|
112
|
+
>
|
113
|
+
)
|
114
|
+
)
|
115
|
+
PATTERN
|
116
|
+
|
117
|
+
# @param node [RuboCop::AST::SendNode]
|
118
|
+
# @return [Boolean]
|
119
|
+
def add_foreign_key_with_validate_option_true?(node)
|
120
|
+
option_validate_true_value_node_from_add_foreign_key(node)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @param node [RuboCop::AST::SendNode]
|
124
|
+
# @return [Boolean]
|
125
|
+
def add_reference_with_validate_option_true?(node)
|
126
|
+
option_foreign_key_true_node_from_add_reference(node)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
130
|
+
# @param node [RuboCop::AST::SendNode]
|
131
|
+
# @return [void]
|
132
|
+
def autocorrect(
|
133
|
+
corrector,
|
134
|
+
node
|
135
|
+
)
|
136
|
+
if add_foreign_key_without_validate_option?(node)
|
137
|
+
corrector.insert_after(node.last_argument, ', validate: false')
|
138
|
+
elsif add_foreign_key_with_validate_option_true?(node)
|
139
|
+
corrector.replace(
|
140
|
+
option_validate_true_value_node_from_add_foreign_key(node),
|
141
|
+
'false'
|
142
|
+
)
|
143
|
+
elsif add_reference_with_validate_option_true?(node)
|
144
|
+
corrector.remove(
|
145
|
+
range_with_surrounding_comma(
|
146
|
+
range_with_surrounding_space(
|
147
|
+
option_foreign_key_true_node_from_add_reference(node).location.expression,
|
148
|
+
side: :left
|
149
|
+
),
|
150
|
+
:left
|
151
|
+
)
|
152
|
+
)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# @param node [RuboCop::AST::SendNode]
|
157
|
+
# @return [Boolean]
|
158
|
+
def bad?(node)
|
159
|
+
add_foreign_key_without_validate_option?(node) ||
|
160
|
+
add_foreign_key_with_validate_option_true?(node) ||
|
161
|
+
add_reference_with_validate_option_true?(node)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sevencop
|
6
|
+
# Use `algorithm: :concurrently` on adding indexes to existing tables in PostgreSQL.
|
7
|
+
#
|
8
|
+
# To avoid blocking writes.
|
9
|
+
#
|
10
|
+
# @safety
|
11
|
+
# Only meaningful in PostgreSQL.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# class AddIndexToUsersName < ActiveRecord::Migration[7.0]
|
16
|
+
# def change
|
17
|
+
# add_index :users, :name
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# class AddIndexToUsersNameConcurrently < ActiveRecord::Migration[7.0]
|
23
|
+
# disable_ddl_transaction!
|
24
|
+
#
|
25
|
+
# def change
|
26
|
+
# add_index :users, :name, algorithm: :concurrently
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
class RailsMigrationAddIndexConcurrently < RuboCop::Cop::Base
|
30
|
+
extend AutoCorrector
|
31
|
+
|
32
|
+
include ::Sevencop::CopConcerns::DisableDdlTransaction
|
33
|
+
|
34
|
+
MSG = 'Use `algorithm: :concurrently` on adding indexes to existing tables in PostgreSQL.'
|
35
|
+
|
36
|
+
RESTRICT_ON_SEND = %i[
|
37
|
+
add_index
|
38
|
+
index
|
39
|
+
].freeze
|
40
|
+
|
41
|
+
# @param node [RuboCop::AST::SendNode]
|
42
|
+
# @return [void]
|
43
|
+
def on_send(node)
|
44
|
+
return unless bad?(node)
|
45
|
+
|
46
|
+
add_offense(node) do |corrector|
|
47
|
+
autocorrect(corrector, node)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# @!method add_index?(node)
|
54
|
+
# @param node [RuboCop::AST::SendNode]
|
55
|
+
# @return [Boolean]
|
56
|
+
def_node_matcher :add_index?, <<~PATTERN
|
57
|
+
(send
|
58
|
+
nil?
|
59
|
+
:add_index
|
60
|
+
_
|
61
|
+
_
|
62
|
+
...
|
63
|
+
)
|
64
|
+
PATTERN
|
65
|
+
|
66
|
+
# @!method add_index_concurrently?(node)
|
67
|
+
# @param node [RuboCop::AST::SendNode]
|
68
|
+
# @return [Boolean]
|
69
|
+
def_node_matcher :add_index_concurrently?, <<~PATTERN
|
70
|
+
(send
|
71
|
+
nil?
|
72
|
+
:add_index
|
73
|
+
_
|
74
|
+
_
|
75
|
+
(hash
|
76
|
+
<
|
77
|
+
(pair
|
78
|
+
(sym :algorithm)
|
79
|
+
(sym :concurrently)
|
80
|
+
)
|
81
|
+
...
|
82
|
+
>
|
83
|
+
)
|
84
|
+
)
|
85
|
+
PATTERN
|
86
|
+
|
87
|
+
# @!method index?(node)
|
88
|
+
# @param node [RuboCop::AST::SendNode]
|
89
|
+
# @return [Boolean]
|
90
|
+
def_node_matcher :index?, <<~PATTERN
|
91
|
+
(send
|
92
|
+
lvar
|
93
|
+
:index
|
94
|
+
_
|
95
|
+
...
|
96
|
+
)
|
97
|
+
PATTERN
|
98
|
+
|
99
|
+
# @!method index_concurrently?(node)
|
100
|
+
# @param node [RuboCop::AST::SendNode]
|
101
|
+
# @return [Boolean]
|
102
|
+
def_node_matcher :index_concurrently?, <<~PATTERN
|
103
|
+
(send
|
104
|
+
lvar
|
105
|
+
:index
|
106
|
+
_
|
107
|
+
(hash
|
108
|
+
<
|
109
|
+
(pair
|
110
|
+
(sym :algorithm)
|
111
|
+
(sym :concurrently)
|
112
|
+
)
|
113
|
+
...
|
114
|
+
>
|
115
|
+
)
|
116
|
+
)
|
117
|
+
PATTERN
|
118
|
+
|
119
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
120
|
+
# @param node [RuboCop::AST::SendNode]
|
121
|
+
# @return [void]
|
122
|
+
def autocorrect(
|
123
|
+
corrector,
|
124
|
+
node
|
125
|
+
)
|
126
|
+
insert_disable_ddl_transaction(corrector, node) unless within_disable_ddl_transaction?(node)
|
127
|
+
insert_algorithm_option(corrector, node)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @param node [RuboCop::AST::SendNode]
|
131
|
+
# @return [Boolean]
|
132
|
+
def bad?(node)
|
133
|
+
case node.method_name
|
134
|
+
when :add_index
|
135
|
+
add_index?(node) && !add_index_concurrently?(node)
|
136
|
+
when :index
|
137
|
+
index?(node) && in_change_table?(node) && !index_concurrently?(node)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# @param node [RuboCop::AST::SendNode]
|
142
|
+
# @return [Boolean]
|
143
|
+
def in_change_table?(node)
|
144
|
+
node.each_ancestor(:block).first&.method?(:change_table)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
148
|
+
# @param node [RuboCop::AST::SendNode]
|
149
|
+
# @return [void]
|
150
|
+
def insert_algorithm_option(
|
151
|
+
corrector,
|
152
|
+
node
|
153
|
+
)
|
154
|
+
target_node = node.last_argument
|
155
|
+
target_node = target_node.pairs.last if target_node.hash_type?
|
156
|
+
corrector.insert_after(
|
157
|
+
target_node,
|
158
|
+
', algorithm: :concurrently'
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sevencop
|
6
|
+
# Use `in_batches` in batch processing.
|
7
|
+
#
|
8
|
+
# For more efficient batch processing.
|
9
|
+
#
|
10
|
+
# @safety
|
11
|
+
# There are some cases where we should not do that,
|
12
|
+
# or this type of consideration might be already done in a way that we cannot detect.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# # bad
|
16
|
+
# class BackfillSomeColumn < ActiveRecord::Migration[7.0]
|
17
|
+
# disable_ddl_transaction!
|
18
|
+
#
|
19
|
+
# def change
|
20
|
+
# User.update_all(some_column: 'some value')
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# class BackfillSomeColumnToUsers < ActiveRecord::Migration[7.0]
|
26
|
+
# disable_ddl_transaction!
|
27
|
+
#
|
28
|
+
# def up
|
29
|
+
# User.within_in_batches do |relation|
|
30
|
+
# relation.update_all(some_column: 'some value')
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
class RailsMigrationBatchInBatches < RuboCop::Cop::Base
|
35
|
+
extend AutoCorrector
|
36
|
+
|
37
|
+
include ::Sevencop::CopConcerns::BatchProcessing
|
38
|
+
|
39
|
+
MSG = 'Use `in_batches` in batch processing.'
|
40
|
+
|
41
|
+
RESTRICT_ON_SEND = %i[
|
42
|
+
delete_all
|
43
|
+
update_all
|
44
|
+
].freeze
|
45
|
+
|
46
|
+
# @param node [RuboCop::AST::SendNode]
|
47
|
+
# @return [void]
|
48
|
+
def on_send(node)
|
49
|
+
return unless wrong?(node)
|
50
|
+
|
51
|
+
add_offense(node) do |corrector|
|
52
|
+
autocorrect(corrector, node)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# @param corrector [RuboCop::Cop::Corrector]
|
59
|
+
# @param node [RuboCop::AST::SendNode]
|
60
|
+
# @return [void]
|
61
|
+
def autocorrect(
|
62
|
+
corrector,
|
63
|
+
node
|
64
|
+
)
|
65
|
+
range = node.location.selector.with(
|
66
|
+
end_pos: node.location.expression.end_pos
|
67
|
+
)
|
68
|
+
corrector.replace(
|
69
|
+
range,
|
70
|
+
<<~TEXT.chomp
|
71
|
+
in_batches do |relation|
|
72
|
+
relation.#{range.source}
|
73
|
+
end
|
74
|
+
TEXT
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param node [RuboCop::AST::Node]
|
79
|
+
# @return [Boolean]
|
80
|
+
def within_in_batches?(node)
|
81
|
+
node.each_ancestor(:block).any? do |ancestor|
|
82
|
+
ancestor.method?(:in_batches)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param node [RuboCop::AST::SendNode]
|
87
|
+
# @return [Boolean]
|
88
|
+
def wrong?(node)
|
89
|
+
batch_processing?(node) &&
|
90
|
+
!within_in_batches?(node)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|