active_record_doctor 1.12.0 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
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