active_record_doctor 1.12.0 → 1.14.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -2
  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/rake/task.rb +35 -3
  14. data/lib/active_record_doctor/runner.rb +1 -1
  15. data/lib/active_record_doctor/utils.rb +2 -2
  16. data/lib/active_record_doctor/version.rb +1 -1
  17. data/lib/tasks/active_record_doctor.rake +1 -2
  18. metadata +12 -49
  19. data/test/active_record_doctor/config/loader_test.rb +0 -120
  20. data/test/active_record_doctor/config_test.rb +0 -116
  21. data/test/active_record_doctor/detectors/disable_test.rb +0 -30
  22. data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +0 -277
  23. data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +0 -79
  24. data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +0 -511
  25. data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +0 -107
  26. data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +0 -116
  27. data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +0 -70
  28. data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +0 -273
  29. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +0 -232
  30. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +0 -496
  31. data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +0 -77
  32. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +0 -55
  33. data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +0 -177
  34. data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +0 -116
  35. data/test/active_record_doctor/runner_test.rb +0 -41
  36. data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +0 -141
  37. data/test/setup.rb +0 -124
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::ConfigTest < Minitest::Test
4
- def test_merge_globals_empty
5
- config1 = ActiveRecordDoctor::Config.new({}, {})
6
- config2 = ActiveRecordDoctor::Config.new({}, {})
7
-
8
- config = config1.merge(config2)
9
-
10
- assert_equal({}, config.globals)
11
- end
12
-
13
- def test_merge_globals_in_config1
14
- config1 = ActiveRecordDoctor::Config.new(
15
- { config1_global: "config1:config1_global" },
16
- {}
17
- )
18
- config2 = ActiveRecordDoctor::Config.new({}, {})
19
-
20
- config = config1.merge(config2)
21
-
22
- assert_equal(
23
- { config1_global: "config1:config1_global" },
24
- config.globals
25
- )
26
- end
27
-
28
- def test_merge_globals_in_config2
29
- config1 = ActiveRecordDoctor::Config.new({}, {})
30
- config2 = ActiveRecordDoctor::Config.new(
31
- { config2_global: "config2:config2_global" },
32
- {}
33
- )
34
-
35
- config = config1.merge(config2)
36
-
37
- assert_equal(
38
- { config2_global: "config2:config2_global" },
39
- config.globals
40
- )
41
- end
42
-
43
- def test_merge_globals_in_config1_and_config2
44
- config1 = ActiveRecordDoctor::Config.new(
45
- {
46
- config1_global: "config1:config1_global",
47
- shared_global: "config1:shared_global"
48
- },
49
- {}
50
- )
51
- config2 = ActiveRecordDoctor::Config.new(
52
- {
53
- config2_global: "config2:config2_global",
54
- shared_global: "config2:shared_global"
55
- },
56
- {}
57
- )
58
-
59
- config = config1.merge(config2)
60
-
61
- assert_equal(
62
- {
63
- config1_global: "config1:config1_global",
64
- shared_global: "config2:shared_global",
65
- config2_global: "config2:config2_global"
66
- },
67
- config.globals
68
- )
69
- end
70
-
71
- def test_merge_detectors
72
- config1 = ActiveRecordDoctor::Config.new(
73
- {},
74
- {
75
- config1_detector: {
76
- config1_setting: "config1:config1_detector.config1_setting"
77
- },
78
- shared_detector: {
79
- config1_setting: "config1:shared_detector.config1_setting",
80
- shared_setting: "config1:shared_detector.shared_setting"
81
- }
82
- }
83
- )
84
- config2 = ActiveRecordDoctor::Config.new(
85
- {},
86
- {
87
- config2_detector: {
88
- config2_setting: "config2:config2_detector.config2_setting"
89
- },
90
- shared_detector: {
91
- config2_setting: "config2:shared_detector.config2_setting",
92
- shared_setting: "config2:shared_detector.shared_setting"
93
- }
94
- }
95
- )
96
-
97
- config = config1.merge(config2)
98
-
99
- assert_equal(
100
- {
101
- config1_detector: {
102
- config1_setting: "config1:config1_detector.config1_setting"
103
- },
104
- config2_detector: {
105
- config2_setting: "config2:config2_detector.config2_setting"
106
- },
107
- shared_detector: {
108
- config1_setting: "config1:shared_detector.config1_setting",
109
- config2_setting: "config2:shared_detector.config2_setting",
110
- shared_setting: "config2:shared_detector.shared_setting"
111
- }
112
- },
113
- config.detectors
114
- )
115
- end
116
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::DisableTest < Minitest::Test
4
- # Disabling detectors is implemented in the base class. It's enought to test
5
- # it on a single detector to be reasonably certain it works on all of them.
6
- def test_disabling
7
- create_table(:users) do |t|
8
- t.string :name, null: true
9
- end.define_model do
10
- validates :name, presence: true
11
- end
12
-
13
- config_file(<<-CONFIG)
14
- ActiveRecordDoctor.configure do |config|
15
- config.detector :missing_non_null_constraint,
16
- enabled: false
17
- end
18
- CONFIG
19
-
20
- refute_problems
21
- end
22
-
23
- private
24
-
25
- # We need to override that method in order to skip the mechanism that
26
- # infers detector name from the test class name.
27
- def detector_name
28
- :missing_non_null_constraint
29
- end
30
- end
@@ -1,277 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::ExtraneousIndexesTest < Minitest::Test
4
- def test_index_on_primary_key_is_duplicate
5
- create_table(:users) do |t|
6
- t.index :id
7
- end
8
-
9
- assert_problems(<<OUTPUT)
10
- remove index_users_on_id from users - coincides with the primary key on the table
11
- OUTPUT
12
- end
13
-
14
- def test_partial_index_on_primary_key
15
- skip("MySQL doesn't support partial indexes") if mysql?
16
-
17
- create_table(:users) do |t|
18
- t.boolean :admin
19
- t.index :id, where: "admin"
20
- end
21
-
22
- refute_problems
23
- end
24
-
25
- def test_index_on_non_standard_primary_key
26
- create_table(:profiles, primary_key: :user_id) do |t|
27
- t.index :user_id
28
- end
29
-
30
- assert_problems(<<OUTPUT)
31
- remove index_profiles_on_user_id from profiles - coincides with the primary key on the table
32
- OUTPUT
33
- end
34
-
35
- def test_non_unique_version_of_index_is_duplicate
36
- create_table(:users) do |t|
37
- t.string :email
38
- t.index :email, unique: true, name: "unique_index_on_users_email"
39
- end
40
-
41
- # Rails 4.2 compatibility - can't be pulled into the block above.
42
- ActiveRecord::Base.connection.add_index :users, :email, name: "index_users_on_email"
43
-
44
- assert_problems(<<OUTPUT)
45
- remove the index index_users_on_email from the table users - queries should be able to use the following index instead: unique_index_on_users_email
46
- OUTPUT
47
- end
48
-
49
- def test_single_column_covered_by_unique_and_non_unique_multi_column_is_duplicate
50
- create_table(:users) do |t|
51
- t.string :first_name
52
- t.string :last_name
53
- t.string :email
54
- t.index [:last_name, :first_name, :email]
55
- t.index [:last_name, :first_name],
56
- unique: true,
57
- name: "unique_index_on_users_last_name_and_first_name"
58
- t.index :last_name
59
- end
60
-
61
- assert_problems(<<OUTPUT)
62
- remove the index index_users_on_last_name from the table users - queries should be able to use the following indices instead: index_users_on_last_name_and_first_name_and_email or unique_index_on_users_last_name_and_first_name
63
- OUTPUT
64
- end
65
-
66
- def test_multi_column_covered_by_unique_and_non_unique_multi_column_is_duplicate
67
- create_table(:users) do |t|
68
- t.string :first_name
69
- t.string :last_name
70
- t.string :email
71
- t.index [:last_name, :first_name, :email]
72
- t.index [:last_name, :first_name],
73
- unique: true,
74
- name: "unique_index_on_users_last_name_and_first_name"
75
- end
76
-
77
- # Rails 4.2 compatibility - can't be pulled into the block above.
78
- ActiveRecord::Base.connection.add_index :users, [:last_name, :first_name]
79
-
80
- assert_problems(<<OUTPUT)
81
- remove the index index_users_on_last_name_and_first_name from the table users - queries should be able to use the following indices instead: index_users_on_last_name_and_first_name_and_email or unique_index_on_users_last_name_and_first_name
82
- OUTPUT
83
- end
84
-
85
- def test_unique_index_with_fewer_columns
86
- create_table(:users) do |t|
87
- t.string :first_name
88
- t.string :last_name
89
- t.index :first_name, unique: true
90
- t.index [:last_name, :first_name], unique: true
91
- end
92
-
93
- assert_problems(<<OUTPUT)
94
- remove the index index_users_on_last_name_and_first_name from the table users - queries should be able to use the following index instead: index_users_on_first_name
95
- OUTPUT
96
- end
97
-
98
- def test_expression_index_not_covered_by_multicolumn_index
99
- skip("Expression indexes are not supported") if ActiveRecordDoctor::Utils.expression_indexes_unsupported?
100
-
101
- create_table(:users) do |t|
102
- t.string :first_name
103
- t.string :email
104
- t.index "(lower(email))"
105
- t.index [:first_name, :email]
106
- end
107
-
108
- refute_problems
109
- end
110
-
111
- def test_unique_expression_index_not_covered_by_unique_multicolumn_index
112
- skip("Expression indexes are not supported") if ActiveRecordDoctor::Utils.expression_indexes_unsupported?
113
-
114
- create_table(:users) do |t|
115
- t.string :first_name
116
- t.string :email
117
- t.index "(lower(email))", unique: true
118
- t.index [:first_name, :email], unique: true
119
- end
120
-
121
- refute_problems
122
- end
123
-
124
- def test_not_covered_by_different_index_type
125
- create_table(:users) do |t|
126
- t.string :first_name
127
- t.string :last_name
128
- t.index [:last_name, :first_name], using: :btree
129
-
130
- if mysql?
131
- t.index :last_name, type: :fulltext
132
- else
133
- t.index :last_name, using: :hash
134
- end
135
- end
136
-
137
- refute_problems
138
- end
139
-
140
- def test_not_covered_by_partial_index
141
- skip("MySQL doesn't support partial indexes") if mysql?
142
-
143
- create_table(:users) do |t|
144
- t.string :first_name
145
- t.string :last_name
146
- t.boolean :active
147
- t.index [:last_name, :first_name], where: "active"
148
- t.index :last_name
149
- end
150
-
151
- refute_problems
152
- end
153
-
154
- def test_not_covered_with_different_opclasses
155
- skip("ActiveRecord < 5.2 doesn't support operator classes") if ActiveRecord::VERSION::STRING < "5.2"
156
- skip("MySQL doesn't support operator classes") if mysql?
157
-
158
- create_table(:users) do |t|
159
- t.string :first_name
160
- t.string :last_name
161
- t.index [:last_name, :first_name], opclass: :varchar_pattern_ops
162
- t.index :last_name
163
- end
164
-
165
- refute_problems
166
- end
167
-
168
- def test_single_column_covered_by_multi_column_on_materialized_view_is_duplicate
169
- skip("Only PostgreSQL supports materialized views") unless postgresql?
170
-
171
- begin
172
- create_table(:users) do |t|
173
- t.string :first_name
174
- t.string :last_name
175
- t.integer :age
176
- end
177
-
178
- connection = ActiveRecord::Base.connection
179
- connection.execute(<<-SQL)
180
- CREATE MATERIALIZED VIEW user_initials AS
181
- SELECT first_name, last_name FROM users
182
- SQL
183
-
184
- connection.add_index(:user_initials, [:last_name, :first_name])
185
- connection.add_index(:user_initials, :last_name)
186
-
187
- assert_problems(<<OUTPUT)
188
- remove the index index_user_initials_on_last_name from the table user_initials - queries should be able to use the following index instead: index_user_initials_on_last_name_and_first_name
189
- OUTPUT
190
- ensure
191
- connection.execute("DROP MATERIALIZED VIEW user_initials")
192
- end
193
- end
194
-
195
- def test_config_ignore_tables
196
- # The detector recognizes two kinds of errors and both must take
197
- # ignore_tables into account. We trigger those errors by indexing the
198
- # primary key (the first extraneous index) and then indexing email twice
199
- # (index2... is the other extraneous index).
200
- create_table(:users) do |t|
201
- t.index :id
202
- t.string :email
203
-
204
- t.index :email, name: "index1_on_users_email"
205
- t.index :email, name: "index2_on_users_email"
206
- end
207
-
208
- config_file(<<-CONFIG)
209
- ActiveRecordDoctor.configure do |config|
210
- config.detector :extraneous_indexes,
211
- ignore_tables: ["users"]
212
- end
213
- CONFIG
214
-
215
- refute_problems
216
- end
217
-
218
- def test_config_global_ignore_tables
219
- create_table(:users) do |t|
220
- t.index :id
221
- t.string :email
222
-
223
- t.index :email, name: "index1_on_users_email"
224
- t.index :email, name: "index2_on_users_email"
225
- end
226
-
227
- config_file(<<-CONFIG)
228
- ActiveRecordDoctor.configure do |config|
229
- config.global :ignore_tables, ["users"]
230
- end
231
- CONFIG
232
-
233
- refute_problems
234
- end
235
-
236
- def test_config_global_ignore_indexes
237
- create_table(:users) do |t|
238
- t.index :id
239
- t.string :email
240
-
241
- t.index :email, name: "index1_on_users_email"
242
- t.index :email, name: "index2_on_users_email"
243
- end
244
-
245
- config_file(<<-CONFIG)
246
- ActiveRecordDoctor.configure do |config|
247
- config.global :ignore_indexes, [
248
- "index1_on_users_email",
249
- "index2_on_users_email",
250
- "index_users_on_id",
251
- ]
252
- end
253
- CONFIG
254
-
255
- refute_problems
256
- end
257
-
258
- def test_config_detector_ignore_indexes
259
- create_table(:users) do |t|
260
- t.index :id
261
- t.string :email
262
- t.string :api_key
263
-
264
- t.index :email, name: "index_on_users_email"
265
- t.index [:email, :api_key], name: "index_on_users_email_and_api_key"
266
- end
267
-
268
- config_file(<<-CONFIG)
269
- ActiveRecordDoctor.configure do |config|
270
- config.detector :extraneous_indexes,
271
- ignore_indexes: ["index_users_on_id", "index_on_users_email"]
272
- end
273
- CONFIG
274
-
275
- refute_problems
276
- end
277
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::Detectors::IncorrectBooleanPresenceValidationTest < Minitest::Test
4
- def test_presence_true_is_reported_on_boolean_only
5
- create_table(:users) do |t|
6
- t.string :email, null: false
7
- t.boolean :active, null: false
8
- end.define_model do
9
- # email is a non-boolean column whose presence CAN be validated in the
10
- # usual way. We include it in the test model to ensure the detector reports
11
- # only boolean columns.
12
- validates :email, :active, presence: true
13
- end
14
-
15
- assert_problems(<<~OUTPUT)
16
- replace the `presence` validator on TransientRecord::Models::User.active with `inclusion` - `presence` can't be used on booleans
17
- OUTPUT
18
- end
19
-
20
- def test_inclusion_is_not_reported
21
- create_table(:users) do |t|
22
- t.boolean :active, null: false
23
- end.define_model do
24
- validates :active, inclusion: { in: [true, false] }
25
- end
26
-
27
- refute_problems
28
- end
29
-
30
- def test_models_with_non_existent_tables_are_skipped
31
- define_model(:User)
32
-
33
- refute_problems
34
- end
35
-
36
- def test_config_ignore_models
37
- create_table(:users) do |t|
38
- t.string :email, null: false
39
- end.define_model
40
-
41
- config_file(<<-CONFIG)
42
- ActiveRecordDoctor.configure do |config|
43
- config.detector :incorrect_boolean_presence_validation,
44
- ignore_models: ["ModelFactory.User"]
45
- end
46
- CONFIG
47
-
48
- refute_problems
49
- end
50
-
51
- def test_global_ignore_models
52
- create_table(:users) do |t|
53
- t.string :email, null: false
54
- end.define_model
55
-
56
- config_file(<<-CONFIG)
57
- ActiveRecordDoctor.configure do |config|
58
- config.global :ignore_models, ["ModelFactory.User"]
59
- end
60
- CONFIG
61
-
62
- refute_problems
63
- end
64
-
65
- def test_config_ignore_attributes
66
- create_table(:users) do |t|
67
- t.string :email, null: false
68
- end.define_model
69
-
70
- config_file(<<-CONFIG)
71
- ActiveRecordDoctor.configure do |config|
72
- config.detector :incorrect_boolean_presence_validation,
73
- ignore_attributes: ["ModelFactory.User.email"]
74
- end
75
- CONFIG
76
-
77
- refute_problems
78
- end
79
- end