active_record_doctor 1.9.0 → 1.11.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +83 -19
  3. data/lib/active_record_doctor/config/default.rb +17 -0
  4. data/lib/active_record_doctor/detectors/base.rb +216 -56
  5. data/lib/active_record_doctor/detectors/extraneous_indexes.rb +38 -56
  6. data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +2 -6
  7. data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +88 -15
  8. data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +60 -0
  9. data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +16 -9
  10. data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +2 -4
  11. data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +14 -11
  12. data/lib/active_record_doctor/detectors/missing_presence_validation.rb +16 -10
  13. data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +61 -17
  14. data/lib/active_record_doctor/detectors/short_primary_key_type.rb +6 -2
  15. data/lib/active_record_doctor/detectors/undefined_table_references.rb +2 -4
  16. data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +6 -15
  17. data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +2 -4
  18. data/lib/active_record_doctor/logger/dummy.rb +11 -0
  19. data/lib/active_record_doctor/logger/hierarchical.rb +22 -0
  20. data/lib/active_record_doctor/logger.rb +6 -0
  21. data/lib/active_record_doctor/rake/task.rb +10 -1
  22. data/lib/active_record_doctor/runner.rb +8 -3
  23. data/lib/active_record_doctor/version.rb +1 -1
  24. data/lib/active_record_doctor.rb +4 -0
  25. data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +5 -5
  26. data/test/active_record_doctor/detectors/disable_test.rb +30 -0
  27. data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +34 -0
  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 +220 -43
  30. data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +107 -0
  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 +78 -21
  33. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +89 -25
  34. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +179 -15
  35. data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +27 -19
  36. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +11 -13
  37. data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +9 -3
  38. data/test/active_record_doctor/runner_test.rb +18 -19
  39. data/test/setup.rb +15 -7
  40. metadata +25 -5
  41. 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
@@ -164,10 +187,44 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
164
187
  refute_problems
165
188
  end
166
189
 
190
+ def test_not_null_check_constraint
191
+ skip unless postgresql?
192
+
193
+ create_table(:users) do |t|
194
+ t.string :email
195
+ end.define_model do
196
+ validates :email, presence: true
197
+ end
198
+
199
+ ActiveRecord::Base.connection.execute(<<-SQL)
200
+ ALTER TABLE users ADD CONSTRAINT email_not_null CHECK (email IS NOT NULL)
201
+ SQL
202
+
203
+ refute_problems
204
+ end
205
+
206
+ def test_not_null_check_constraint_not_valid
207
+ skip unless postgresql?
208
+
209
+ create_table(:users) do |t|
210
+ t.string :email
211
+ end.define_model do
212
+ validates :email, presence: true
213
+ end
214
+
215
+ ActiveRecord::Base.connection.execute(<<-SQL)
216
+ ALTER TABLE users ADD CONSTRAINT email_not_null CHECK (email IS NOT NULL) NOT VALID
217
+ SQL
218
+
219
+ assert_problems(<<~OUTPUT)
220
+ add `NOT NULL` to users.email - models validates its presence but it's not non-NULL in the database
221
+ OUTPUT
222
+ end
223
+
167
224
  def test_config_ignore_tables
168
225
  create_table(:users) do |t|
169
226
  t.string :name, null: true
170
- end.create_model do
227
+ end.define_model do
171
228
  validates :name, presence: true
172
229
  end
173
230
 
@@ -184,7 +241,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
184
241
  def test_global_ignore_tables
185
242
  create_table(:users) do |t|
186
243
  t.string :name, null: true
187
- end.create_model do
244
+ end.define_model do
188
245
  validates :name, presence: true
189
246
  end
190
247
 
@@ -200,7 +257,7 @@ class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Te
200
257
  def test_config_ignore_columns
201
258
  create_table(:users) do |t|
202
259
  t.string :name, null: true
203
- end.create_model do
260
+ end.define_model do
204
261
  validates :name, presence: true
205
262
  end
206
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,37 +84,101 @@ 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
 
106
126
  def test_timestamps_are_not_reported
107
127
  create_table(:users) do |t|
128
+ # Create created_at/updated_at timestamps.
108
129
  t.timestamps null: false
109
- end.create_model do
110
- validates :name, presence: true
130
+
131
+ # Rails also supports created_on/updated_on. We used datetime, which is
132
+ # what the timestamps method users under the hood, to avoid default value
133
+ # errors in some MySQL versions when using t.timestamp.
134
+ t.datetime :created_on, null: false
135
+ t.datetime :updated_on, null: false
136
+ end.define_model do
111
137
  end
112
138
 
113
139
  refute_problems
114
140
  end
115
141
 
116
142
  def test_models_with_non_existent_tables_are_skipped
117
- create_model(:User)
143
+ define_model(:User)
144
+
145
+ refute_problems
146
+ end
147
+
148
+ def test_not_null_check_constraint
149
+ skip unless postgresql?
150
+
151
+ create_table(:users) do |t|
152
+ t.string :name
153
+ end.define_model
154
+
155
+ ActiveRecord::Base.connection.execute(<<-SQL)
156
+ ALTER TABLE users ADD CONSTRAINT name_not_null CHECK (name IS NOT NULL)
157
+ SQL
158
+
159
+ assert_problems(<<~OUTPUT)
160
+ add a `presence` validator to TransientRecord::Models::User.name - it's NOT NULL but lacks a validator
161
+ OUTPUT
162
+ end
163
+
164
+ def test_not_null_check_constraint_not_valid
165
+ skip unless postgresql?
166
+
167
+ create_table(:users) do |t|
168
+ t.string :name
169
+ end.define_model
170
+
171
+ ActiveRecord::Base.connection.execute(<<-SQL)
172
+ ALTER TABLE users ADD CONSTRAINT name_not_null CHECK (name IS NOT NULL) NOT VALID
173
+ SQL
174
+
175
+ refute_problems
176
+ end
177
+
178
+ def test_abstract_class
179
+ define_model(:ApplicationRecord) do
180
+ self.abstract_class = true
181
+ end
118
182
 
119
183
  refute_problems
120
184
  end
@@ -122,13 +186,13 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
122
186
  def test_config_ignore_models
123
187
  create_table(:users) do |t|
124
188
  t.string :name, null: false
125
- end.create_model do
189
+ end.define_model do
126
190
  end
127
191
 
128
192
  config_file(<<-CONFIG)
129
193
  ActiveRecordDoctor.configure do |config|
130
194
  config.detector :missing_presence_validation,
131
- ignore_models: ["ModelFactory::Models::User"]
195
+ ignore_models: ["TransientRecord::Models::User"]
132
196
  end
133
197
  CONFIG
134
198
 
@@ -138,12 +202,12 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
138
202
  def test_global_ignore_models
139
203
  create_table(:users) do |t|
140
204
  t.string :name, null: false
141
- end.create_model do
205
+ end.define_model do
142
206
  end
143
207
 
144
208
  config_file(<<-CONFIG)
145
209
  ActiveRecordDoctor.configure do |config|
146
- config.global :ignore_models, ["ModelFactory::Models::User"]
210
+ config.global :ignore_models, ["TransientRecord::Models::User"]
147
211
  end
148
212
  CONFIG
149
213
 
@@ -153,13 +217,13 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
153
217
  def test_config_ignore_attributes
154
218
  create_table(:users) do |t|
155
219
  t.string :name, null: false
156
- end.create_model do
220
+ end.define_model do
157
221
  end
158
222
 
159
223
  config_file(<<-CONFIG)
160
224
  ActiveRecordDoctor.configure do |config|
161
225
  config.detector :missing_presence_validation,
162
- ignore_attributes: ["ModelFactory::Models::User.name"]
226
+ ignore_attributes: ["TransientRecord::Models::User.name"]
163
227
  end
164
228
  CONFIG
165
229