active_record_doctor 1.11.0 → 1.13.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/README.md +41 -14
- data/lib/active_record_doctor/config/loader.rb +1 -1
- data/lib/active_record_doctor/detectors/base.rb +30 -15
- data/lib/active_record_doctor/detectors/extraneous_indexes.rb +14 -9
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +1 -1
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +44 -31
- data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +1 -1
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +2 -2
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +73 -23
- data/lib/active_record_doctor/detectors/short_primary_key_type.rb +3 -3
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +34 -7
- data/lib/active_record_doctor/logger/hierarchical.rb +1 -1
- data/lib/active_record_doctor/railtie.rb +1 -1
- data/lib/active_record_doctor/runner.rb +1 -1
- data/lib/active_record_doctor/utils.rb +21 -0
- data/lib/active_record_doctor/version.rb +1 -1
- data/lib/active_record_doctor.rb +2 -0
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +14 -14
- data/lib/tasks/active_record_doctor.rake +2 -2
- metadata +11 -47
- data/test/active_record_doctor/config/loader_test.rb +0 -120
- data/test/active_record_doctor/config_test.rb +0 -116
- data/test/active_record_doctor/detectors/disable_test.rb +0 -30
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +0 -224
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +0 -79
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +0 -472
- data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +0 -107
- data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +0 -116
- data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +0 -70
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +0 -273
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +0 -232
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +0 -327
- data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +0 -72
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +0 -55
- data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +0 -177
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +0 -78
- data/test/active_record_doctor/runner_test.rb +0 -41
- data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +0 -131
- data/test/setup.rb +0 -124
@@ -1,327 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
4
|
-
def test_missing_unique_index
|
5
|
-
create_table(:users) do |t|
|
6
|
-
t.string :email
|
7
|
-
t.index :email
|
8
|
-
end.define_model do
|
9
|
-
validates :email, uniqueness: true
|
10
|
-
end
|
11
|
-
|
12
|
-
assert_problems(<<~OUTPUT)
|
13
|
-
add a unique index on users(email) - validating uniqueness in the model without an index can lead to duplicates
|
14
|
-
OUTPUT
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_missing_unique_index_on_functional_index
|
18
|
-
skip if !(ActiveRecord::VERSION::STRING >= "5.0" && postgresql?)
|
19
|
-
|
20
|
-
create_table(:users) do |t|
|
21
|
-
t.string :email
|
22
|
-
t.index "lower(email)"
|
23
|
-
end.define_model do
|
24
|
-
validates :email, uniqueness: true
|
25
|
-
end
|
26
|
-
|
27
|
-
# Running the detector should NOT raise an error when a functional index
|
28
|
-
# is present. No need to assert anything -- the test is successful if no
|
29
|
-
# exception was raised.
|
30
|
-
run_detector
|
31
|
-
end
|
32
|
-
|
33
|
-
def test_validates_multiple_attributes
|
34
|
-
create_table(:users) do |t|
|
35
|
-
t.string :email
|
36
|
-
t.string :ref_token
|
37
|
-
end.define_model do
|
38
|
-
validates :email, :ref_token, uniqueness: true
|
39
|
-
end
|
40
|
-
|
41
|
-
assert_problems(<<~OUTPUT)
|
42
|
-
add a unique index on users(email) - validating uniqueness in the model without an index can lead to duplicates
|
43
|
-
add a unique index on users(ref_token) - validating uniqueness in the model without an index can lead to duplicates
|
44
|
-
OUTPUT
|
45
|
-
end
|
46
|
-
|
47
|
-
def test_present_unique_index
|
48
|
-
create_table(:users) do |t|
|
49
|
-
t.string :email
|
50
|
-
t.index :email, unique: true
|
51
|
-
end.define_model do
|
52
|
-
validates :email, uniqueness: true
|
53
|
-
end
|
54
|
-
|
55
|
-
refute_problems
|
56
|
-
end
|
57
|
-
|
58
|
-
def test_present_partial_unique_index
|
59
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
60
|
-
|
61
|
-
create_table(:users) do |t|
|
62
|
-
t.string :email
|
63
|
-
t.boolean :active
|
64
|
-
t.index :email, unique: true, where: "active"
|
65
|
-
end.define_model do
|
66
|
-
validates :email, uniqueness: true
|
67
|
-
end
|
68
|
-
|
69
|
-
assert_problems(<<~OUTPUT)
|
70
|
-
add a unique index on users(email) - validating uniqueness in the model without an index can lead to duplicates
|
71
|
-
OUTPUT
|
72
|
-
end
|
73
|
-
|
74
|
-
def test_unique_index_with_extra_columns_with_scope
|
75
|
-
create_table(:users) do |t|
|
76
|
-
t.string :email
|
77
|
-
t.integer :company_id
|
78
|
-
t.integer :department_id
|
79
|
-
t.index [:company_id, :department_id, :email]
|
80
|
-
end.define_model do
|
81
|
-
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
82
|
-
end
|
83
|
-
|
84
|
-
assert_problems(<<~OUTPUT)
|
85
|
-
add a unique index on users(company_id, department_id, email) - validating uniqueness in the model without an index can lead to duplicates
|
86
|
-
OUTPUT
|
87
|
-
end
|
88
|
-
|
89
|
-
def test_unique_index_with_exact_columns_with_scope
|
90
|
-
create_table(:users) do |t|
|
91
|
-
t.string :email
|
92
|
-
t.integer :company_id
|
93
|
-
t.integer :department_id
|
94
|
-
t.index [:company_id, :department_id, :email], unique: true
|
95
|
-
end.define_model do
|
96
|
-
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
97
|
-
end
|
98
|
-
|
99
|
-
refute_problems
|
100
|
-
end
|
101
|
-
|
102
|
-
def test_unique_index_with_fewer_columns_with_scope
|
103
|
-
create_table(:users) do |t|
|
104
|
-
t.string :email
|
105
|
-
t.integer :company_id
|
106
|
-
t.integer :department_id
|
107
|
-
t.index [:company_id, :department_id], unique: true
|
108
|
-
end.define_model do
|
109
|
-
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
110
|
-
end
|
111
|
-
|
112
|
-
refute_problems
|
113
|
-
end
|
114
|
-
|
115
|
-
def test_missing_unique_index_with_association_attribute
|
116
|
-
create_table(:users) do |t|
|
117
|
-
t.integer :account_id
|
118
|
-
end.define_model do
|
119
|
-
belongs_to :account
|
120
|
-
validates :account, uniqueness: true
|
121
|
-
end
|
122
|
-
|
123
|
-
assert_problems(<<~OUTPUT)
|
124
|
-
add a unique index on users(account_id) - validating uniqueness in the model without an index can lead to duplicates
|
125
|
-
OUTPUT
|
126
|
-
end
|
127
|
-
|
128
|
-
def test_present_unique_index_with_association_attribute
|
129
|
-
create_table(:users) do |t|
|
130
|
-
t.integer :account_id
|
131
|
-
t.index :account_id, unique: true
|
132
|
-
end.define_model do
|
133
|
-
belongs_to :account
|
134
|
-
validates :account, uniqueness: true
|
135
|
-
end
|
136
|
-
|
137
|
-
refute_problems
|
138
|
-
end
|
139
|
-
|
140
|
-
def test_missing_unique_index_with_association_scope
|
141
|
-
create_table(:comments) do |t|
|
142
|
-
t.string :title
|
143
|
-
t.integer :commentable_id
|
144
|
-
t.string :commentable_type
|
145
|
-
end.define_model do
|
146
|
-
belongs_to :commentable, polymorphic: true
|
147
|
-
validates :title, uniqueness: { scope: :commentable }
|
148
|
-
end
|
149
|
-
|
150
|
-
assert_problems(<<~OUTPUT)
|
151
|
-
add a unique index on comments(commentable_type, commentable_id, title) - validating uniqueness in the model without an index can lead to duplicates
|
152
|
-
OUTPUT
|
153
|
-
end
|
154
|
-
|
155
|
-
def test_present_unique_index_with_association_scope
|
156
|
-
create_table(:comments) do |t|
|
157
|
-
t.string :title
|
158
|
-
t.integer :commentable_id
|
159
|
-
t.string :commentable_type
|
160
|
-
t.index [:commentable_id, :commentable_type, :title], unique: true
|
161
|
-
end.define_model do
|
162
|
-
belongs_to :commentable, polymorphic: true
|
163
|
-
validates :title, uniqueness: { scope: :commentable }
|
164
|
-
end
|
165
|
-
|
166
|
-
refute_problems
|
167
|
-
end
|
168
|
-
|
169
|
-
def test_column_order_is_ignored
|
170
|
-
create_table(:users) do |t|
|
171
|
-
t.string :email
|
172
|
-
t.integer :organization_id
|
173
|
-
|
174
|
-
t.index [:email, :organization_id], unique: true
|
175
|
-
end.define_model do
|
176
|
-
validates :email, uniqueness: { scope: :organization_id }
|
177
|
-
end
|
178
|
-
|
179
|
-
refute_problems
|
180
|
-
end
|
181
|
-
|
182
|
-
def test_conditions_is_skipped
|
183
|
-
assert_skipped(conditions: -> { where.not(email: nil) })
|
184
|
-
end
|
185
|
-
|
186
|
-
def test_case_insensitive_is_skipped
|
187
|
-
assert_skipped(case_sensitive: false)
|
188
|
-
end
|
189
|
-
|
190
|
-
def test_if_is_skipped
|
191
|
-
assert_skipped(if: ->(_model) { true })
|
192
|
-
end
|
193
|
-
|
194
|
-
def test_unless_is_skipped
|
195
|
-
assert_skipped(unless: ->(_model) { true })
|
196
|
-
end
|
197
|
-
|
198
|
-
def test_skips_validator_without_attributes
|
199
|
-
create_table(:users) do |t|
|
200
|
-
t.string :email
|
201
|
-
t.index :email
|
202
|
-
end.define_model do
|
203
|
-
validates_with DummyValidator
|
204
|
-
end
|
205
|
-
|
206
|
-
refute_problems
|
207
|
-
end
|
208
|
-
|
209
|
-
def test_has_one_without_index
|
210
|
-
create_table(:users)
|
211
|
-
.define_model do
|
212
|
-
has_one :account, class_name: "TransientRecord::Models::Account"
|
213
|
-
has_one :account_history, through: :account, class_name: "TransientRecord::Models::Account"
|
214
|
-
end
|
215
|
-
|
216
|
-
create_table(:accounts) do |t|
|
217
|
-
t.integer :user_id
|
218
|
-
end.define_model do
|
219
|
-
has_one :account_history, class_name: "TransientRecord::Models::AccountHistory"
|
220
|
-
end
|
221
|
-
|
222
|
-
create_table(:account_histories) do |t|
|
223
|
-
t.integer :account_id
|
224
|
-
end.define_model do
|
225
|
-
belongs_to :account, class_name: "TransientRecord::Models::Account"
|
226
|
-
end
|
227
|
-
|
228
|
-
assert_problems(<<~OUTPUT)
|
229
|
-
add a unique index on accounts(user_id) - using `has_one` in the TransientRecord::Models::User model without an index can lead to duplicates
|
230
|
-
add a unique index on account_histories(account_id) - using `has_one` in the TransientRecord::Models::Account model without an index can lead to duplicates
|
231
|
-
OUTPUT
|
232
|
-
end
|
233
|
-
|
234
|
-
def test_has_one_with_scope_and_without_index
|
235
|
-
create_table(:users)
|
236
|
-
.define_model do
|
237
|
-
has_one :last_comment, -> { order(created_at: :desc) }, class_name: "TransientRecord::Models::Comment"
|
238
|
-
end
|
239
|
-
|
240
|
-
create_table(:comments) do |t|
|
241
|
-
t.integer :user_id
|
242
|
-
end.define_model
|
243
|
-
|
244
|
-
refute_problems
|
245
|
-
end
|
246
|
-
|
247
|
-
def test_has_one_with_index
|
248
|
-
create_table(:users)
|
249
|
-
.define_model do
|
250
|
-
has_one :account, class_name: "TransientRecord::Models::Account"
|
251
|
-
end
|
252
|
-
|
253
|
-
create_table(:accounts) do |t|
|
254
|
-
t.integer :user_id, index: { unique: true }
|
255
|
-
end.define_model
|
256
|
-
|
257
|
-
refute_problems
|
258
|
-
end
|
259
|
-
|
260
|
-
def test_config_ignore_models
|
261
|
-
create_table(:users) do |t|
|
262
|
-
t.string :email
|
263
|
-
end.define_model do
|
264
|
-
validates :email, uniqueness: true
|
265
|
-
end
|
266
|
-
|
267
|
-
config_file(<<-CONFIG)
|
268
|
-
ActiveRecordDoctor.configure do |config|
|
269
|
-
config.detector :missing_unique_indexes,
|
270
|
-
ignore_models: ["TransientRecord::Models::User"]
|
271
|
-
end
|
272
|
-
CONFIG
|
273
|
-
|
274
|
-
refute_problems
|
275
|
-
end
|
276
|
-
|
277
|
-
def test_global_ignore_models
|
278
|
-
create_table(:users) do |t|
|
279
|
-
t.string :email
|
280
|
-
end.define_model do
|
281
|
-
validates :email, uniqueness: true
|
282
|
-
end
|
283
|
-
|
284
|
-
config_file(<<-CONFIG)
|
285
|
-
ActiveRecordDoctor.configure do |config|
|
286
|
-
config.global :ignore_models, ["TransientRecord::Models::User"]
|
287
|
-
end
|
288
|
-
CONFIG
|
289
|
-
|
290
|
-
refute_problems
|
291
|
-
end
|
292
|
-
|
293
|
-
def test_config_ignore_columns
|
294
|
-
create_table(:users) do |t|
|
295
|
-
t.string :email
|
296
|
-
t.integer :role
|
297
|
-
end.define_model do
|
298
|
-
validates :email, :role, uniqueness: { scope: :organization_id }
|
299
|
-
end
|
300
|
-
|
301
|
-
config_file(<<-CONFIG)
|
302
|
-
ActiveRecordDoctor.configure do |config|
|
303
|
-
config.detector :missing_unique_indexes,
|
304
|
-
ignore_columns: ["TransientRecord::Models::User(organization_id, email)", "TransientRecord::Models::User(organization_id, role)"]
|
305
|
-
end
|
306
|
-
CONFIG
|
307
|
-
|
308
|
-
refute_problems
|
309
|
-
end
|
310
|
-
|
311
|
-
class DummyValidator < ActiveModel::Validator
|
312
|
-
def validate(record)
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
private
|
317
|
-
|
318
|
-
def assert_skipped(options)
|
319
|
-
create_table(:users) do |t|
|
320
|
-
t.string :email
|
321
|
-
end.define_model do
|
322
|
-
validates :email, uniqueness: options
|
323
|
-
end
|
324
|
-
|
325
|
-
refute_problems
|
326
|
-
end
|
327
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ActiveRecordDoctor::Detectors::ShortPrimaryKeyTypeTest < Minitest::Test
|
4
|
-
def setup
|
5
|
-
@connection = ActiveRecord::Base.connection
|
6
|
-
@connection.enable_extension("uuid-ossp") if postgresql?
|
7
|
-
super
|
8
|
-
end
|
9
|
-
|
10
|
-
def teardown
|
11
|
-
@connection.disable_extension("uuid-ossp") if postgresql?
|
12
|
-
super
|
13
|
-
end
|
14
|
-
|
15
|
-
def test_short_integer_primary_key_is_reported
|
16
|
-
create_table(:companies, id: :int)
|
17
|
-
|
18
|
-
# In Rails 4.2 and MySQL primary key is not created due to a bug
|
19
|
-
if mysql? && ActiveRecord::VERSION::STRING < "5.0"
|
20
|
-
@connection.execute("ALTER TABLE companies ADD PRIMARY KEY(id)")
|
21
|
-
end
|
22
|
-
|
23
|
-
assert_problems(<<~OUTPUT)
|
24
|
-
change the type of companies.id to bigint
|
25
|
-
OUTPUT
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_long_integer_primary_key_is_not_reported
|
29
|
-
create_table(:companies, id: :bigint)
|
30
|
-
refute_problems
|
31
|
-
end
|
32
|
-
|
33
|
-
def test_uuid_primary_key_is_not_reported
|
34
|
-
skip unless postgresql?
|
35
|
-
|
36
|
-
create_table(:companies, id: :uuid)
|
37
|
-
refute_problems
|
38
|
-
end
|
39
|
-
|
40
|
-
def test_no_primary_key_is_not_reported
|
41
|
-
create_table(:companies, id: false) do |t|
|
42
|
-
t.string :name, null: false
|
43
|
-
end
|
44
|
-
|
45
|
-
refute_problems
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_config_ignore_tables
|
49
|
-
create_table(:companies, id: :integer)
|
50
|
-
|
51
|
-
config_file(<<-CONFIG)
|
52
|
-
ActiveRecordDoctor.configure do |config|
|
53
|
-
config.detector :short_primary_key_type,
|
54
|
-
ignore_tables: ["companies"]
|
55
|
-
end
|
56
|
-
CONFIG
|
57
|
-
|
58
|
-
refute_problems
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_global_ignore_tables
|
62
|
-
create_table(:companies, id: :integer)
|
63
|
-
|
64
|
-
config_file(<<-CONFIG)
|
65
|
-
ActiveRecordDoctor.configure do |config|
|
66
|
-
config.global :ignore_tables, ["companies"]
|
67
|
-
end
|
68
|
-
CONFIG
|
69
|
-
|
70
|
-
refute_problems
|
71
|
-
end
|
72
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Test
|
4
|
-
def test_model_backed_by_table
|
5
|
-
create_table(:users) do
|
6
|
-
end.define_model do
|
7
|
-
end
|
8
|
-
|
9
|
-
refute_problems
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_model_backed_by_non_existent_table
|
13
|
-
define_model(:User)
|
14
|
-
|
15
|
-
assert_problems(<<~OUTPUT)
|
16
|
-
TransientRecord::Models::User references a non-existent table or view named users
|
17
|
-
OUTPUT
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_model_backed_by_view
|
21
|
-
# We replace the underlying table with a view. The view doesn't have to be
|
22
|
-
# backed by an actual table - it can simply return a predefined tuple.
|
23
|
-
ActiveRecord::Base.connection.execute("CREATE VIEW users AS SELECT 1")
|
24
|
-
define_model(:User)
|
25
|
-
|
26
|
-
refute_problems
|
27
|
-
ensure
|
28
|
-
ActiveRecord::Base.connection.execute("DROP VIEW users")
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_config_ignore_tables
|
32
|
-
define_model(:User)
|
33
|
-
|
34
|
-
config_file(<<-CONFIG)
|
35
|
-
ActiveRecordDoctor.configure do |config|
|
36
|
-
config.detector :undefined_table_references,
|
37
|
-
ignore_models: ["TransientRecord::Models::User"]
|
38
|
-
end
|
39
|
-
CONFIG
|
40
|
-
|
41
|
-
refute_problems
|
42
|
-
end
|
43
|
-
|
44
|
-
def test_global_ignore_tables
|
45
|
-
define_model(:User)
|
46
|
-
|
47
|
-
config_file(<<-CONFIG)
|
48
|
-
ActiveRecordDoctor.configure do |config|
|
49
|
-
config.global :ignore_models, ["TransientRecord::Models::User"]
|
50
|
-
end
|
51
|
-
CONFIG
|
52
|
-
|
53
|
-
refute_problems
|
54
|
-
end
|
55
|
-
end
|
@@ -1,177 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ActiveRecordDoctor::Detectors::UnindexedDeletedAtTest < Minitest::Test
|
4
|
-
def test_indexed_deleted_at_is_not_reported
|
5
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
6
|
-
|
7
|
-
create_table(:users) do |t|
|
8
|
-
t.string :first_name
|
9
|
-
t.string :last_name
|
10
|
-
t.datetime :deleted_at
|
11
|
-
t.index [:first_name, :last_name],
|
12
|
-
name: "index_profiles_on_first_name_and_last_name",
|
13
|
-
where: "deleted_at IS NULL"
|
14
|
-
t.index [:last_name],
|
15
|
-
name: "index_deleted_profiles_on_last_name",
|
16
|
-
where: "deleted_at IS NOT NULL"
|
17
|
-
end
|
18
|
-
|
19
|
-
refute_problems
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_unindexed_deleted_at_is_reported
|
23
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
24
|
-
|
25
|
-
create_table(:users) do |t|
|
26
|
-
t.string :first_name
|
27
|
-
t.string :last_name
|
28
|
-
t.datetime :deleted_at
|
29
|
-
t.index [:first_name, :last_name],
|
30
|
-
name: "index_profiles_on_first_name_and_last_name"
|
31
|
-
end
|
32
|
-
|
33
|
-
assert_problems(<<~OUTPUT)
|
34
|
-
consider adding `WHERE deleted_at IS NULL` or `WHERE deleted_at IS NOT NULL` to index_profiles_on_first_name_and_last_name - a partial index can speed lookups of soft-deletable models
|
35
|
-
OUTPUT
|
36
|
-
end
|
37
|
-
|
38
|
-
def test_indexed_discarded_at_is_not_reported
|
39
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
40
|
-
|
41
|
-
create_table(:users) do |t|
|
42
|
-
t.string :first_name
|
43
|
-
t.string :last_name
|
44
|
-
t.datetime :discarded_at
|
45
|
-
t.index [:first_name, :last_name],
|
46
|
-
name: "index_profiles_on_first_name_and_last_name",
|
47
|
-
where: "discarded_at IS NULL"
|
48
|
-
t.index [:last_name],
|
49
|
-
name: "index_discarded_profiles_on_last_name",
|
50
|
-
where: "discarded_at IS NOT NULL"
|
51
|
-
end
|
52
|
-
|
53
|
-
refute_problems
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_unindexed_discarded_at_is_reported
|
57
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
58
|
-
|
59
|
-
create_table(:users) do |t|
|
60
|
-
t.string :first_name
|
61
|
-
t.string :last_name
|
62
|
-
t.datetime :discarded_at
|
63
|
-
t.index [:first_name, :last_name],
|
64
|
-
name: "index_profiles_on_first_name_and_last_name"
|
65
|
-
end
|
66
|
-
|
67
|
-
assert_problems(<<~OUTPUT)
|
68
|
-
consider adding `WHERE discarded_at IS NULL` or `WHERE discarded_at IS NOT NULL` to index_profiles_on_first_name_and_last_name - a partial index can speed lookups of soft-deletable models
|
69
|
-
OUTPUT
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_config_ignore_tables
|
73
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
74
|
-
|
75
|
-
create_table(:users) do |t|
|
76
|
-
t.string :first_name
|
77
|
-
t.string :last_name
|
78
|
-
t.datetime :discarded_at
|
79
|
-
t.index [:first_name, :last_name],
|
80
|
-
name: "index_profiles_on_first_name_and_last_name"
|
81
|
-
end
|
82
|
-
|
83
|
-
config_file(<<-CONFIG)
|
84
|
-
ActiveRecordDoctor.configure do |config|
|
85
|
-
config.detector :unindexed_deleted_at,
|
86
|
-
ignore_tables: ["users"]
|
87
|
-
end
|
88
|
-
CONFIG
|
89
|
-
|
90
|
-
refute_problems
|
91
|
-
end
|
92
|
-
|
93
|
-
def test_global_ignore_tables
|
94
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
95
|
-
|
96
|
-
create_table(:users) do |t|
|
97
|
-
t.string :first_name
|
98
|
-
t.string :last_name
|
99
|
-
t.datetime :discarded_at
|
100
|
-
t.index [:first_name, :last_name],
|
101
|
-
name: "index_profiles_on_first_name_and_last_name"
|
102
|
-
end
|
103
|
-
|
104
|
-
config_file(<<-CONFIG)
|
105
|
-
ActiveRecordDoctor.configure do |config|
|
106
|
-
config.global :ignore_tables, ["users"]
|
107
|
-
end
|
108
|
-
CONFIG
|
109
|
-
|
110
|
-
refute_problems
|
111
|
-
end
|
112
|
-
|
113
|
-
def test_config_ignore_columns
|
114
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
115
|
-
|
116
|
-
create_table(:users) do |t|
|
117
|
-
t.string :first_name
|
118
|
-
t.string :last_name
|
119
|
-
t.datetime :discarded_at
|
120
|
-
t.index [:first_name, :last_name],
|
121
|
-
name: "index_profiles_on_first_name_and_last_name"
|
122
|
-
end
|
123
|
-
|
124
|
-
config_file(<<-CONFIG)
|
125
|
-
ActiveRecordDoctor.configure do |config|
|
126
|
-
config.detector :unindexed_deleted_at,
|
127
|
-
ignore_columns: ["users.discarded_at"]
|
128
|
-
end
|
129
|
-
CONFIG
|
130
|
-
|
131
|
-
refute_problems
|
132
|
-
end
|
133
|
-
|
134
|
-
def test_config_ignore_indexes
|
135
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
136
|
-
|
137
|
-
create_table(:users) do |t|
|
138
|
-
t.string :first_name
|
139
|
-
t.string :last_name
|
140
|
-
t.datetime :discarded_at
|
141
|
-
t.index [:first_name, :last_name],
|
142
|
-
name: "index_profiles_on_first_name_and_last_name"
|
143
|
-
end
|
144
|
-
|
145
|
-
config_file(<<-CONFIG)
|
146
|
-
ActiveRecordDoctor.configure do |config|
|
147
|
-
config.detector :unindexed_deleted_at,
|
148
|
-
ignore_indexes: ["index_profiles_on_first_name_and_last_name"]
|
149
|
-
end
|
150
|
-
CONFIG
|
151
|
-
|
152
|
-
refute_problems
|
153
|
-
end
|
154
|
-
|
155
|
-
def test_config_column_names
|
156
|
-
skip("MySQL doesn't support partial indexes") if mysql?
|
157
|
-
|
158
|
-
create_table(:users) do |t|
|
159
|
-
t.string :first_name
|
160
|
-
t.string :last_name
|
161
|
-
t.datetime :obliverated_at
|
162
|
-
t.index [:first_name, :last_name],
|
163
|
-
name: "index_profiles_on_first_name_and_last_name"
|
164
|
-
end
|
165
|
-
|
166
|
-
config_file(<<-CONFIG)
|
167
|
-
ActiveRecordDoctor.configure do |config|
|
168
|
-
config.detector :unindexed_deleted_at,
|
169
|
-
column_names: ["obliverated_at"]
|
170
|
-
end
|
171
|
-
CONFIG
|
172
|
-
|
173
|
-
assert_problems(<<~OUTPUT)
|
174
|
-
consider adding `WHERE obliverated_at IS NULL` or `WHERE obliverated_at IS NOT NULL` to index_profiles_on_first_name_and_last_name - a partial index can speed lookups of soft-deletable models
|
175
|
-
OUTPUT
|
176
|
-
end
|
177
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ActiveRecordDoctor::Detectors::UnindexedForeignKeysTest < Minitest::Test
|
4
|
-
def test_unindexed_foreign_key_is_reported
|
5
|
-
skip("MySQL always indexes foreign keys") if mysql?
|
6
|
-
|
7
|
-
create_table(:companies)
|
8
|
-
create_table(:users) do |t|
|
9
|
-
t.references :company, foreign_key: true, index: false
|
10
|
-
end
|
11
|
-
|
12
|
-
assert_problems(<<~OUTPUT)
|
13
|
-
add an index on users.company_id - foreign keys are often used in database lookups and should be indexed for performance reasons
|
14
|
-
OUTPUT
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_indexed_foreign_key_is_not_reported
|
18
|
-
create_table(:companies)
|
19
|
-
create_table(:users) do |t|
|
20
|
-
t.references :company, foreign_key: true, index: true
|
21
|
-
end
|
22
|
-
|
23
|
-
refute_problems
|
24
|
-
end
|
25
|
-
|
26
|
-
def test_config_ignore_tables
|
27
|
-
skip("MySQL always indexes foreign keys") if mysql?
|
28
|
-
|
29
|
-
create_table(:companies)
|
30
|
-
create_table(:users) do |t|
|
31
|
-
t.references :company, foreign_key: true, index: false
|
32
|
-
end
|
33
|
-
|
34
|
-
config_file(<<-CONFIG)
|
35
|
-
ActiveRecordDoctor.configure do |config|
|
36
|
-
config.detector :unindexed_foreign_keys,
|
37
|
-
ignore_tables: ["users"]
|
38
|
-
end
|
39
|
-
CONFIG
|
40
|
-
|
41
|
-
refute_problems
|
42
|
-
end
|
43
|
-
|
44
|
-
def test_global_ignore_tables
|
45
|
-
skip("MySQL always indexes foreign keys") if mysql?
|
46
|
-
|
47
|
-
create_table(:companies)
|
48
|
-
create_table(:users) do |t|
|
49
|
-
t.references :company, foreign_key: true, index: false
|
50
|
-
end
|
51
|
-
|
52
|
-
config_file(<<-CONFIG)
|
53
|
-
ActiveRecordDoctor.configure do |config|
|
54
|
-
config.global :ignore_tables, ["users"]
|
55
|
-
end
|
56
|
-
CONFIG
|
57
|
-
|
58
|
-
refute_problems
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_config_ignore_columns
|
62
|
-
skip("MySQL always indexes foreign keys") if mysql?
|
63
|
-
|
64
|
-
create_table(:companies)
|
65
|
-
create_table(:users) do |t|
|
66
|
-
t.references :company, foreign_key: true, index: false
|
67
|
-
end
|
68
|
-
|
69
|
-
config_file(<<-CONFIG)
|
70
|
-
ActiveRecordDoctor.configure do |config|
|
71
|
-
config.detector :unindexed_foreign_keys,
|
72
|
-
ignore_columns: ["users.company_id"]
|
73
|
-
end
|
74
|
-
CONFIG
|
75
|
-
|
76
|
-
refute_problems
|
77
|
-
end
|
78
|
-
end
|