active_record_doctor 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -0
  3. data/lib/active_record_doctor/config/loader.rb +1 -1
  4. data/lib/active_record_doctor/detectors/base.rb +11 -7
  5. data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +1 -1
  6. data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +9 -4
  7. data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +1 -1
  8. data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +2 -2
  9. data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +16 -7
  10. data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +1 -0
  11. data/lib/active_record_doctor/logger/hierarchical.rb +1 -1
  12. data/lib/active_record_doctor/railtie.rb +1 -1
  13. data/lib/active_record_doctor/runner.rb +1 -1
  14. data/lib/active_record_doctor/utils.rb +2 -2
  15. data/lib/active_record_doctor/version.rb +1 -1
  16. data/lib/tasks/active_record_doctor.rake +2 -2
  17. metadata +12 -49
  18. data/test/active_record_doctor/config/loader_test.rb +0 -120
  19. data/test/active_record_doctor/config_test.rb +0 -116
  20. data/test/active_record_doctor/detectors/disable_test.rb +0 -30
  21. data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +0 -277
  22. data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +0 -79
  23. data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +0 -511
  24. data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +0 -107
  25. data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +0 -116
  26. data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +0 -70
  27. data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +0 -273
  28. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +0 -232
  29. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +0 -496
  30. data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +0 -77
  31. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +0 -55
  32. data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +0 -177
  33. data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +0 -116
  34. data/test/active_record_doctor/runner_test.rb +0 -41
  35. data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +0 -141
  36. data/test/setup.rb +0 -124
@@ -1,496 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
4
- def test_missing_unique_index
5
- create_table(:users) do |t|
6
- t.string :email
7
- t.index :email
8
- end.define_model do
9
- validates :email, uniqueness: true
10
- end
11
-
12
- assert_problems(<<~OUTPUT)
13
- add a unique index on users(email) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
14
- OUTPUT
15
- end
16
-
17
- def test_missing_unique_index_on_functional_index
18
- skip if !(ActiveRecord::VERSION::STRING >= "5.0" && postgresql?)
19
-
20
- create_table(:users) do |t|
21
- t.string :email
22
- t.index "lower(email)"
23
- end.define_model do
24
- validates :email, uniqueness: true
25
- end
26
-
27
- # Running the detector should NOT raise an error when a functional index
28
- # is present. No need to assert anything -- the test is successful if no
29
- # exception was raised.
30
- run_detector
31
- end
32
-
33
- def test_validates_multiple_attributes
34
- create_table(:users) do |t|
35
- t.string :email
36
- t.string :ref_token
37
- end.define_model do
38
- validates :email, :ref_token, uniqueness: true
39
- end
40
-
41
- assert_problems(<<~OUTPUT)
42
- add a unique index on users(email) - validating uniqueness in 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
- OUTPUT
45
- end
46
-
47
- def test_present_unique_index
48
- create_table(:users) do |t|
49
- t.string :email
50
- t.index :email, unique: true
51
- end.define_model do
52
- validates :email, uniqueness: true
53
- end
54
-
55
- refute_problems
56
- end
57
-
58
- def test_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
-
77
- def test_present_partial_unique_index
78
- skip("MySQL doesn't support partial indexes") if mysql?
79
-
80
- create_table(:users) do |t|
81
- t.string :email
82
- t.boolean :active
83
- t.index :email, unique: true, where: "active"
84
- end.define_model do
85
- validates :email, uniqueness: true
86
- end
87
-
88
- assert_problems(<<~OUTPUT)
89
- add a unique index on users(email) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
90
- OUTPUT
91
- end
92
-
93
- def test_unique_index_with_extra_columns_with_scope
94
- create_table(:users) do |t|
95
- t.string :email
96
- t.integer :company_id
97
- t.integer :department_id
98
- t.index [:company_id, :department_id, :email]
99
- end.define_model do
100
- validates :email, uniqueness: { scope: [:company_id, :department_id] }
101
- end
102
-
103
- assert_problems(<<~OUTPUT)
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
105
- OUTPUT
106
- end
107
-
108
- def test_unique_index_with_exact_columns_with_scope
109
- create_table(:users) do |t|
110
- t.string :email
111
- t.integer :company_id
112
- t.integer :department_id
113
- t.index [:company_id, :department_id, :email], unique: true
114
- end.define_model do
115
- validates :email, uniqueness: { scope: [:company_id, :department_id] }
116
- end
117
-
118
- refute_problems
119
- end
120
-
121
- def test_unique_index_with_fewer_columns_with_scope
122
- create_table(:users) do |t|
123
- t.string :email
124
- t.integer :company_id
125
- t.integer :department_id
126
- t.index [:company_id, :department_id], unique: true
127
- end.define_model do
128
- validates :email, uniqueness: { scope: [:company_id, :department_id] }
129
- end
130
-
131
- refute_problems
132
- end
133
-
134
- def test_missing_unique_index_with_association_attribute
135
- create_table(:users) do |t|
136
- t.integer :account_id
137
- end.define_model do
138
- belongs_to :account
139
- validates :account, uniqueness: true
140
- end
141
-
142
- assert_problems(<<~OUTPUT)
143
- add a unique index on users(account_id) - validating uniqueness in TransientRecord::Models::User without an index can lead to duplicates
144
- OUTPUT
145
- end
146
-
147
- def test_present_unique_index_with_association_attribute
148
- create_table(:users) do |t|
149
- t.integer :account_id
150
- t.index :account_id, unique: true
151
- end.define_model do
152
- belongs_to :account
153
- validates :account, uniqueness: true
154
- end
155
-
156
- refute_problems
157
- end
158
-
159
- def test_missing_unique_index_with_association_scope
160
- create_table(:comments) do |t|
161
- t.string :title
162
- t.integer :commentable_id
163
- t.string :commentable_type
164
- end.define_model do
165
- belongs_to :commentable, polymorphic: true
166
- validates :title, uniqueness: { scope: :commentable }
167
- end
168
-
169
- assert_problems(<<~OUTPUT)
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
171
- OUTPUT
172
- end
173
-
174
- def test_present_unique_index_with_association_scope
175
- create_table(:comments) do |t|
176
- t.string :title
177
- t.integer :commentable_id
178
- t.string :commentable_type
179
- t.index [:commentable_id, :commentable_type, :title], unique: true
180
- end.define_model do
181
- belongs_to :commentable, polymorphic: true
182
- validates :title, uniqueness: { scope: :commentable }
183
- end
184
-
185
- refute_problems
186
- end
187
-
188
- def test_column_order_is_ignored
189
- create_table(:users) do |t|
190
- t.string :email
191
- t.integer :organization_id
192
-
193
- t.index [:email, :organization_id], unique: true
194
- end.define_model do
195
- validates :email, uniqueness: { scope: :organization_id }
196
- end
197
-
198
- refute_problems
199
- end
200
-
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
215
- end
216
-
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) })
307
- end
308
-
309
- def test_if_is_skipped
310
- assert_skipped(if: ->(_model) { true })
311
- end
312
-
313
- def test_unless_is_skipped
314
- assert_skipped(unless: ->(_model) { true })
315
- end
316
-
317
- def test_skips_validator_without_attributes
318
- create_table(:users) do |t|
319
- t.string :email
320
- t.index :email
321
- end.define_model do
322
- validates_with DummyValidator
323
- end
324
-
325
- refute_problems
326
- end
327
-
328
- def test_has_one_without_index
329
- create_table(:users)
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"
333
- end
334
-
335
- create_table(:accounts) do |t|
336
- t.integer :user_id
337
- end.define_model do
338
- has_one :account_history, class_name: "TransientRecord::Models::AccountHistory"
339
- end
340
-
341
- create_table(:account_histories) do |t|
342
- t.integer :account_id
343
- end.define_model do
344
- belongs_to :account, class_name: "TransientRecord::Models::Account"
345
- end
346
-
347
- assert_problems(<<~OUTPUT)
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
350
- OUTPUT
351
- end
352
-
353
- def test_has_one_with_scope_and_without_index
354
- create_table(:users)
355
- .define_model do
356
- has_one :last_comment, -> { order(created_at: :desc) }, class_name: "TransientRecord::Models::Comment"
357
- end
358
-
359
- create_table(:comments) do |t|
360
- t.integer :user_id
361
- end.define_model
362
-
363
- refute_problems
364
- end
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
-
384
- def test_has_one_with_index
385
- create_table(:users)
386
- .define_model do
387
- has_one :account, class_name: "TransientRecord::Models::Account"
388
- end
389
-
390
- create_table(:accounts) do |t|
391
- t.integer :user_id, index: { unique: true }
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
425
-
426
- refute_problems
427
- end
428
-
429
- def test_config_ignore_models
430
- create_table(:users) do |t|
431
- t.string :email
432
- end.define_model do
433
- validates :email, uniqueness: true
434
- end
435
-
436
- config_file(<<-CONFIG)
437
- ActiveRecordDoctor.configure do |config|
438
- config.detector :missing_unique_indexes,
439
- ignore_models: ["TransientRecord::Models::User"]
440
- end
441
- CONFIG
442
-
443
- refute_problems
444
- end
445
-
446
- def test_global_ignore_models
447
- create_table(:users) do |t|
448
- t.string :email
449
- end.define_model do
450
- validates :email, uniqueness: true
451
- end
452
-
453
- config_file(<<-CONFIG)
454
- ActiveRecordDoctor.configure do |config|
455
- config.global :ignore_models, ["TransientRecord::Models::User"]
456
- end
457
- CONFIG
458
-
459
- refute_problems
460
- end
461
-
462
- def test_config_ignore_columns
463
- create_table(:users) do |t|
464
- t.string :email
465
- t.integer :role
466
- end.define_model do
467
- validates :email, :role, uniqueness: { scope: :organization_id }
468
- end
469
-
470
- config_file(<<-CONFIG)
471
- ActiveRecordDoctor.configure do |config|
472
- config.detector :missing_unique_indexes,
473
- ignore_columns: ["TransientRecord::Models::User(organization_id, email)", "TransientRecord::Models::User(organization_id, role)"]
474
- end
475
- CONFIG
476
-
477
- refute_problems
478
- end
479
-
480
- class DummyValidator < ActiveModel::Validator
481
- def validate(record)
482
- end
483
- end
484
-
485
- private
486
-
487
- def assert_skipped(options)
488
- create_table(:users) do |t|
489
- t.string :email
490
- end.define_model do
491
- validates :email, uniqueness: options
492
- end
493
-
494
- refute_problems
495
- end
496
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::ShortPrimaryKeyTypeTest < Minitest::Test
4
- def setup
5
- @connection = ActiveRecord::Base.connection
6
- @connection.enable_extension("uuid-ossp") if postgresql?
7
- super
8
- end
9
-
10
- def teardown
11
- @connection.disable_extension("uuid-ossp") if postgresql?
12
- super
13
- end
14
-
15
- def test_short_integer_primary_key_is_reported
16
- create_table(:companies, id: :int)
17
-
18
- # In Rails 4.2 and MySQL primary key is not created due to a bug
19
- if mysql? && ActiveRecord::VERSION::STRING < "5.0"
20
- @connection.execute("ALTER TABLE companies ADD PRIMARY KEY(id)")
21
- end
22
-
23
- assert_problems(<<~OUTPUT)
24
- change the type of companies.id to bigint
25
- OUTPUT
26
- end
27
-
28
- def test_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
-
33
- def test_long_integer_primary_key_is_not_reported
34
- create_table(:companies, id: :bigint)
35
- refute_problems
36
- end
37
-
38
- def test_uuid_primary_key_is_not_reported
39
- skip unless postgresql?
40
-
41
- create_table(:companies, id: :uuid)
42
- refute_problems
43
- end
44
-
45
- def test_no_primary_key_is_not_reported
46
- create_table(:companies, id: false) do |t|
47
- t.string :name, null: false
48
- end
49
-
50
- refute_problems
51
- end
52
-
53
- def test_config_ignore_tables
54
- create_table(:companies, id: :integer)
55
-
56
- config_file(<<-CONFIG)
57
- ActiveRecordDoctor.configure do |config|
58
- config.detector :short_primary_key_type,
59
- ignore_tables: ["companies"]
60
- end
61
- CONFIG
62
-
63
- refute_problems
64
- end
65
-
66
- def test_global_ignore_tables
67
- create_table(:companies, id: :integer)
68
-
69
- config_file(<<-CONFIG)
70
- ActiveRecordDoctor.configure do |config|
71
- config.global :ignore_tables, ["companies"]
72
- end
73
- CONFIG
74
-
75
- refute_problems
76
- end
77
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Test
4
- def test_model_backed_by_table
5
- create_table(:users) do
6
- end.define_model do
7
- end
8
-
9
- refute_problems
10
- end
11
-
12
- def test_model_backed_by_non_existent_table
13
- define_model(:User)
14
-
15
- assert_problems(<<~OUTPUT)
16
- TransientRecord::Models::User references a non-existent table or view named users
17
- OUTPUT
18
- end
19
-
20
- def test_model_backed_by_view
21
- # We replace the underlying table with a view. The view doesn't have to be
22
- # backed by an actual table - it can simply return a predefined tuple.
23
- ActiveRecord::Base.connection.execute("CREATE VIEW users AS SELECT 1")
24
- define_model(:User)
25
-
26
- refute_problems
27
- ensure
28
- ActiveRecord::Base.connection.execute("DROP VIEW users")
29
- end
30
-
31
- def test_config_ignore_tables
32
- define_model(:User)
33
-
34
- config_file(<<-CONFIG)
35
- ActiveRecordDoctor.configure do |config|
36
- config.detector :undefined_table_references,
37
- ignore_models: ["TransientRecord::Models::User"]
38
- end
39
- CONFIG
40
-
41
- refute_problems
42
- end
43
-
44
- def test_global_ignore_tables
45
- define_model(:User)
46
-
47
- config_file(<<-CONFIG)
48
- ActiveRecordDoctor.configure do |config|
49
- config.global :ignore_models, ["TransientRecord::Models::User"]
50
- end
51
- CONFIG
52
-
53
- refute_problems
54
- end
55
- end