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.
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