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.
- checksums.yaml +4 -4
- data/README.md +15 -15
- data/lib/active_record_doctor/detectors/base.rb +194 -53
- data/lib/active_record_doctor/detectors/extraneous_indexes.rb +36 -34
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +2 -5
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +87 -37
- data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +7 -10
- data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +16 -9
- data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +2 -4
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +13 -11
- data/lib/active_record_doctor/detectors/missing_presence_validation.rb +14 -7
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +70 -35
- data/lib/active_record_doctor/detectors/short_primary_key_type.rb +4 -4
- data/lib/active_record_doctor/detectors/undefined_table_references.rb +2 -2
- data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +5 -13
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +35 -11
- data/lib/active_record_doctor/logger/dummy.rb +11 -0
- data/lib/active_record_doctor/logger/hierarchical.rb +22 -0
- data/lib/active_record_doctor/logger.rb +6 -0
- data/lib/active_record_doctor/rake/task.rb +10 -1
- data/lib/active_record_doctor/runner.rb +8 -3
- data/lib/active_record_doctor/utils.rb +21 -0
- data/lib/active_record_doctor/version.rb +1 -1
- data/lib/active_record_doctor.rb +5 -0
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +14 -14
- data/test/active_record_doctor/detectors/disable_test.rb +1 -1
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +59 -6
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +7 -7
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +175 -57
- data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +16 -14
- data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +35 -1
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +46 -23
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +55 -27
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +216 -47
- data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +5 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +11 -13
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +39 -1
- data/test/active_record_doctor/runner_test.rb +18 -19
- data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +16 -6
- data/test/setup.rb +10 -6
- metadata +23 -7
- 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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
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.
|
128
|
+
end.define_model
|
106
129
|
|
107
|
-
|
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.
|
143
|
+
end.define_model
|
121
144
|
|
122
|
-
|
145
|
+
define_model(:Client, TransientRecord::Models::User) do
|
123
146
|
validates :email, presence: true
|
124
147
|
end
|
125
148
|
|
126
|
-
|
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.
|
159
|
+
end.define_model do
|
137
160
|
validates :email, presence: true
|
138
161
|
end
|
139
162
|
|
140
|
-
|
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.
|
177
|
+
end.define_model do
|
155
178
|
validates :email, presence: true
|
156
179
|
end
|
157
180
|
|
158
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
16
|
+
end.define_model do
|
17
17
|
end
|
18
18
|
|
19
19
|
assert_problems(<<~OUTPUT)
|
20
|
-
add a `presence` validator to
|
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.
|
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).
|
35
|
+
create_table(:companies).define_model
|
36
36
|
create_table(:users) do |t|
|
37
37
|
t.references :company, null: false
|
38
|
-
end.
|
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).
|
47
|
-
has_and_belongs_to_many :projects, class_name: "
|
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).
|
56
|
-
has_and_belongs_to_many :users, class_name: "
|
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.
|
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
|
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.
|
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.
|
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.
|
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
|
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.
|
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
|
-
|
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.
|
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
|
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.
|
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.
|
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: ["
|
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.
|
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, ["
|
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.
|
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: ["
|
226
|
+
ignore_attributes: ["TransientRecord::Models::User.name"]
|
199
227
|
end
|
200
228
|
CONFIG
|
201
229
|
|