active_record_doctor 1.10.0 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|