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::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