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
@@ -4,7 +4,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
4
4
  def test_optional_columns_with_presence_validator_are_disallowed
5
5
  create_table(:users) do |t|
6
6
  t.string :name, null: true
7
- end.create_model do
7
+ end.define_model do
8
8
  validates :name, presence: true
9
9
  end
10
10
 
@@ -17,7 +17,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
17
17
  create_table(:companies)
18
18
  create_table(:users) do |t|
19
19
  t.references :company, null: true
20
- end.create_model do
20
+ end.define_model do
21
21
  belongs_to :company, required: true
22
22
  end
23
23
 
@@ -26,10 +26,33 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
26
26
  OUTPUT
27
27
  end
28
28
 
29
+ def test_optional_columns_with_required_polymorphic_association_are_disallowed
30
+ create_table(:comments) do |t|
31
+ t.references :commentable, polymorphic: true, null: true
32
+ end.define_model do
33
+ belongs_to :commentable, polymorphic: true, required: true
34
+ end
35
+
36
+ assert_problems(<<~OUTPUT)
37
+ add `NOT NULL` to comments.commentable_id - models validates its presence but it's not non-NULL in the database
38
+ add `NOT NULL` to comments.commentable_type - models validates its presence but it's not non-NULL in the database
39
+ OUTPUT
40
+ end
41
+
42
+ def test_required_columns_with_required_polymorphic_association_are_allowed
43
+ create_table(:comments) do |t|
44
+ t.references :commentable, polymorphic: true, null: false
45
+ end.define_model do
46
+ belongs_to :commentable, polymorphic: true, required: true
47
+ end
48
+
49
+ refute_problems
50
+ end
51
+
29
52
  def test_required_columns_with_presence_validators_are_allowed
30
53
  create_table(:users) do |t|
31
54
  t.string :name, null: false
32
- end.create_model do
55
+ end.define_model do
33
56
  validates :name, presence: true
34
57
  end
35
58
 
@@ -39,7 +62,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
39
62
  def test_optional_columns_without_presence_validator_are_allowed
40
63
  create_table(:users) do |t|
41
64
  t.string :name, null: false
42
- end.create_model do
65
+ end.define_model do
43
66
  validates :name, presence: false
44
67
  end
45
68
 
@@ -49,7 +72,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
49
72
  def test_validators_matched_to_correct_columns
50
73
  create_table(:users) do |t|
51
74
  t.string :name, null: true
52
- end.create_model do
75
+ end.define_model do
53
76
  # The age validator is a form of regression test against a bug that
54
77
  # caused false positives. In this test case, name is NOT validated
55
78
  # for presence so it does NOT need be marked non-NULL. However, the
@@ -65,7 +88,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
65
88
  def test_validators_with_if_on_optional_columns_are_allowed
66
89
  create_table(:users) do |t|
67
90
  t.string :name, null: true
68
- end.create_model do
91
+ end.define_model do
69
92
  validates :name, presence: true, if: -> { false }
70
93
  end
71
94
 
@@ -75,7 +98,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
75
98
  def test_validators_with_unless_on_optional_columns_are_allowed
76
99
  create_table(:users) do |t|
77
100
  t.string :name, null: true
78
- end.create_model do
101
+ end.define_model do
79
102
  validates :name, presence: true, unless: -> { false }
80
103
  end
81
104
 
@@ -85,7 +108,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
85
108
  def test_validators_allowing_nil_on_optional_columns_are_allowed
86
109
  create_table(:users) do |t|
87
110
  t.string :name, null: true
88
- end.create_model do
111
+ end.define_model do
89
112
  validates :name, presence: true, allow_nil: true
90
113
  end
91
114
 
@@ -93,7 +116,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
93
116
  end
94
117
 
95
118
  def test_models_with_non_existent_tables_are_skipped
96
- create_model(:User)
119
+ define_model(:User)
97
120
 
98
121
  refute_problems
99
122
  end
@@ -102,9 +125,9 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
102
125
  create_table(:users) do |t|
103
126
  t.string :type, null: false
104
127
  t.string :email, null: true
105
- end.create_model
128
+ end.define_model
106
129
 
107
- create_model(:Client, ModelFactory::Models::User) do
130
+ define_model(:Client, TransientRecord::Models::User) do
108
131
  validates :email, presence: true
109
132
  end
110
133
 
@@ -117,13 +140,13 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
117
140
  create_table(:users) do |t|
118
141
  t.string :type, null: false
119
142
  t.string :email, null: true
120
- end.create_model
143
+ end.define_model
121
144
 
122
- create_model(:Client, ModelFactory::Models::User) do
145
+ define_model(:Client, TransientRecord::Models::User) do
123
146
  validates :email, presence: true
124
147
  end
125
148
 
126
- create_model(:Admin, ModelFactory::Models::User) do
149
+ define_model(:Admin, TransientRecord::Models::User) do
127
150
  validates :email, presence: false
128
151
  end
129
152
 
@@ -133,11 +156,11 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
133
156
  def test_optional_columns_validated_by_all_non_sti_models_are_disallowed
134
157
  create_table(:users) do |t|
135
158
  t.string :email, null: true
136
- end.create_model do
159
+ end.define_model do
137
160
  validates :email, presence: true
138
161
  end
139
162
 
140
- create_model(:Client) do
163
+ define_model(:Client) do
141
164
  self.table_name = :users
142
165
 
143
166
  validates :email, presence: true
@@ -151,11 +174,11 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
151
174
  def test_optional_columns_validated_by_some_non_sti_models_are_allowed
152
175
  create_table(:users) do |t|
153
176
  t.string :email, null: true
154
- end.create_model do
177
+ end.define_model do
155
178
  validates :email, presence: true
156
179
  end
157
180
 
158
- create_model(:Client) do
181
+ define_model(:Client) do
159
182
  self.table_name = :users
160
183
 
161
184
  validates :email, presence: false
@@ -169,7 +192,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
169
192
 
170
193
  create_table(:users) do |t|
171
194
  t.string :email
172
- end.create_model do
195
+ end.define_model do
173
196
  validates :email, presence: true
174
197
  end
175
198
 
@@ -185,7 +208,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
185
208
 
186
209
  create_table(:users) do |t|
187
210
  t.string :email
188
- end.create_model do
211
+ end.define_model do
189
212
  validates :email, presence: true
190
213
  end
191
214
 
@@ -201,7 +224,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
201
224
  def test_config_ignore_tables
202
225
  create_table(:users) do |t|
203
226
  t.string :name, null: true
204
- end.create_model do
227
+ end.define_model do
205
228
  validates :name, presence: true
206
229
  end
207
230
 
@@ -218,7 +241,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
218
241
  def test_global_ignore_tables
219
242
  create_table(:users) do |t|
220
243
  t.string :name, null: true
221
- end.create_model do
244
+ end.define_model do
222
245
  validates :name, presence: true
223
246
  end
224
247
 
@@ -234,7 +257,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
234
257
  def test_config_ignore_columns
235
258
  create_table(:users) do |t|
236
259
  t.string :name, null: true
237
- end.create_model do
260
+ end.define_model do
238
261
  validates :name, presence: true
239
262
  end
240
263
 
@@ -4,7 +4,7 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
4
4
  def test_null_column_is_not_reported_if_validation_absent
5
5
  create_table(:users) do |t|
6
6
  t.string :name
7
- end.create_model do
7
+ end.define_model do
8
8
  end
9
9
 
10
10
  refute_problems
@@ -13,18 +13,18 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
13
13
  def test_non_null_column_is_reported_if_validation_absent
14
14
  create_table(:users) do |t|
15
15
  t.string :name, null: false
16
- end.create_model do
16
+ end.define_model do
17
17
  end
18
18
 
19
19
  assert_problems(<<~OUTPUT)
20
- add a `presence` validator to ModelFactory::Models::User.name - it's NOT NULL but lacks a validator
20
+ add a `presence` validator to TransientRecord::Models::User.name - it's NOT NULL but lacks a validator
21
21
  OUTPUT
22
22
  end
23
23
 
24
24
  def test_non_null_column_is_not_reported_if_validation_present
25
25
  create_table(:users) do |t|
26
26
  t.string :name, null: false
27
- end.create_model do
27
+ end.define_model do
28
28
  validates :name, presence: true
29
29
  end
30
30
 
@@ -32,10 +32,10 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
32
32
  end
33
33
 
34
34
  def test_non_null_column_is_not_reported_if_association_validation_present
35
- create_table(:companies).create_model
35
+ create_table(:companies).define_model
36
36
  create_table(:users) do |t|
37
37
  t.references :company, null: false
38
- end.create_model do
38
+ end.define_model do
39
39
  belongs_to :company, required: true
40
40
  end
41
41
 
@@ -43,8 +43,8 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
43
43
  end
44
44
 
45
45
  def test_not_null_column_is_not_reported_if_habtm_association
46
- create_table(:users).create_model do
47
- has_and_belongs_to_many :projects, class_name: "ModelFactory::Models::Project"
46
+ create_table(:users).define_model do
47
+ has_and_belongs_to_many :projects, class_name: "TransientRecord::Models::Project"
48
48
  end
49
49
 
50
50
  create_table(:projects_users) do |t|
@@ -52,8 +52,8 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
52
52
  t.bigint :user_id, null: false
53
53
  end
54
54
 
55
- create_table(:projects).create_model do
56
- has_and_belongs_to_many :users, class_name: "ModelFactory::Models::User"
55
+ create_table(:projects).define_model do
56
+ has_and_belongs_to_many :users, class_name: "TransientRecord::Models::User"
57
57
  end
58
58
 
59
59
  refute_problems
@@ -62,19 +62,19 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
62
62
  def test_non_null_boolean_is_reported_if_nil_included
63
63
  create_table(:users) do |t|
64
64
  t.boolean :active, null: false
65
- end.create_model do
65
+ end.define_model do
66
66
  validates :active, inclusion: { in: [nil, true, false] }
67
67
  end
68
68
 
69
69
  assert_problems(<<~OUTPUT)
70
- add a `presence` validator to ModelFactory::Models::User.active - it's NOT NULL but lacks a validator
70
+ add a `presence` validator to TransientRecord::Models::User.active - it's NOT NULL but lacks a validator
71
71
  OUTPUT
72
72
  end
73
73
 
74
74
  def test_non_null_boolean_is_not_reported_if_nil_not_included
75
75
  create_table(:users) do |t|
76
76
  t.boolean :active, null: false
77
- end.create_model do
77
+ end.define_model do
78
78
  validates :active, inclusion: { in: [true, false] }
79
79
  end
80
80
 
@@ -84,22 +84,42 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
84
84
  def test_non_null_boolean_is_not_reported_if_nil_excluded
85
85
  create_table(:users) do |t|
86
86
  t.boolean :active, null: false
87
- end.create_model do
87
+ end.define_model do
88
88
  validates :active, exclusion: { in: [nil] }
89
89
  end
90
90
 
91
91
  refute_problems
92
92
  end
93
93
 
94
+ def test_non_null_boolean_is_not_reported_if_exclusion_is_proc
95
+ create_table(:users) do |t|
96
+ t.boolean :active, null: false
97
+ end.define_model do
98
+ validates :active, exclusion: { in: ->(_user) { [nil] } }
99
+ end
100
+
101
+ refute_problems
102
+ end
103
+
104
+ def test_non_null_boolean_is_not_reported_if_inclusion_is_proc
105
+ create_table(:users) do |t|
106
+ t.boolean :active, null: false
107
+ end.define_model do
108
+ validates :active, inclusion: { in: ->(_user) { [true, false] } }
109
+ end
110
+
111
+ refute_problems
112
+ end
113
+
94
114
  def test_non_null_boolean_is_reported_if_nil_not_excluded
95
115
  create_table(:users) do |t|
96
116
  t.boolean :active, null: false
97
- end.create_model do
117
+ end.define_model do
98
118
  validates :active, exclusion: { in: [false] }
99
119
  end
100
120
 
101
121
  assert_problems(<<~OUTPUT)
102
- add a `presence` validator to ModelFactory::Models::User.active - it's NOT NULL but lacks a validator
122
+ add a `presence` validator to TransientRecord::Models::User.active - it's NOT NULL but lacks a validator
103
123
  OUTPUT
104
124
  end
105
125
 
@@ -113,14 +133,14 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
113
133
  # errors in some MySQL versions when using t.timestamp.
114
134
  t.datetime :created_on, null: false
115
135
  t.datetime :updated_on, null: false
116
- end.create_model do
136
+ end.define_model do
117
137
  end
118
138
 
119
139
  refute_problems
120
140
  end
121
141
 
122
142
  def test_models_with_non_existent_tables_are_skipped
123
- create_model(:User)
143
+ define_model(:User)
124
144
 
125
145
  refute_problems
126
146
  end
@@ -130,14 +150,14 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
130
150
 
131
151
  create_table(:users) do |t|
132
152
  t.string :name
133
- end.create_model
153
+ end.define_model
134
154
 
135
155
  ActiveRecord::Base.connection.execute(<<-SQL)
136
156
  ALTER TABLE users ADD CONSTRAINT name_not_null CHECK (name IS NOT NULL)
137
157
  SQL
138
158
 
139
159
  assert_problems(<<~OUTPUT)
140
- add a `presence` validator to ModelFactory::Models::User.name - it's NOT NULL but lacks a validator
160
+ add a `presence` validator to TransientRecord::Models::User.name - it's NOT NULL but lacks a validator
141
161
  OUTPUT
142
162
  end
143
163
 
@@ -146,7 +166,7 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
146
166
 
147
167
  create_table(:users) do |t|
148
168
  t.string :name
149
- end.create_model
169
+ end.define_model
150
170
 
151
171
  ActiveRecord::Base.connection.execute(<<-SQL)
152
172
  ALTER TABLE users ADD CONSTRAINT name_not_null CHECK (name IS NOT NULL) NOT VALID
@@ -155,16 +175,24 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
155
175
  refute_problems
156
176
  end
157
177
 
178
+ def test_abstract_class
179
+ define_model(:ApplicationRecord) do
180
+ self.abstract_class = true
181
+ end
182
+
183
+ refute_problems
184
+ end
185
+
158
186
  def test_config_ignore_models
159
187
  create_table(:users) do |t|
160
188
  t.string :name, null: false
161
- end.create_model do
189
+ end.define_model do
162
190
  end
163
191
 
164
192
  config_file(<<-CONFIG)
165
193
  ActiveRecordDoctor.configure do |config|
166
194
  config.detector :missing_presence_validation,
167
- ignore_models: ["ModelFactory::Models::User"]
195
+ ignore_models: ["TransientRecord::Models::User"]
168
196
  end
169
197
  CONFIG
170
198
 
@@ -174,12 +202,12 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
174
202
  def test_global_ignore_models
175
203
  create_table(:users) do |t|
176
204
  t.string :name, null: false
177
- end.create_model do
205
+ end.define_model do
178
206
  end
179
207
 
180
208
  config_file(<<-CONFIG)
181
209
  ActiveRecordDoctor.configure do |config|
182
- config.global :ignore_models, ["ModelFactory::Models::User"]
210
+ config.global :ignore_models, ["TransientRecord::Models::User"]
183
211
  end
184
212
  CONFIG
185
213
 
@@ -189,13 +217,13 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
189
217
  def test_config_ignore_attributes
190
218
  create_table(:users) do |t|
191
219
  t.string :name, null: false
192
- end.create_model do
220
+ end.define_model do
193
221
  end
194
222
 
195
223
  config_file(<<-CONFIG)
196
224
  ActiveRecordDoctor.configure do |config|
197
225
  config.detector :missing_presence_validation,
198
- ignore_attributes: ["ModelFactory::Models::User.name"]
226
+ ignore_attributes: ["TransientRecord::Models::User.name"]
199
227
  end
200
228
  CONFIG
201
229