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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -15
  3. data/lib/active_record_doctor/detectors/base.rb +194 -53
  4. data/lib/active_record_doctor/detectors/extraneous_indexes.rb +36 -34
  5. data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +2 -5
  6. data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +87 -37
  7. data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +7 -10
  8. data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +16 -9
  9. data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +2 -4
  10. data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +13 -11
  11. data/lib/active_record_doctor/detectors/missing_presence_validation.rb +14 -7
  12. data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +70 -35
  13. data/lib/active_record_doctor/detectors/short_primary_key_type.rb +4 -4
  14. data/lib/active_record_doctor/detectors/undefined_table_references.rb +2 -2
  15. data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +5 -13
  16. data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +35 -11
  17. data/lib/active_record_doctor/logger/dummy.rb +11 -0
  18. data/lib/active_record_doctor/logger/hierarchical.rb +22 -0
  19. data/lib/active_record_doctor/logger.rb +6 -0
  20. data/lib/active_record_doctor/rake/task.rb +10 -1
  21. data/lib/active_record_doctor/runner.rb +8 -3
  22. data/lib/active_record_doctor/utils.rb +21 -0
  23. data/lib/active_record_doctor/version.rb +1 -1
  24. data/lib/active_record_doctor.rb +5 -0
  25. data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +14 -14
  26. data/test/active_record_doctor/detectors/disable_test.rb +1 -1
  27. data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +59 -6
  28. data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +7 -7
  29. data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +175 -57
  30. data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +16 -14
  31. data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +35 -1
  32. data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +46 -23
  33. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +55 -27
  34. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +216 -47
  35. data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +5 -0
  36. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +11 -13
  37. data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +39 -1
  38. data/test/active_record_doctor/runner_test.rb +18 -19
  39. data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +16 -6
  40. data/test/setup.rb +10 -6
  41. metadata +23 -7
  42. 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.create_model do
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 the model without an index can lead to duplicates
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.create_model do
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.create_model do
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 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
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.create_model do
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.create_model do
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 the model without an index can lead to duplicates
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.create_model do
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 the model without an index can lead to duplicates
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.create_model do
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.create_model do
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.create_model do
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 the model without an index can lead to duplicates
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.create_model do
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.create_model do
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 the model without an index can lead to duplicates
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.create_model do
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.create_model do
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 test_conditions_is_skipped
183
- assert_skipped(conditions: -> { where.not(email: nil) })
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 test_case_insensitive_is_skipped
187
- assert_skipped(case_sensitive: false)
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.create_model do
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
- .create_model do
212
- has_one :account, class_name: "ModelFactory::Models::Account"
213
- has_one :account_history, through: :account, class_name: "ModelFactory::Models::Account"
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.create_model do
219
- has_one :account_history, class_name: "ModelFactory::Models::AccountHistory"
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.create_model do
225
- belongs_to :account, class_name: "ModelFactory::Models::Account"
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 the ModelFactory::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 ModelFactory::Models::Account model without an index can lead to duplicates
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
- .create_model do
237
- has_one :last_comment, -> { order(created_at: :desc) }, class_name: "ModelFactory::Models::Comment"
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.create_model
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
- .create_model do
250
- has_one :account, class_name: "ModelFactory::Models::Account"
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.create_model
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.create_model do
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: ["ModelFactory::Models::User"]
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.create_model do
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, ["ModelFactory::Models::User"]
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.create_model do
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: ["ModelFactory::Models::User(organization_id, email)", "ModelFactory::Models::User(organization_id, role)"]
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.create_model do
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.create_model do
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
- create_model(:User)
13
+ define_model(:User)
14
14
 
15
15
  assert_problems(<<~OUTPUT)
16
- ModelFactory::Models::User references a non-existent table or view named users
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
- create_model(:User)
24
+ define_model(:User)
25
25
 
26
- begin
27
- refute_problems
28
- ensure
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
- create_model(:User)
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: ["ModelFactory::Models::User"]
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
- create_model(:User)
45
+ define_model(:User)
48
46
 
49
47
  config_file(<<-CONFIG)
50
48
  ActiveRecordDoctor.configure do |config|
51
- config.global :ignore_models, ["ModelFactory::Models::User"]
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.company_id - foreign keys are often used in database lookups and should be indexed for performance reasons
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 test_run_one_raises_on_unknown_detectors
5
- io = StringIO.new
6
- runner = ActiveRecordDoctor::Runner.new(load_config, io)
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
- io = StringIO.new
15
- runner = ActiveRecordDoctor::Runner.new(load_config, io)
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
- create_model(:User)
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 = StringIO.new
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