active_record_doctor 1.8.0 → 1.10.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.
- checksums.yaml +4 -4
- data/README.md +316 -54
- data/lib/active_record_doctor/config/default.rb +76 -0
- data/lib/active_record_doctor/config/loader.rb +137 -0
- data/lib/active_record_doctor/config.rb +14 -0
- data/lib/active_record_doctor/detectors/base.rb +142 -21
- data/lib/active_record_doctor/detectors/extraneous_indexes.rb +59 -48
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +31 -23
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +102 -35
- data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +63 -0
- data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +45 -0
- data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +32 -23
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +41 -28
- data/lib/active_record_doctor/detectors/missing_presence_validation.rb +29 -23
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +92 -32
- data/lib/active_record_doctor/detectors/short_primary_key_type.rb +45 -0
- data/lib/active_record_doctor/detectors/undefined_table_references.rb +17 -20
- data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +43 -18
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +31 -20
- data/lib/active_record_doctor/detectors.rb +12 -4
- data/lib/active_record_doctor/errors.rb +226 -0
- data/lib/active_record_doctor/help.rb +39 -0
- data/lib/active_record_doctor/rake/task.rb +78 -0
- data/lib/active_record_doctor/runner.rb +41 -0
- data/lib/active_record_doctor/version.rb +1 -1
- data/lib/active_record_doctor.rb +8 -3
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +34 -21
- data/lib/tasks/active_record_doctor.rake +9 -18
- data/test/active_record_doctor/config/loader_test.rb +120 -0
- data/test/active_record_doctor/config_test.rb +116 -0
- data/test/active_record_doctor/detectors/disable_test.rb +30 -0
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +165 -8
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +48 -5
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +288 -12
- data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +105 -0
- data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +82 -0
- data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +50 -4
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +172 -24
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +111 -14
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +223 -10
- data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +72 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +34 -21
- data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +118 -8
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +56 -4
- data/test/active_record_doctor/runner_test.rb +42 -0
- data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +131 -0
- data/test/model_factory.rb +73 -23
- data/test/setup.rb +65 -71
- metadata +43 -7
- data/lib/active_record_doctor/printers/io_printer.rb +0 -133
- data/lib/active_record_doctor/task.rb +0 -28
- data/test/active_record_doctor/printers/io_printer_test.rb +0 -33
@@ -1,34 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ActiveRecordDoctor::Detectors::MissingNonNullConstraintTest < Minitest::Test
|
4
|
-
def
|
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
7
|
end.create_model do
|
8
8
|
validates :name, presence: true
|
9
9
|
end
|
10
10
|
|
11
|
-
assert_problems(
|
12
|
-
|
13
|
-
|
14
|
-
OUTPUT
|
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
|
15
14
|
end
|
16
15
|
|
17
|
-
def
|
16
|
+
def test_optional_foreign_keys_with_required_association_are_disallowed
|
18
17
|
create_table(:companies)
|
19
18
|
create_table(:users) do |t|
|
20
|
-
t.references :company
|
19
|
+
t.references :company, null: true
|
21
20
|
end.create_model do
|
22
21
|
belongs_to :company, required: true
|
23
22
|
end
|
24
23
|
|
25
|
-
assert_problems(
|
26
|
-
|
27
|
-
|
28
|
-
OUTPUT
|
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
|
29
27
|
end
|
30
28
|
|
31
|
-
def
|
29
|
+
def test_required_columns_with_presence_validators_are_allowed
|
32
30
|
create_table(:users) do |t|
|
33
31
|
t.string :name, null: false
|
34
32
|
end.create_model do
|
@@ -38,7 +36,17 @@ OUTPUT
|
|
38
36
|
refute_problems
|
39
37
|
end
|
40
38
|
|
41
|
-
def
|
39
|
+
def test_optional_columns_without_presence_validator_are_allowed
|
40
|
+
create_table(:users) do |t|
|
41
|
+
t.string :name, null: false
|
42
|
+
end.create_model do
|
43
|
+
validates :name, presence: false
|
44
|
+
end
|
45
|
+
|
46
|
+
refute_problems
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_validators_matched_to_correct_columns
|
42
50
|
create_table(:users) do |t|
|
43
51
|
t.string :name, null: true
|
44
52
|
end.create_model do
|
@@ -54,48 +62,188 @@ OUTPUT
|
|
54
62
|
refute_problems
|
55
63
|
end
|
56
64
|
|
57
|
-
def
|
65
|
+
def test_validators_with_if_on_optional_columns_are_allowed
|
58
66
|
create_table(:users) do |t|
|
59
|
-
t.string :name, null:
|
67
|
+
t.string :name, null: true
|
60
68
|
end.create_model do
|
61
|
-
validates :name, presence: false
|
69
|
+
validates :name, presence: true, if: -> { false }
|
62
70
|
end
|
63
71
|
|
64
72
|
refute_problems
|
65
73
|
end
|
66
74
|
|
67
|
-
def
|
75
|
+
def test_validators_with_unless_on_optional_columns_are_allowed
|
68
76
|
create_table(:users) do |t|
|
69
77
|
t.string :name, null: true
|
70
78
|
end.create_model do
|
71
|
-
validates :name, presence: true,
|
79
|
+
validates :name, presence: true, unless: -> { false }
|
72
80
|
end
|
73
81
|
|
74
82
|
refute_problems
|
75
83
|
end
|
76
84
|
|
77
|
-
def
|
85
|
+
def test_validators_allowing_nil_on_optional_columns_are_allowed
|
78
86
|
create_table(:users) do |t|
|
79
87
|
t.string :name, null: true
|
80
88
|
end.create_model do
|
81
|
-
validates :name, presence: true,
|
89
|
+
validates :name, presence: true, allow_nil: true
|
82
90
|
end
|
83
91
|
|
84
92
|
refute_problems
|
85
93
|
end
|
86
94
|
|
87
|
-
def
|
95
|
+
def test_models_with_non_existent_tables_are_skipped
|
96
|
+
create_model(:User)
|
97
|
+
|
98
|
+
refute_problems
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_optional_columns_validated_by_all_sti_models_are_disallowed
|
102
|
+
create_table(:users) do |t|
|
103
|
+
t.string :type, null: false
|
104
|
+
t.string :email, null: true
|
105
|
+
end.create_model
|
106
|
+
|
107
|
+
create_model(:Client, ModelFactory::Models::User) do
|
108
|
+
validates :email, presence: true
|
109
|
+
end
|
110
|
+
|
111
|
+
assert_problems(<<~OUTPUT)
|
112
|
+
add `NOT NULL` to users.email - models validates its presence but it's not non-NULL in the database
|
113
|
+
OUTPUT
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_optional_columns_validated_by_some_sti_models_are_allowed
|
117
|
+
create_table(:users) do |t|
|
118
|
+
t.string :type, null: false
|
119
|
+
t.string :email, null: true
|
120
|
+
end.create_model
|
121
|
+
|
122
|
+
create_model(:Client, ModelFactory::Models::User) do
|
123
|
+
validates :email, presence: true
|
124
|
+
end
|
125
|
+
|
126
|
+
create_model(:Admin, ModelFactory::Models::User) do
|
127
|
+
validates :email, presence: false
|
128
|
+
end
|
129
|
+
|
130
|
+
refute_problems
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_optional_columns_validated_by_all_non_sti_models_are_disallowed
|
134
|
+
create_table(:users) do |t|
|
135
|
+
t.string :email, null: true
|
136
|
+
end.create_model do
|
137
|
+
validates :email, presence: true
|
138
|
+
end
|
139
|
+
|
140
|
+
create_model(:Client) do
|
141
|
+
self.table_name = :users
|
142
|
+
|
143
|
+
validates :email, presence: true
|
144
|
+
end
|
145
|
+
|
146
|
+
assert_problems(<<~OUTPUT)
|
147
|
+
add `NOT NULL` to users.email - models validates its presence but it's not non-NULL in the database
|
148
|
+
OUTPUT
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_optional_columns_validated_by_some_non_sti_models_are_allowed
|
152
|
+
create_table(:users) do |t|
|
153
|
+
t.string :email, null: true
|
154
|
+
end.create_model do
|
155
|
+
validates :email, presence: true
|
156
|
+
end
|
157
|
+
|
158
|
+
create_model(:Client) do
|
159
|
+
self.table_name = :users
|
160
|
+
|
161
|
+
validates :email, presence: false
|
162
|
+
end
|
163
|
+
|
164
|
+
refute_problems
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_not_null_check_constraint
|
168
|
+
skip unless postgresql?
|
169
|
+
|
170
|
+
create_table(:users) do |t|
|
171
|
+
t.string :email
|
172
|
+
end.create_model do
|
173
|
+
validates :email, presence: true
|
174
|
+
end
|
175
|
+
|
176
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
177
|
+
ALTER TABLE users ADD CONSTRAINT email_not_null CHECK (email IS NOT NULL)
|
178
|
+
SQL
|
179
|
+
|
180
|
+
refute_problems
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_not_null_check_constraint_not_valid
|
184
|
+
skip unless postgresql?
|
185
|
+
|
186
|
+
create_table(:users) do |t|
|
187
|
+
t.string :email
|
188
|
+
end.create_model do
|
189
|
+
validates :email, presence: true
|
190
|
+
end
|
191
|
+
|
192
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
193
|
+
ALTER TABLE users ADD CONSTRAINT email_not_null CHECK (email IS NOT NULL) NOT VALID
|
194
|
+
SQL
|
195
|
+
|
196
|
+
assert_problems(<<~OUTPUT)
|
197
|
+
add `NOT NULL` to users.email - models validates its presence but it's not non-NULL in the database
|
198
|
+
OUTPUT
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_config_ignore_tables
|
88
202
|
create_table(:users) do |t|
|
89
203
|
t.string :name, null: true
|
90
204
|
end.create_model do
|
91
|
-
validates :name, presence: true
|
205
|
+
validates :name, presence: true
|
92
206
|
end
|
93
207
|
|
208
|
+
config_file(<<-CONFIG)
|
209
|
+
ActiveRecordDoctor.configure do |config|
|
210
|
+
config.detector :missing_non_null_constraint,
|
211
|
+
ignore_tables: ["users"]
|
212
|
+
end
|
213
|
+
CONFIG
|
214
|
+
|
94
215
|
refute_problems
|
95
216
|
end
|
96
217
|
|
97
|
-
def
|
98
|
-
|
218
|
+
def test_global_ignore_tables
|
219
|
+
create_table(:users) do |t|
|
220
|
+
t.string :name, null: true
|
221
|
+
end.create_model do
|
222
|
+
validates :name, presence: true
|
223
|
+
end
|
224
|
+
|
225
|
+
config_file(<<-CONFIG)
|
226
|
+
ActiveRecordDoctor.configure do |config|
|
227
|
+
config.global :ignore_tables, ["users"]
|
228
|
+
end
|
229
|
+
CONFIG
|
230
|
+
|
231
|
+
refute_problems
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_config_ignore_columns
|
235
|
+
create_table(:users) do |t|
|
236
|
+
t.string :name, null: true
|
237
|
+
end.create_model do
|
238
|
+
validates :name, presence: true
|
239
|
+
end
|
240
|
+
|
241
|
+
config_file(<<-CONFIG)
|
242
|
+
ActiveRecordDoctor.configure do |config|
|
243
|
+
config.detector :missing_non_null_constraint,
|
244
|
+
ignore_columns: ["users.name"]
|
245
|
+
end
|
246
|
+
CONFIG
|
99
247
|
|
100
248
|
refute_problems
|
101
249
|
end
|
@@ -16,10 +16,9 @@ class ActiveRecordDoctor::Detectors::MissingPresenceValidationTest < Minitest::T
|
|
16
16
|
end.create_model do
|
17
17
|
end
|
18
18
|
|
19
|
-
assert_problems(
|
20
|
-
|
21
|
-
|
22
|
-
OUTPUT
|
19
|
+
assert_problems(<<~OUTPUT)
|
20
|
+
add a `presence` validator to ModelFactory::Models::User.name - it's NOT NULL but lacks a validator
|
21
|
+
OUTPUT
|
23
22
|
end
|
24
23
|
|
25
24
|
def test_non_null_column_is_not_reported_if_validation_present
|
@@ -43,6 +42,23 @@ OUTPUT
|
|
43
42
|
refute_problems
|
44
43
|
end
|
45
44
|
|
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"
|
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).create_model do
|
56
|
+
has_and_belongs_to_many :users, class_name: "ModelFactory::Models::User"
|
57
|
+
end
|
58
|
+
|
59
|
+
refute_problems
|
60
|
+
end
|
61
|
+
|
46
62
|
def test_non_null_boolean_is_reported_if_nil_included
|
47
63
|
create_table(:users) do |t|
|
48
64
|
t.boolean :active, null: false
|
@@ -50,10 +66,9 @@ OUTPUT
|
|
50
66
|
validates :active, inclusion: { in: [nil, true, false] }
|
51
67
|
end
|
52
68
|
|
53
|
-
assert_problems(
|
54
|
-
|
55
|
-
|
56
|
-
OUTPUT
|
69
|
+
assert_problems(<<~OUTPUT)
|
70
|
+
add a `presence` validator to ModelFactory::Models::User.active - it's NOT NULL but lacks a validator
|
71
|
+
OUTPUT
|
57
72
|
end
|
58
73
|
|
59
74
|
def test_non_null_boolean_is_not_reported_if_nil_not_included
|
@@ -83,24 +98,106 @@ OUTPUT
|
|
83
98
|
validates :active, exclusion: { in: [false] }
|
84
99
|
end
|
85
100
|
|
86
|
-
assert_problems(
|
87
|
-
|
88
|
-
|
89
|
-
OUTPUT
|
101
|
+
assert_problems(<<~OUTPUT)
|
102
|
+
add a `presence` validator to ModelFactory::Models::User.active - it's NOT NULL but lacks a validator
|
103
|
+
OUTPUT
|
90
104
|
end
|
91
105
|
|
92
106
|
def test_timestamps_are_not_reported
|
93
107
|
create_table(:users) do |t|
|
108
|
+
# Create created_at/updated_at timestamps.
|
94
109
|
t.timestamps null: false
|
110
|
+
|
111
|
+
# Rails also supports created_on/updated_on. We used datetime, which is
|
112
|
+
# what the timestamps method users under the hood, to avoid default value
|
113
|
+
# errors in some MySQL versions when using t.timestamp.
|
114
|
+
t.datetime :created_on, null: false
|
115
|
+
t.datetime :updated_on, null: false
|
95
116
|
end.create_model do
|
96
|
-
validates :name, presence: true
|
97
117
|
end
|
98
118
|
|
99
119
|
refute_problems
|
100
120
|
end
|
101
121
|
|
102
122
|
def test_models_with_non_existent_tables_are_skipped
|
103
|
-
create_model(:
|
123
|
+
create_model(:User)
|
124
|
+
|
125
|
+
refute_problems
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_not_null_check_constraint
|
129
|
+
skip unless postgresql?
|
130
|
+
|
131
|
+
create_table(:users) do |t|
|
132
|
+
t.string :name
|
133
|
+
end.create_model
|
134
|
+
|
135
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
136
|
+
ALTER TABLE users ADD CONSTRAINT name_not_null CHECK (name IS NOT NULL)
|
137
|
+
SQL
|
138
|
+
|
139
|
+
assert_problems(<<~OUTPUT)
|
140
|
+
add a `presence` validator to ModelFactory::Models::User.name - it's NOT NULL but lacks a validator
|
141
|
+
OUTPUT
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_not_null_check_constraint_not_valid
|
145
|
+
skip unless postgresql?
|
146
|
+
|
147
|
+
create_table(:users) do |t|
|
148
|
+
t.string :name
|
149
|
+
end.create_model
|
150
|
+
|
151
|
+
ActiveRecord::Base.connection.execute(<<-SQL)
|
152
|
+
ALTER TABLE users ADD CONSTRAINT name_not_null CHECK (name IS NOT NULL) NOT VALID
|
153
|
+
SQL
|
154
|
+
|
155
|
+
refute_problems
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_config_ignore_models
|
159
|
+
create_table(:users) do |t|
|
160
|
+
t.string :name, null: false
|
161
|
+
end.create_model do
|
162
|
+
end
|
163
|
+
|
164
|
+
config_file(<<-CONFIG)
|
165
|
+
ActiveRecordDoctor.configure do |config|
|
166
|
+
config.detector :missing_presence_validation,
|
167
|
+
ignore_models: ["ModelFactory::Models::User"]
|
168
|
+
end
|
169
|
+
CONFIG
|
170
|
+
|
171
|
+
refute_problems
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_global_ignore_models
|
175
|
+
create_table(:users) do |t|
|
176
|
+
t.string :name, null: false
|
177
|
+
end.create_model do
|
178
|
+
end
|
179
|
+
|
180
|
+
config_file(<<-CONFIG)
|
181
|
+
ActiveRecordDoctor.configure do |config|
|
182
|
+
config.global :ignore_models, ["ModelFactory::Models::User"]
|
183
|
+
end
|
184
|
+
CONFIG
|
185
|
+
|
186
|
+
refute_problems
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_config_ignore_attributes
|
190
|
+
create_table(:users) do |t|
|
191
|
+
t.string :name, null: false
|
192
|
+
end.create_model do
|
193
|
+
end
|
194
|
+
|
195
|
+
config_file(<<-CONFIG)
|
196
|
+
ActiveRecordDoctor.configure do |config|
|
197
|
+
config.detector :missing_presence_validation,
|
198
|
+
ignore_attributes: ["ModelFactory::Models::User.name"]
|
199
|
+
end
|
200
|
+
CONFIG
|
104
201
|
|
105
202
|
refute_problems
|
106
203
|
end
|
@@ -9,10 +9,39 @@ class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
|
9
9
|
validates :email, uniqueness: true
|
10
10
|
end
|
11
11
|
|
12
|
-
assert_problems(
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
assert_problems(<<~OUTPUT)
|
13
|
+
add a unique index on users(email) - validating uniqueness in the model without an index can lead to duplicates
|
14
|
+
OUTPUT
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_missing_unique_index_on_functional_index
|
18
|
+
skip if !(ActiveRecord::VERSION::STRING >= "5.0" && postgresql?)
|
19
|
+
|
20
|
+
create_table(:users) do |t|
|
21
|
+
t.string :email
|
22
|
+
t.index "lower(email)"
|
23
|
+
end.create_model do
|
24
|
+
validates :email, uniqueness: true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Running the detector should NOT raise an error when a functional index
|
28
|
+
# is present. No need to assert anything -- the test is successful if no
|
29
|
+
# exception was raised.
|
30
|
+
run_detector
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_validates_multiple_attributes
|
34
|
+
create_table(:users) do |t|
|
35
|
+
t.string :email
|
36
|
+
t.string :ref_token
|
37
|
+
end.create_model do
|
38
|
+
validates :email, :ref_token, uniqueness: true
|
39
|
+
end
|
40
|
+
|
41
|
+
assert_problems(<<~OUTPUT)
|
42
|
+
add a unique index on users(email) - validating uniqueness in the model without an index can lead to duplicates
|
43
|
+
add a unique index on users(ref_token) - validating uniqueness in the model without an index can lead to duplicates
|
44
|
+
OUTPUT
|
16
45
|
end
|
17
46
|
|
18
47
|
def test_present_unique_index
|
@@ -26,7 +55,23 @@ OUTPUT
|
|
26
55
|
refute_problems
|
27
56
|
end
|
28
57
|
|
29
|
-
def
|
58
|
+
def test_present_partial_unique_index
|
59
|
+
skip("MySQL doesn't support partial indexes") if mysql?
|
60
|
+
|
61
|
+
create_table(:users) do |t|
|
62
|
+
t.string :email
|
63
|
+
t.boolean :active
|
64
|
+
t.index :email, unique: true, where: "active"
|
65
|
+
end.create_model do
|
66
|
+
validates :email, uniqueness: true
|
67
|
+
end
|
68
|
+
|
69
|
+
assert_problems(<<~OUTPUT)
|
70
|
+
add a unique index on users(email) - validating uniqueness in the model without an index can lead to duplicates
|
71
|
+
OUTPUT
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_unique_index_with_extra_columns_with_scope
|
30
75
|
create_table(:users) do |t|
|
31
76
|
t.string :email
|
32
77
|
t.integer :company_id
|
@@ -36,13 +81,12 @@ OUTPUT
|
|
36
81
|
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
37
82
|
end
|
38
83
|
|
39
|
-
assert_problems(
|
40
|
-
|
41
|
-
|
42
|
-
OUTPUT
|
84
|
+
assert_problems(<<~OUTPUT)
|
85
|
+
add a unique index on users(company_id, department_id, email) - validating uniqueness in the model without an index can lead to duplicates
|
86
|
+
OUTPUT
|
43
87
|
end
|
44
88
|
|
45
|
-
def
|
89
|
+
def test_unique_index_with_exact_columns_with_scope
|
46
90
|
create_table(:users) do |t|
|
47
91
|
t.string :email
|
48
92
|
t.integer :company_id
|
@@ -55,6 +99,73 @@ OUTPUT
|
|
55
99
|
refute_problems
|
56
100
|
end
|
57
101
|
|
102
|
+
def test_unique_index_with_fewer_columns_with_scope
|
103
|
+
create_table(:users) do |t|
|
104
|
+
t.string :email
|
105
|
+
t.integer :company_id
|
106
|
+
t.integer :department_id
|
107
|
+
t.index [:company_id, :department_id], unique: true
|
108
|
+
end.create_model do
|
109
|
+
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
110
|
+
end
|
111
|
+
|
112
|
+
refute_problems
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_missing_unique_index_with_association_attribute
|
116
|
+
create_table(:users) do |t|
|
117
|
+
t.integer :account_id
|
118
|
+
end.create_model do
|
119
|
+
belongs_to :account
|
120
|
+
validates :account, uniqueness: true
|
121
|
+
end
|
122
|
+
|
123
|
+
assert_problems(<<~OUTPUT)
|
124
|
+
add a unique index on users(account_id) - validating uniqueness in the model without an index can lead to duplicates
|
125
|
+
OUTPUT
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_present_unique_index_with_association_attribute
|
129
|
+
create_table(:users) do |t|
|
130
|
+
t.integer :account_id
|
131
|
+
t.index :account_id, unique: true
|
132
|
+
end.create_model do
|
133
|
+
belongs_to :account
|
134
|
+
validates :account, uniqueness: true
|
135
|
+
end
|
136
|
+
|
137
|
+
refute_problems
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_missing_unique_index_with_association_scope
|
141
|
+
create_table(:comments) do |t|
|
142
|
+
t.string :title
|
143
|
+
t.integer :commentable_id
|
144
|
+
t.string :commentable_type
|
145
|
+
end.create_model do
|
146
|
+
belongs_to :commentable, polymorphic: true
|
147
|
+
validates :title, uniqueness: { scope: :commentable }
|
148
|
+
end
|
149
|
+
|
150
|
+
assert_problems(<<~OUTPUT)
|
151
|
+
add a unique index on comments(commentable_type, commentable_id, title) - validating uniqueness in the model without an index can lead to duplicates
|
152
|
+
OUTPUT
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_present_unique_index_with_association_scope
|
156
|
+
create_table(:comments) do |t|
|
157
|
+
t.string :title
|
158
|
+
t.integer :commentable_id
|
159
|
+
t.string :commentable_type
|
160
|
+
t.index [:commentable_id, :commentable_type, :title], unique: true
|
161
|
+
end.create_model do
|
162
|
+
belongs_to :commentable, polymorphic: true
|
163
|
+
validates :title, uniqueness: { scope: :commentable }
|
164
|
+
end
|
165
|
+
|
166
|
+
refute_problems
|
167
|
+
end
|
168
|
+
|
58
169
|
def test_column_order_is_ignored
|
59
170
|
create_table(:users) do |t|
|
60
171
|
t.string :email
|
@@ -95,6 +206,108 @@ OUTPUT
|
|
95
206
|
refute_problems
|
96
207
|
end
|
97
208
|
|
209
|
+
def test_has_one_without_index
|
210
|
+
create_table(:users)
|
211
|
+
.create_model do
|
212
|
+
has_one :account, class_name: "ModelFactory::Models::Account"
|
213
|
+
has_one :account_history, through: :account, class_name: "ModelFactory::Models::Account"
|
214
|
+
end
|
215
|
+
|
216
|
+
create_table(:accounts) do |t|
|
217
|
+
t.integer :user_id
|
218
|
+
end.create_model do
|
219
|
+
has_one :account_history, class_name: "ModelFactory::Models::AccountHistory"
|
220
|
+
end
|
221
|
+
|
222
|
+
create_table(:account_histories) do |t|
|
223
|
+
t.integer :account_id
|
224
|
+
end.create_model do
|
225
|
+
belongs_to :account, class_name: "ModelFactory::Models::Account"
|
226
|
+
end
|
227
|
+
|
228
|
+
assert_problems(<<~OUTPUT)
|
229
|
+
add a unique index on accounts(user_id) - using `has_one` in the ModelFactory::Models::User model without an index can lead to duplicates
|
230
|
+
add a unique index on account_histories(account_id) - using `has_one` in the ModelFactory::Models::Account model without an index can lead to duplicates
|
231
|
+
OUTPUT
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_has_one_with_scope_and_without_index
|
235
|
+
create_table(:users)
|
236
|
+
.create_model do
|
237
|
+
has_one :last_comment, -> { order(created_at: :desc) }, class_name: "ModelFactory::Models::Comment"
|
238
|
+
end
|
239
|
+
|
240
|
+
create_table(:comments) do |t|
|
241
|
+
t.integer :user_id
|
242
|
+
end.create_model
|
243
|
+
|
244
|
+
refute_problems
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_has_one_with_index
|
248
|
+
create_table(:users)
|
249
|
+
.create_model do
|
250
|
+
has_one :account, class_name: "ModelFactory::Models::Account"
|
251
|
+
end
|
252
|
+
|
253
|
+
create_table(:accounts) do |t|
|
254
|
+
t.integer :user_id, index: { unique: true }
|
255
|
+
end.create_model
|
256
|
+
|
257
|
+
refute_problems
|
258
|
+
end
|
259
|
+
|
260
|
+
def test_config_ignore_models
|
261
|
+
create_table(:users) do |t|
|
262
|
+
t.string :email
|
263
|
+
end.create_model do
|
264
|
+
validates :email, uniqueness: true
|
265
|
+
end
|
266
|
+
|
267
|
+
config_file(<<-CONFIG)
|
268
|
+
ActiveRecordDoctor.configure do |config|
|
269
|
+
config.detector :missing_unique_indexes,
|
270
|
+
ignore_models: ["ModelFactory::Models::User"]
|
271
|
+
end
|
272
|
+
CONFIG
|
273
|
+
|
274
|
+
refute_problems
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_global_ignore_models
|
278
|
+
create_table(:users) do |t|
|
279
|
+
t.string :email
|
280
|
+
end.create_model do
|
281
|
+
validates :email, uniqueness: true
|
282
|
+
end
|
283
|
+
|
284
|
+
config_file(<<-CONFIG)
|
285
|
+
ActiveRecordDoctor.configure do |config|
|
286
|
+
config.global :ignore_models, ["ModelFactory::Models::User"]
|
287
|
+
end
|
288
|
+
CONFIG
|
289
|
+
|
290
|
+
refute_problems
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_config_ignore_columns
|
294
|
+
create_table(:users) do |t|
|
295
|
+
t.string :email
|
296
|
+
t.integer :role
|
297
|
+
end.create_model do
|
298
|
+
validates :email, :role, uniqueness: { scope: :organization_id }
|
299
|
+
end
|
300
|
+
|
301
|
+
config_file(<<-CONFIG)
|
302
|
+
ActiveRecordDoctor.configure do |config|
|
303
|
+
config.detector :missing_unique_indexes,
|
304
|
+
ignore_columns: ["ModelFactory::Models::User(organization_id, email)", "ModelFactory::Models::User(organization_id, role)"]
|
305
|
+
end
|
306
|
+
CONFIG
|
307
|
+
|
308
|
+
refute_problems
|
309
|
+
end
|
310
|
+
|
98
311
|
class DummyValidator < ActiveModel::Validator
|
99
312
|
def validate(record)
|
100
313
|
end
|