active_record_doctor 1.12.0 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -2
  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/rake/task.rb +35 -3
  14. data/lib/active_record_doctor/runner.rb +1 -1
  15. data/lib/active_record_doctor/utils.rb +2 -2
  16. data/lib/active_record_doctor/version.rb +1 -1
  17. data/lib/tasks/active_record_doctor.rake +1 -2
  18. metadata +12 -49
  19. data/test/active_record_doctor/config/loader_test.rb +0 -120
  20. data/test/active_record_doctor/config_test.rb +0 -116
  21. data/test/active_record_doctor/detectors/disable_test.rb +0 -30
  22. data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +0 -277
  23. data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +0 -79
  24. data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +0 -511
  25. data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +0 -107
  26. data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +0 -116
  27. data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +0 -70
  28. data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +0 -273
  29. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +0 -232
  30. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +0 -496
  31. data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +0 -77
  32. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +0 -55
  33. data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +0 -177
  34. data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +0 -116
  35. data/test/active_record_doctor/runner_test.rb +0 -41
  36. data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +0 -141
  37. 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