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,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::MismatchedForeignKeyTypeTest < Minitest::Test
4
- def test_mismatched_foreign_key_type_is_reported
5
- # MySQL does not allow foreign keys to have different type than paired primary keys
6
- return if mysql?
7
-
8
- create_table(:companies, id: :bigint)
9
- create_table(:users) do |t|
10
- t.references :company, foreign_key: true, type: :integer
11
- end
12
-
13
- assert_problems(<<~OUTPUT)
14
- users.company_id is a foreign key of type integer and references companies.id of type bigint - foreign keys should be of the same type as the referenced column
15
- OUTPUT
16
- end
17
-
18
- def test_matched_foreign_key_type_is_not_reported
19
- create_table(:companies)
20
- create_table(:users) do |t|
21
- t.references :company, foreign_key: true
22
- end
23
-
24
- refute_problems
25
- end
26
-
27
- def test_mismatched_foreign_key_with_non_primary_key_type_is_reported
28
- # MySQL does not allow foreign keys to have different type than paired primary keys
29
- return if mysql?
30
-
31
- create_table(:companies, id: :bigint) do |t|
32
- t.string :code
33
- t.index :code, unique: true
34
- end
35
- create_table(:users) do |t|
36
- t.text :code
37
- t.foreign_key :companies, table: :companies, column: :code, primary_key: :code
38
- end
39
-
40
- assert_problems(<<~OUTPUT)
41
- users.code is a foreign key of type text and references companies.code of type character varying - foreign keys should be of the same type as the referenced column
42
- OUTPUT
43
- end
44
-
45
- def test_matched_foreign_key_with_non_primary_key_type_is_not_reported
46
- # MySQL does not allow foreign keys to have different type than paired primary keys
47
- return if mysql?
48
-
49
- create_table(:companies, id: :bigint) do |t|
50
- t.string :code
51
- t.index :code, unique: true
52
- end
53
- create_table(:users) do |t|
54
- t.string :code
55
- t.foreign_key :companies, table: :companies, column: :code, primary_key: :code
56
- end
57
-
58
- refute_problems
59
- end
60
-
61
- def test_config_ignore_tables
62
- # MySQL does not allow foreign keys to have different type than paired primary keys
63
- return if mysql?
64
-
65
- create_table(:companies, id: :bigint)
66
- create_table(:users) do |t|
67
- t.references :company, foreign_key: true, type: :integer
68
- end
69
-
70
- config_file(<<-CONFIG)
71
- ActiveRecordDoctor.configure do |config|
72
- config.detector :mismatched_foreign_key_type,
73
- ignore_tables: ["users"]
74
- end
75
- CONFIG
76
-
77
- refute_problems
78
- end
79
-
80
- def test_global_ignore_tables
81
- # MySQL does not allow foreign keys to have different type than paired primary keys
82
- return if mysql?
83
-
84
- create_table(:companies, id: :bigint)
85
- create_table(:users) do |t|
86
- t.references :company, foreign_key: true, type: :integer
87
- end
88
-
89
- config_file(<<-CONFIG)
90
- ActiveRecordDoctor.configure do |config|
91
- config.global :ignore_tables, ["users"]
92
- end
93
- CONFIG
94
-
95
- refute_problems
96
- end
97
-
98
- def test_config_ignore_columns
99
- # MySQL does not allow foreign keys to have different type than paired primary keys
100
- return if mysql?
101
-
102
- create_table(:companies, id: :bigint)
103
- create_table(:users) do |t|
104
- t.references :company, foreign_key: true, type: :integer
105
- end
106
-
107
- config_file(<<-CONFIG)
108
- ActiveRecordDoctor.configure do |config|
109
- config.detector :mismatched_foreign_key_type,
110
- ignore_columns: ["users.company_id"]
111
- end
112
- CONFIG
113
-
114
- refute_problems
115
- end
116
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::MissingForeignKeysTest < Minitest::Test
4
- def test_missing_foreign_key_is_reported
5
- create_table(:companies)
6
- create_table(:users) do |t|
7
- t.references :company, foreign_key: false
8
- end
9
-
10
- assert_problems(<<~OUTPUT)
11
- create a foreign key on users.company_id - looks like an association without a foreign key constraint
12
- OUTPUT
13
- end
14
-
15
- def test_present_foreign_key_is_not_reported
16
- create_table(:companies)
17
- create_table(:users) do |t|
18
- t.references :company, foreign_key: true
19
- end
20
-
21
- refute_problems
22
- end
23
-
24
- def test_config_ignore_models
25
- create_table(:companies)
26
- create_table(:users) do |t|
27
- t.references :company, foreign_key: false
28
- end
29
-
30
- config_file(<<-CONFIG)
31
- ActiveRecordDoctor.configure do |config|
32
- config.detector :missing_foreign_keys,
33
- ignore_tables: ["users"]
34
- end
35
- CONFIG
36
-
37
- refute_problems
38
- end
39
-
40
- def test_global_ignore_models
41
- create_table(:companies)
42
- create_table(:users) do |t|
43
- t.references :company, foreign_key: false
44
- end
45
-
46
- config_file(<<-CONFIG)
47
- ActiveRecordDoctor.configure do |config|
48
- config.global :ignore_tables, ["users"]
49
- end
50
- CONFIG
51
-
52
- refute_problems
53
- end
54
-
55
- def test_config_ignore_columns
56
- create_table(:companies)
57
- create_table(:users) do |t|
58
- t.references :company, foreign_key: false
59
- end
60
-
61
- config_file(<<-CONFIG)
62
- ActiveRecordDoctor.configure do |config|
63
- config.detector :missing_foreign_keys,
64
- ignore_columns: ["users.company_id"]
65
- end
66
- CONFIG
67
-
68
- refute_problems
69
- end
70
- end
@@ -1,273 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Test
4
- def test_optional_columns_with_presence_validator_are_disallowed
5
- create_table(:users) do |t|
6
- t.string :name, null: true
7
- end.define_model do
8
- validates :name, presence: true
9
- end
10
-
11
- assert_problems(<<~OUTPUT)
12
- add `NOT NULL` to users.name - models validates its presence but it's not non-NULL in the database
13
- OUTPUT
14
- end
15
-
16
- def test_optional_foreign_keys_with_required_association_are_disallowed
17
- create_table(:companies)
18
- create_table(:users) do |t|
19
- t.references :company, null: true
20
- end.define_model do
21
- belongs_to :company, required: true
22
- end
23
-
24
- assert_problems(<<~OUTPUT)
25
- add `NOT NULL` to users.company_id - models validates its presence but it's not non-NULL in the database
26
- OUTPUT
27
- end
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
-
52
- def test_required_columns_with_presence_validators_are_allowed
53
- create_table(:users) do |t|
54
- t.string :name, null: false
55
- end.define_model do
56
- validates :name, presence: true
57
- end
58
-
59
- refute_problems
60
- end
61
-
62
- def test_optional_columns_without_presence_validator_are_allowed
63
- create_table(:users) do |t|
64
- t.string :name, null: false
65
- end.define_model do
66
- validates :name, presence: false
67
- end
68
-
69
- refute_problems
70
- end
71
-
72
- def test_validators_matched_to_correct_columns
73
- create_table(:users) do |t|
74
- t.string :name, null: true
75
- end.define_model do
76
- # The age validator is a form of regression test against a bug that
77
- # caused false positives. In this test case, name is NOT validated
78
- # for presence so it does NOT need be marked non-NULL. However, the
79
- # bug would match the age presence validator with the NULL-able name
80
- # column which would result in a false positive error report.
81
- validates :age, presence: true
82
- validates :name, presence: false
83
- end
84
-
85
- refute_problems
86
- end
87
-
88
- def test_validators_with_if_on_optional_columns_are_allowed
89
- create_table(:users) do |t|
90
- t.string :name, null: true
91
- end.define_model do
92
- validates :name, presence: true, if: -> { false }
93
- end
94
-
95
- refute_problems
96
- end
97
-
98
- def test_validators_with_unless_on_optional_columns_are_allowed
99
- create_table(:users) do |t|
100
- t.string :name, null: true
101
- end.define_model do
102
- validates :name, presence: true, unless: -> { false }
103
- end
104
-
105
- refute_problems
106
- end
107
-
108
- def test_validators_allowing_nil_on_optional_columns_are_allowed
109
- create_table(:users) do |t|
110
- t.string :name, null: true
111
- end.define_model do
112
- validates :name, presence: true, allow_nil: true
113
- end
114
-
115
- refute_problems
116
- end
117
-
118
- def test_models_with_non_existent_tables_are_skipped
119
- define_model(:User)
120
-
121
- refute_problems
122
- end
123
-
124
- def test_optional_columns_validated_by_all_sti_models_are_disallowed
125
- create_table(:users) do |t|
126
- t.string :type, null: false
127
- t.string :email, null: true
128
- end.define_model
129
-
130
- define_model(:Client, TransientRecord::Models::User) do
131
- validates :email, presence: true
132
- end
133
-
134
- assert_problems(<<~OUTPUT)
135
- add `NOT NULL` to users.email - models validates its presence but it's not non-NULL in the database
136
- OUTPUT
137
- end
138
-
139
- def test_optional_columns_validated_by_some_sti_models_are_allowed
140
- create_table(:users) do |t|
141
- t.string :type, null: false
142
- t.string :email, null: true
143
- end.define_model
144
-
145
- define_model(:Client, TransientRecord::Models::User) do
146
- validates :email, presence: true
147
- end
148
-
149
- define_model(:Admin, TransientRecord::Models::User) do
150
- validates :email, presence: false
151
- end
152
-
153
- refute_problems
154
- end
155
-
156
- def test_optional_columns_validated_by_all_non_sti_models_are_disallowed
157
- create_table(:users) do |t|
158
- t.string :email, null: true
159
- end.define_model do
160
- validates :email, presence: true
161
- end
162
-
163
- define_model(:Client) do
164
- self.table_name = :users
165
-
166
- validates :email, presence: true
167
- end
168
-
169
- assert_problems(<<~OUTPUT)
170
- add `NOT NULL` to users.email - models validates its presence but it's not non-NULL in the database
171
- OUTPUT
172
- end
173
-
174
- def test_optional_columns_validated_by_some_non_sti_models_are_allowed
175
- create_table(:users) do |t|
176
- t.string :email, null: true
177
- end.define_model do
178
- validates :email, presence: true
179
- end
180
-
181
- define_model(:Client) do
182
- self.table_name = :users
183
-
184
- validates :email, presence: false
185
- end
186
-
187
- refute_problems
188
- end
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
-
224
- def test_config_ignore_tables
225
- create_table(:users) do |t|
226
- t.string :name, null: true
227
- end.define_model do
228
- validates :name, presence: true
229
- end
230
-
231
- config_file(<<-CONFIG)
232
- ActiveRecordDoctor.configure do |config|
233
- config.detector :missing_non_null_constraint,
234
- ignore_tables: ["users"]
235
- end
236
- CONFIG
237
-
238
- refute_problems
239
- end
240
-
241
- def test_global_ignore_tables
242
- create_table(:users) do |t|
243
- t.string :name, null: true
244
- end.define_model do
245
- validates :name, presence: true
246
- end
247
-
248
- config_file(<<-CONFIG)
249
- ActiveRecordDoctor.configure do |config|
250
- config.global :ignore_tables, ["users"]
251
- end
252
- CONFIG
253
-
254
- refute_problems
255
- end
256
-
257
- def test_config_ignore_columns
258
- create_table(:users) do |t|
259
- t.string :name, null: true
260
- end.define_model do
261
- validates :name, presence: true
262
- end
263
-
264
- config_file(<<-CONFIG)
265
- ActiveRecordDoctor.configure do |config|
266
- config.detector :missing_non_null_constraint,
267
- ignore_columns: ["users.name"]
268
- end
269
- CONFIG
270
-
271
- refute_problems
272
- end
273
- end
@@ -1,232 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::Test
4
- def test_null_column_is_not_reported_if_validation_absent
5
- create_table(:users) do |t|
6
- t.string :name
7
- end.define_model do
8
- end
9
-
10
- refute_problems
11
- end
12
-
13
- def test_non_null_column_is_reported_if_validation_absent
14
- create_table(:users) do |t|
15
- t.string :name, null: false
16
- end.define_model do
17
- end
18
-
19
- assert_problems(<<~OUTPUT)
20
- add a `presence` validator to TransientRecord::Models::User.name - it's NOT NULL but lacks a validator
21
- OUTPUT
22
- end
23
-
24
- def test_non_null_column_is_not_reported_if_validation_present
25
- create_table(:users) do |t|
26
- t.string :name, null: false
27
- end.define_model do
28
- validates :name, presence: true
29
- end
30
-
31
- refute_problems
32
- end
33
-
34
- def test_non_null_column_is_not_reported_if_association_validation_present
35
- create_table(:companies).define_model
36
- create_table(:users) do |t|
37
- t.references :company, null: false
38
- end.define_model do
39
- belongs_to :company, required: true
40
- end
41
-
42
- refute_problems
43
- end
44
-
45
- def test_not_null_column_is_not_reported_if_habtm_association
46
- create_table(:users).define_model do
47
- has_and_belongs_to_many :projects, class_name: "TransientRecord::Models::Project"
48
- end
49
-
50
- create_table(:projects_users) do |t|
51
- t.bigint :project_id, null: false
52
- t.bigint :user_id, null: false
53
- end
54
-
55
- create_table(:projects).define_model do
56
- has_and_belongs_to_many :users, class_name: "TransientRecord::Models::User"
57
- end
58
-
59
- refute_problems
60
- end
61
-
62
- def test_non_null_boolean_is_reported_if_nil_included
63
- create_table(:users) do |t|
64
- t.boolean :active, null: false
65
- end.define_model do
66
- validates :active, inclusion: { in: [nil, true, false] }
67
- end
68
-
69
- assert_problems(<<~OUTPUT)
70
- add a `presence` validator to TransientRecord::Models::User.active - it's NOT NULL but lacks a validator
71
- OUTPUT
72
- end
73
-
74
- def test_non_null_boolean_is_not_reported_if_nil_not_included
75
- create_table(:users) do |t|
76
- t.boolean :active, null: false
77
- end.define_model do
78
- validates :active, inclusion: { in: [true, false] }
79
- end
80
-
81
- refute_problems
82
- end
83
-
84
- def test_non_null_boolean_is_not_reported_if_nil_excluded
85
- create_table(:users) do |t|
86
- t.boolean :active, null: false
87
- end.define_model do
88
- validates :active, exclusion: { in: [nil] }
89
- end
90
-
91
- refute_problems
92
- end
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
-
114
- def test_non_null_boolean_is_reported_if_nil_not_excluded
115
- create_table(:users) do |t|
116
- t.boolean :active, null: false
117
- end.define_model do
118
- validates :active, exclusion: { in: [false] }
119
- end
120
-
121
- assert_problems(<<~OUTPUT)
122
- add a `presence` validator to TransientRecord::Models::User.active - it's NOT NULL but lacks a validator
123
- OUTPUT
124
- end
125
-
126
- def test_timestamps_are_not_reported
127
- create_table(:users) do |t|
128
- # Create created_at/updated_at timestamps.
129
- t.timestamps null: false
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
137
- end
138
-
139
- refute_problems
140
- end
141
-
142
- def test_models_with_non_existent_tables_are_skipped
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
182
-
183
- refute_problems
184
- end
185
-
186
- def test_config_ignore_models
187
- create_table(:users) do |t|
188
- t.string :name, null: false
189
- end.define_model do
190
- end
191
-
192
- config_file(<<-CONFIG)
193
- ActiveRecordDoctor.configure do |config|
194
- config.detector :missing_presence_validation,
195
- ignore_models: ["TransientRecord::Models::User"]
196
- end
197
- CONFIG
198
-
199
- refute_problems
200
- end
201
-
202
- def test_global_ignore_models
203
- create_table(:users) do |t|
204
- t.string :name, null: false
205
- end.define_model do
206
- end
207
-
208
- config_file(<<-CONFIG)
209
- ActiveRecordDoctor.configure do |config|
210
- config.global :ignore_models, ["TransientRecord::Models::User"]
211
- end
212
- CONFIG
213
-
214
- refute_problems
215
- end
216
-
217
- def test_config_ignore_attributes
218
- create_table(:users) do |t|
219
- t.string :name, null: false
220
- end.define_model do
221
- end
222
-
223
- config_file(<<-CONFIG)
224
- ActiveRecordDoctor.configure do |config|
225
- config.detector :missing_presence_validation,
226
- ignore_attributes: ["TransientRecord::Models::User.name"]
227
- end
228
- CONFIG
229
-
230
- refute_problems
231
- end
232
- end