active_record_doctor 1.10.0 → 1.12.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 +15 -15
- data/lib/active_record_doctor/detectors/base.rb +194 -53
- data/lib/active_record_doctor/detectors/extraneous_indexes.rb +36 -34
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +2 -5
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +87 -37
- data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +7 -10
- data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +16 -9
- data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +2 -4
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +13 -11
- data/lib/active_record_doctor/detectors/missing_presence_validation.rb +14 -7
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +70 -35
- data/lib/active_record_doctor/detectors/short_primary_key_type.rb +4 -4
- data/lib/active_record_doctor/detectors/undefined_table_references.rb +2 -2
- data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +5 -13
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +35 -11
- data/lib/active_record_doctor/logger/dummy.rb +11 -0
- data/lib/active_record_doctor/logger/hierarchical.rb +22 -0
- data/lib/active_record_doctor/logger.rb +6 -0
- data/lib/active_record_doctor/rake/task.rb +10 -1
- data/lib/active_record_doctor/runner.rb +8 -3
- data/lib/active_record_doctor/utils.rb +21 -0
- data/lib/active_record_doctor/version.rb +1 -1
- data/lib/active_record_doctor.rb +5 -0
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +14 -14
- data/test/active_record_doctor/detectors/disable_test.rb +1 -1
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +59 -6
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +7 -7
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +175 -57
- data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +16 -14
- data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +35 -1
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +46 -23
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +55 -27
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +216 -47
- data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +5 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +11 -13
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +39 -1
- data/test/active_record_doctor/runner_test.rb +18 -19
- data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +16 -6
- data/test/setup.rb +10 -6
- metadata +23 -7
- data/test/model_factory.rb +0 -128
@@ -5,12 +5,12 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
5
5
|
create_table(:users) do |t|
|
6
6
|
t.string :email
|
7
7
|
t.index :email
|
8
|
-
end.
|
8
|
+
end.define_model do
|
9
9
|
validates :email, uniqueness: true
|
10
10
|
end
|
11
11
|
|
12
12
|
assert_problems(<<~OUTPUT)
|
13
|
-
add a unique index on users(email) - validating uniqueness in
|
13
|
+
add a unique index on users(email) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
|
14
14
|
OUTPUT
|
15
15
|
end
|
16
16
|
|
@@ -20,7 +20,7 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
20
20
|
create_table(:users) do |t|
|
21
21
|
t.string :email
|
22
22
|
t.index "lower(email)"
|
23
|
-
end.
|
23
|
+
end.define_model do
|
24
24
|
validates :email, uniqueness: true
|
25
25
|
end
|
26
26
|
|
@@ -34,13 +34,13 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
34
34
|
create_table(:users) do |t|
|
35
35
|
t.string :email
|
36
36
|
t.string :ref_token
|
37
|
-
end.
|
37
|
+
end.define_model do
|
38
38
|
validates :email, :ref_token, uniqueness: true
|
39
39
|
end
|
40
40
|
|
41
41
|
assert_problems(<<~OUTPUT)
|
42
|
-
add a unique index on users(email) - validating uniqueness in
|
43
|
-
add a unique index on users(ref_token) - validating uniqueness in
|
42
|
+
add a unique index on users(email) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
|
43
|
+
add a unique index on users(ref_token) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
|
44
44
|
OUTPUT
|
45
45
|
end
|
46
46
|
|
@@ -48,13 +48,32 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
48
48
|
create_table(:users) do |t|
|
49
49
|
t.string :email
|
50
50
|
t.index :email, unique: true
|
51
|
-
end.
|
51
|
+
end.define_model do
|
52
52
|
validates :email, uniqueness: true
|
53
53
|
end
|
54
54
|
|
55
55
|
refute_problems
|
56
56
|
end
|
57
57
|
|
58
|
+
def test_missing_unique_index_reported_only_on_base_class
|
59
|
+
create_table(:users) do |t|
|
60
|
+
t.string :type
|
61
|
+
t.string :email
|
62
|
+
t.string :name
|
63
|
+
end.define_model do
|
64
|
+
validates :email, uniqueness: true
|
65
|
+
end
|
66
|
+
|
67
|
+
define_model(:Client, TransientRecord::Models::User) do
|
68
|
+
validates :name, uniqueness: true
|
69
|
+
end
|
70
|
+
|
71
|
+
assert_problems(<<~OUTPUT)
|
72
|
+
add a unique index on users(email) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
|
73
|
+
add a unique index on users(name) - validating uniqueness in TransientRecord::Models::Client without an index can lead to duplicates
|
74
|
+
OUTPUT
|
75
|
+
end
|
76
|
+
|
58
77
|
def test_present_partial_unique_index
|
59
78
|
skip("MySQL doesn't support partial indexes") if mysql?
|
60
79
|
|
@@ -62,12 +81,12 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
62
81
|
t.string :email
|
63
82
|
t.boolean :active
|
64
83
|
t.index :email, unique: true, where: "active"
|
65
|
-
end.
|
84
|
+
end.define_model do
|
66
85
|
validates :email, uniqueness: true
|
67
86
|
end
|
68
87
|
|
69
88
|
assert_problems(<<~OUTPUT)
|
70
|
-
add a unique index on users(email) - validating uniqueness in
|
89
|
+
add a unique index on users(email) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
|
71
90
|
OUTPUT
|
72
91
|
end
|
73
92
|
|
@@ -77,12 +96,12 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
77
96
|
t.integer :company_id
|
78
97
|
t.integer :department_id
|
79
98
|
t.index [:company_id, :department_id, :email]
|
80
|
-
end.
|
99
|
+
end.define_model do
|
81
100
|
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
82
101
|
end
|
83
102
|
|
84
103
|
assert_problems(<<~OUTPUT)
|
85
|
-
add a unique index on users(company_id, department_id, email) - validating uniqueness in
|
104
|
+
add a unique index on users(company_id, department_id, email) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
|
86
105
|
OUTPUT
|
87
106
|
end
|
88
107
|
|
@@ -92,7 +111,7 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
92
111
|
t.integer :company_id
|
93
112
|
t.integer :department_id
|
94
113
|
t.index [:company_id, :department_id, :email], unique: true
|
95
|
-
end.
|
114
|
+
end.define_model do
|
96
115
|
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
97
116
|
end
|
98
117
|
|
@@ -105,7 +124,7 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
105
124
|
t.integer :company_id
|
106
125
|
t.integer :department_id
|
107
126
|
t.index [:company_id, :department_id], unique: true
|
108
|
-
end.
|
127
|
+
end.define_model do
|
109
128
|
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
110
129
|
end
|
111
130
|
|
@@ -115,13 +134,13 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
115
134
|
def test_missing_unique_index_with_association_attribute
|
116
135
|
create_table(:users) do |t|
|
117
136
|
t.integer :account_id
|
118
|
-
end.
|
137
|
+
end.define_model do
|
119
138
|
belongs_to :account
|
120
139
|
validates :account, uniqueness: true
|
121
140
|
end
|
122
141
|
|
123
142
|
assert_problems(<<~OUTPUT)
|
124
|
-
add a unique index on users(account_id) - validating uniqueness in
|
143
|
+
add a unique index on users(account_id) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
|
125
144
|
OUTPUT
|
126
145
|
end
|
127
146
|
|
@@ -129,7 +148,7 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
129
148
|
create_table(:users) do |t|
|
130
149
|
t.integer :account_id
|
131
150
|
t.index :account_id, unique: true
|
132
|
-
end.
|
151
|
+
end.define_model do
|
133
152
|
belongs_to :account
|
134
153
|
validates :account, uniqueness: true
|
135
154
|
end
|
@@ -142,13 +161,13 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
142
161
|
t.string :title
|
143
162
|
t.integer :commentable_id
|
144
163
|
t.string :commentable_type
|
145
|
-
end.
|
164
|
+
end.define_model do
|
146
165
|
belongs_to :commentable, polymorphic: true
|
147
166
|
validates :title, uniqueness: { scope: :commentable }
|
148
167
|
end
|
149
168
|
|
150
169
|
assert_problems(<<~OUTPUT)
|
151
|
-
add a unique index on comments(commentable_type, commentable_id, title) - validating uniqueness in
|
170
|
+
add a unique index on comments(commentable_type, commentable_id, title) - validating uniqueness in TransientRecord::Models::Comment without an index can lead to duplicates
|
152
171
|
OUTPUT
|
153
172
|
end
|
154
173
|
|
@@ -158,7 +177,7 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
158
177
|
t.integer :commentable_id
|
159
178
|
t.string :commentable_type
|
160
179
|
t.index [:commentable_id, :commentable_type, :title], unique: true
|
161
|
-
end.
|
180
|
+
end.define_model do
|
162
181
|
belongs_to :commentable, polymorphic: true
|
163
182
|
validates :title, uniqueness: { scope: :commentable }
|
164
183
|
end
|
@@ -172,19 +191,119 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
172
191
|
t.integer :organization_id
|
173
192
|
|
174
193
|
t.index [:email, :organization_id], unique: true
|
175
|
-
end.
|
194
|
+
end.define_model do
|
176
195
|
validates :email, uniqueness: { scope: :organization_id }
|
177
196
|
end
|
178
197
|
|
179
198
|
refute_problems
|
180
199
|
end
|
181
200
|
|
182
|
-
def
|
183
|
-
|
201
|
+
def test_case_insensitive_unique_index_exists
|
202
|
+
skip("Expression indexes are not supported") if ActiveRecordDoctor::Utils.expression_indexes_unsupported?
|
203
|
+
|
204
|
+
create_table(:users) do |t|
|
205
|
+
t.string :email
|
206
|
+
|
207
|
+
t.index :email, unique: true
|
208
|
+
end.define_model do
|
209
|
+
validates :email, uniqueness: { case_sensitive: false }
|
210
|
+
end
|
211
|
+
|
212
|
+
assert_problems(<<~OUTPUT)
|
213
|
+
add a unique expression index on users(lower(email)) - validating case-insensitive uniqueness in TransientRecord::Models::User without an expression index can lead to duplicates (a regular unique index is not enough)
|
214
|
+
OUTPUT
|
184
215
|
end
|
185
216
|
|
186
|
-
def
|
187
|
-
|
217
|
+
def test_case_insensitive_non_unique_lower_index_exists
|
218
|
+
skip("Expression indexes are not supported") if ActiveRecordDoctor::Utils.expression_indexes_unsupported?
|
219
|
+
|
220
|
+
create_table(:users) do |t|
|
221
|
+
t.string :email
|
222
|
+
end.define_model do
|
223
|
+
validates :email, uniqueness: { case_sensitive: false }
|
224
|
+
end
|
225
|
+
|
226
|
+
# ActiveRecord < 5 does not support expression indexes.
|
227
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
228
|
+
CREATE INDEX index_users_on_lower_email ON users ((lower(email)))
|
229
|
+
SQL
|
230
|
+
|
231
|
+
assert_problems(<<~OUTPUT)
|
232
|
+
add a unique expression index on users(lower(email)) - validating case-insensitive uniqueness in TransientRecord::Models::User without an expression index can lead to duplicates (a regular unique index is not enough)
|
233
|
+
OUTPUT
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_case_insensitive_unique_lower_index_exists
|
237
|
+
skip("Expression indexes are not supported") if ActiveRecordDoctor::Utils.expression_indexes_unsupported?
|
238
|
+
|
239
|
+
create_table(:users) do |t|
|
240
|
+
t.string :email
|
241
|
+
end.define_model do
|
242
|
+
validates :email, uniqueness: { case_sensitive: false }
|
243
|
+
end
|
244
|
+
|
245
|
+
# ActiveRecord < 5 does not support expression indexes.
|
246
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
247
|
+
CREATE UNIQUE INDEX index_users_on_lower_email ON users ((lower(email)))
|
248
|
+
SQL
|
249
|
+
|
250
|
+
refute_problems
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_case_insensitive_compound_unique_index_exists
|
254
|
+
skip("Expression indexes are not supported") if ActiveRecordDoctor::Utils.expression_indexes_unsupported?
|
255
|
+
|
256
|
+
create_table(:users) do |t|
|
257
|
+
t.string :email
|
258
|
+
t.integer :organization_id
|
259
|
+
t.index [:email, :organization_id], unique: true
|
260
|
+
end.define_model do
|
261
|
+
validates :email, uniqueness: { scope: :organization_id, case_sensitive: false }
|
262
|
+
end
|
263
|
+
|
264
|
+
assert_problems(<<~OUTPUT)
|
265
|
+
add a unique expression index on users(organization_id, lower(email)) - validating case-insensitive uniqueness in TransientRecord::Models::User without an expression index can lead to duplicates (a regular unique index is not enough)
|
266
|
+
OUTPUT
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_case_insensitive_compound_non_unique_lower_index_exists
|
270
|
+
skip("Expression indexes are not supported") if ActiveRecordDoctor::Utils.expression_indexes_unsupported?
|
271
|
+
|
272
|
+
create_table(:users) do |t|
|
273
|
+
t.string :email
|
274
|
+
t.integer :organization_id
|
275
|
+
end.define_model do
|
276
|
+
validates :email, uniqueness: { scope: :organization_id, case_sensitive: false }
|
277
|
+
end
|
278
|
+
|
279
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
280
|
+
CREATE INDEX index_users_on_lower_email_and_organization_id ON users ((lower(email)), organization_id)
|
281
|
+
SQL
|
282
|
+
|
283
|
+
assert_problems(<<~OUTPUT)
|
284
|
+
add a unique expression index on users(organization_id, lower(email)) - validating case-insensitive uniqueness in TransientRecord::Models::User without an expression index can lead to duplicates (a regular unique index is not enough)
|
285
|
+
OUTPUT
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_case_insensitive_compound_unique_lower_index_exists
|
289
|
+
skip("Expression indexes are not supported") if ActiveRecordDoctor::Utils.expression_indexes_unsupported?
|
290
|
+
|
291
|
+
create_table(:users) do |t|
|
292
|
+
t.string :email
|
293
|
+
t.integer :organization_id
|
294
|
+
end.define_model do
|
295
|
+
validates :email, uniqueness: { scope: :organization_id, case_sensitive: false }
|
296
|
+
end
|
297
|
+
|
298
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
299
|
+
CREATE UNIQUE INDEX index_users_on_lower_email_and_organization_id ON users ((lower(email)), organization_id)
|
300
|
+
SQL
|
301
|
+
|
302
|
+
refute_problems
|
303
|
+
end
|
304
|
+
|
305
|
+
def test_conditions_is_skipped
|
306
|
+
assert_skipped(conditions: -> { where.not(email: nil) })
|
188
307
|
end
|
189
308
|
|
190
309
|
def test_if_is_skipped
|
@@ -199,7 +318,7 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
199
318
|
create_table(:users) do |t|
|
200
319
|
t.string :email
|
201
320
|
t.index :email
|
202
|
-
end.
|
321
|
+
end.define_model do
|
203
322
|
validates_with DummyValidator
|
204
323
|
end
|
205
324
|
|
@@ -208,51 +327,101 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
208
327
|
|
209
328
|
def test_has_one_without_index
|
210
329
|
create_table(:users)
|
211
|
-
.
|
212
|
-
has_one :account, class_name: "
|
213
|
-
has_one :account_history, through: :account, class_name: "
|
330
|
+
.define_model do
|
331
|
+
has_one :account, class_name: "TransientRecord::Models::Account"
|
332
|
+
has_one :account_history, through: :account, class_name: "TransientRecord::Models::Account"
|
214
333
|
end
|
215
334
|
|
216
335
|
create_table(:accounts) do |t|
|
217
336
|
t.integer :user_id
|
218
|
-
end.
|
219
|
-
has_one :account_history, class_name: "
|
337
|
+
end.define_model do
|
338
|
+
has_one :account_history, class_name: "TransientRecord::Models::AccountHistory"
|
220
339
|
end
|
221
340
|
|
222
341
|
create_table(:account_histories) do |t|
|
223
342
|
t.integer :account_id
|
224
|
-
end.
|
225
|
-
belongs_to :account, class_name: "
|
343
|
+
end.define_model do
|
344
|
+
belongs_to :account, class_name: "TransientRecord::Models::Account"
|
226
345
|
end
|
227
346
|
|
228
347
|
assert_problems(<<~OUTPUT)
|
229
|
-
add a unique index on accounts(user_id) - using `has_one` in
|
230
|
-
add a unique index on account_histories(account_id) - using `has_one` in
|
348
|
+
add a unique index on accounts(user_id) - using `has_one` in TransientRecord::Models::User without an index can lead to duplicates
|
349
|
+
add a unique index on account_histories(account_id) - using `has_one` in TransientRecord::Models::Account without an index can lead to duplicates
|
231
350
|
OUTPUT
|
232
351
|
end
|
233
352
|
|
234
353
|
def test_has_one_with_scope_and_without_index
|
235
354
|
create_table(:users)
|
236
|
-
.
|
237
|
-
has_one :last_comment, -> { order(created_at: :desc) }, class_name: "
|
355
|
+
.define_model do
|
356
|
+
has_one :last_comment, -> { order(created_at: :desc) }, class_name: "TransientRecord::Models::Comment"
|
238
357
|
end
|
239
358
|
|
240
359
|
create_table(:comments) do |t|
|
241
360
|
t.integer :user_id
|
242
|
-
end.
|
361
|
+
end.define_model
|
243
362
|
|
244
363
|
refute_problems
|
245
364
|
end
|
246
365
|
|
366
|
+
def test_missing_has_one_unique_index_reported_only_on_base_class
|
367
|
+
create_table(:users) do |t|
|
368
|
+
t.string :type
|
369
|
+
end.define_model do
|
370
|
+
has_one :account, class_name: "TransientRecord::Models::Account"
|
371
|
+
end
|
372
|
+
|
373
|
+
define_model(:Client, TransientRecord::Models::User)
|
374
|
+
|
375
|
+
create_table(:accounts) do |t|
|
376
|
+
t.integer :user_id
|
377
|
+
end.define_model
|
378
|
+
|
379
|
+
assert_problems(<<~OUTPUT)
|
380
|
+
add a unique index on accounts(user_id) - using `has_one` in TransientRecord::Models::User without an index can lead to duplicates
|
381
|
+
OUTPUT
|
382
|
+
end
|
383
|
+
|
247
384
|
def test_has_one_with_index
|
248
385
|
create_table(:users)
|
249
|
-
.
|
250
|
-
has_one :account, class_name: "
|
386
|
+
.define_model do
|
387
|
+
has_one :account, class_name: "TransientRecord::Models::Account"
|
251
388
|
end
|
252
389
|
|
253
390
|
create_table(:accounts) do |t|
|
254
391
|
t.integer :user_id, index: { unique: true }
|
255
|
-
end.
|
392
|
+
end.define_model
|
393
|
+
|
394
|
+
refute_problems
|
395
|
+
end
|
396
|
+
|
397
|
+
def test_polymorphic_has_one_without_index
|
398
|
+
create_table(:users)
|
399
|
+
.define_model do
|
400
|
+
has_one :account, as: :accountable
|
401
|
+
end
|
402
|
+
|
403
|
+
create_table(:accounts) do |t|
|
404
|
+
t.belongs_to :accountable, polymorphic: true, index: false
|
405
|
+
end.define_model do
|
406
|
+
belongs_to :accountable, polymorphic: true
|
407
|
+
end
|
408
|
+
|
409
|
+
assert_problems(<<~OUTPUT)
|
410
|
+
add a unique index on accounts(accountable_type, accountable_id) - using `has_one` in TransientRecord::Models::User without an index can lead to duplicates
|
411
|
+
OUTPUT
|
412
|
+
end
|
413
|
+
|
414
|
+
def test_polymorphic_has_one_with_index
|
415
|
+
create_table(:users)
|
416
|
+
.define_model do
|
417
|
+
has_one :account, as: :accountable
|
418
|
+
end
|
419
|
+
|
420
|
+
create_table(:accounts) do |t|
|
421
|
+
t.belongs_to :accountable, polymorphic: true, index: { unique: true }
|
422
|
+
end.define_model do
|
423
|
+
belongs_to :accountable, polymorphic: true
|
424
|
+
end
|
256
425
|
|
257
426
|
refute_problems
|
258
427
|
end
|
@@ -260,14 +429,14 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
260
429
|
def test_config_ignore_models
|
261
430
|
create_table(:users) do |t|
|
262
431
|
t.string :email
|
263
|
-
end.
|
432
|
+
end.define_model do
|
264
433
|
validates :email, uniqueness: true
|
265
434
|
end
|
266
435
|
|
267
436
|
config_file(<<-CONFIG)
|
268
437
|
ActiveRecordDoctor.configure do |config|
|
269
438
|
config.detector :missing_unique_indexes,
|
270
|
-
ignore_models: ["
|
439
|
+
ignore_models: ["TransientRecord::Models::User"]
|
271
440
|
end
|
272
441
|
CONFIG
|
273
442
|
|
@@ -277,13 +446,13 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
277
446
|
def test_global_ignore_models
|
278
447
|
create_table(:users) do |t|
|
279
448
|
t.string :email
|
280
|
-
end.
|
449
|
+
end.define_model do
|
281
450
|
validates :email, uniqueness: true
|
282
451
|
end
|
283
452
|
|
284
453
|
config_file(<<-CONFIG)
|
285
454
|
ActiveRecordDoctor.configure do |config|
|
286
|
-
config.global :ignore_models, ["
|
455
|
+
config.global :ignore_models, ["TransientRecord::Models::User"]
|
287
456
|
end
|
288
457
|
CONFIG
|
289
458
|
|
@@ -294,14 +463,14 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
294
463
|
create_table(:users) do |t|
|
295
464
|
t.string :email
|
296
465
|
t.integer :role
|
297
|
-
end.
|
466
|
+
end.define_model do
|
298
467
|
validates :email, :role, uniqueness: { scope: :organization_id }
|
299
468
|
end
|
300
469
|
|
301
470
|
config_file(<<-CONFIG)
|
302
471
|
ActiveRecordDoctor.configure do |config|
|
303
472
|
config.detector :missing_unique_indexes,
|
304
|
-
ignore_columns: ["
|
473
|
+
ignore_columns: ["TransientRecord::Models::User(organization_id, email)", "TransientRecord::Models::User(organization_id, role)"]
|
305
474
|
end
|
306
475
|
CONFIG
|
307
476
|
|
@@ -318,7 +487,7 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
318
487
|
def assert_skipped(options)
|
319
488
|
create_table(:users) do |t|
|
320
489
|
t.string :email
|
321
|
-
end.
|
490
|
+
end.define_model do
|
322
491
|
validates :email, uniqueness: options
|
323
492
|
end
|
324
493
|
|
@@ -25,6 +25,11 @@ class ActiveRecordDoctor::Detectors::ShortPrimaryKeyTypeTest < Minitest::Test
|
|
25
25
|
OUTPUT
|
26
26
|
end
|
27
27
|
|
28
|
+
def test_non_integer_and_non_uuid_primary_key_is_not_reported
|
29
|
+
create_table(:companies, id: :string, primary_key: :uuid)
|
30
|
+
refute_problems
|
31
|
+
end
|
32
|
+
|
28
33
|
def test_long_integer_primary_key_is_not_reported
|
29
34
|
create_table(:companies, id: :bigint)
|
30
35
|
refute_problems
|
@@ -3,17 +3,17 @@
|
|
3
3
|
class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Test
|
4
4
|
def test_model_backed_by_table
|
5
5
|
create_table(:users) do
|
6
|
-
end.
|
6
|
+
end.define_model do
|
7
7
|
end
|
8
8
|
|
9
9
|
refute_problems
|
10
10
|
end
|
11
11
|
|
12
12
|
def test_model_backed_by_non_existent_table
|
13
|
-
|
13
|
+
define_model(:User)
|
14
14
|
|
15
15
|
assert_problems(<<~OUTPUT)
|
16
|
-
|
16
|
+
TransientRecord::Models::User references a non-existent table or view named users
|
17
17
|
OUTPUT
|
18
18
|
end
|
19
19
|
|
@@ -21,22 +21,20 @@ class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Te
|
|
21
21
|
# We replace the underlying table with a view. The view doesn't have to be
|
22
22
|
# backed by an actual table - it can simply return a predefined tuple.
|
23
23
|
ActiveRecord::Base.connection.execute("CREATE VIEW users AS SELECT 1")
|
24
|
-
|
24
|
+
define_model(:User)
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
ActiveRecord::Base.connection.execute("DROP VIEW users")
|
30
|
-
end
|
26
|
+
refute_problems
|
27
|
+
ensure
|
28
|
+
ActiveRecord::Base.connection.execute("DROP VIEW users")
|
31
29
|
end
|
32
30
|
|
33
31
|
def test_config_ignore_tables
|
34
|
-
|
32
|
+
define_model(:User)
|
35
33
|
|
36
34
|
config_file(<<-CONFIG)
|
37
35
|
ActiveRecordDoctor.configure do |config|
|
38
36
|
config.detector :undefined_table_references,
|
39
|
-
ignore_models: ["
|
37
|
+
ignore_models: ["TransientRecord::Models::User"]
|
40
38
|
end
|
41
39
|
CONFIG
|
42
40
|
|
@@ -44,11 +42,11 @@ class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Te
|
|
44
42
|
end
|
45
43
|
|
46
44
|
def test_global_ignore_tables
|
47
|
-
|
45
|
+
define_model(:User)
|
48
46
|
|
49
47
|
config_file(<<-CONFIG)
|
50
48
|
ActiveRecordDoctor.configure do |config|
|
51
|
-
config.global :ignore_models, ["
|
49
|
+
config.global :ignore_models, ["TransientRecord::Models::User"]
|
52
50
|
end
|
53
51
|
CONFIG
|
54
52
|
|
@@ -10,10 +10,48 @@ class ActiveRecordDoctor::Detectors::UnindexedForeignKeysTest < Minitest::Test
|
|
10
10
|
end
|
11
11
|
|
12
12
|
assert_problems(<<~OUTPUT)
|
13
|
-
add an index on users
|
13
|
+
add an index on users(company_id) - foreign keys are often used in database lookups and should be indexed for performance reasons
|
14
14
|
OUTPUT
|
15
15
|
end
|
16
16
|
|
17
|
+
def test_unindexed_foreign_key_with_nonstandard_name_is_reported
|
18
|
+
skip("MySQL always indexes foreign keys") if mysql?
|
19
|
+
|
20
|
+
create_table(:companies)
|
21
|
+
create_table(:users) do |t|
|
22
|
+
t.integer :company
|
23
|
+
t.foreign_key :companies, column: :company
|
24
|
+
end
|
25
|
+
|
26
|
+
assert_problems(<<~OUTPUT)
|
27
|
+
add an index on users(company) - foreign keys are often used in database lookups and should be indexed for performance reasons
|
28
|
+
OUTPUT
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_unindexed_polymorphic_foreign_key_is_reported
|
32
|
+
create_table(:notes) do |t|
|
33
|
+
t.integer :notable_id
|
34
|
+
t.string :notable_type
|
35
|
+
end
|
36
|
+
|
37
|
+
assert_problems(<<~OUTPUT)
|
38
|
+
add an index on notes(notable_type, notable_id) - foreign keys are often used in database lookups and should be indexed for performance reasons
|
39
|
+
OUTPUT
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_indexed_polymorphic_foreign_key_is_not_reported
|
43
|
+
create_table(:notes) do |t|
|
44
|
+
t.string :title
|
45
|
+
t.integer :notable_id
|
46
|
+
t.string :notable_type
|
47
|
+
|
48
|
+
# Includes additional column except `notable`
|
49
|
+
t.index [:notable_type, :notable_id, :title]
|
50
|
+
end
|
51
|
+
|
52
|
+
refute_problems
|
53
|
+
end
|
54
|
+
|
17
55
|
def test_indexed_foreign_key_is_not_reported
|
18
56
|
create_table(:companies)
|
19
57
|
create_table(:users) do |t|
|
@@ -1,42 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ActiveRecordDoctor::RunnerTest < Minitest::Test
|
4
|
-
def
|
5
|
-
io = StringIO.new
|
6
|
-
runner = ActiveRecordDoctor::Runner.new(
|
4
|
+
def setup
|
5
|
+
@io = StringIO.new
|
6
|
+
@runner = ActiveRecordDoctor::Runner.new(
|
7
|
+
config: load_config,
|
8
|
+
logger: ActiveRecordDoctor::Logger::Dummy.new,
|
9
|
+
io: @io
|
10
|
+
)
|
11
|
+
end
|
7
12
|
|
13
|
+
def test_run_one_raises_on_unknown_detectors
|
8
14
|
assert_raises(KeyError) do
|
9
|
-
runner.run_one(:performance_issues)
|
15
|
+
@runner.run_one(:performance_issues)
|
10
16
|
end
|
11
17
|
end
|
12
18
|
|
13
19
|
def test_run_all_returns_true_when_no_errors
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
assert(runner.run_all)
|
18
|
-
assert(io.string.blank?)
|
20
|
+
assert(@runner.run_all)
|
21
|
+
assert(@io.string.blank?)
|
19
22
|
end
|
20
23
|
|
21
24
|
def test_run_all_returns_false_when_errors
|
22
25
|
# Create a model without its underlying table to trigger an error.
|
23
|
-
|
24
|
-
|
25
|
-
io = StringIO.new
|
26
|
-
runner = ActiveRecordDoctor::Runner.new(load_config, io)
|
26
|
+
define_model(:User)
|
27
27
|
|
28
|
-
refute(runner.run_all)
|
29
|
-
refute(io.string.blank?)
|
28
|
+
refute(@runner.run_all)
|
29
|
+
refute(@io.string.blank?)
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_help_prints_help
|
33
33
|
ActiveRecordDoctor.detectors.each do |name, _|
|
34
|
-
io
|
35
|
-
runner = ActiveRecordDoctor::Runner.new(load_config, io)
|
34
|
+
@io.truncate(0)
|
36
35
|
|
37
|
-
runner.help(name)
|
36
|
+
@runner.help(name)
|
38
37
|
|
39
|
-
refute(io.string.blank?, "expected help for #{name}")
|
38
|
+
refute(@io.string.blank?, "expected help for #{name}")
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|