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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +316 -54
  3. data/lib/active_record_doctor/config/default.rb +76 -0
  4. data/lib/active_record_doctor/config/loader.rb +137 -0
  5. data/lib/active_record_doctor/config.rb +14 -0
  6. data/lib/active_record_doctor/detectors/base.rb +142 -21
  7. data/lib/active_record_doctor/detectors/extraneous_indexes.rb +59 -48
  8. data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +31 -23
  9. data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +102 -35
  10. data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +63 -0
  11. data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +45 -0
  12. data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +32 -23
  13. data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +41 -28
  14. data/lib/active_record_doctor/detectors/missing_presence_validation.rb +29 -23
  15. data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +92 -32
  16. data/lib/active_record_doctor/detectors/short_primary_key_type.rb +45 -0
  17. data/lib/active_record_doctor/detectors/undefined_table_references.rb +17 -20
  18. data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +43 -18
  19. data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +31 -20
  20. data/lib/active_record_doctor/detectors.rb +12 -4
  21. data/lib/active_record_doctor/errors.rb +226 -0
  22. data/lib/active_record_doctor/help.rb +39 -0
  23. data/lib/active_record_doctor/rake/task.rb +78 -0
  24. data/lib/active_record_doctor/runner.rb +41 -0
  25. data/lib/active_record_doctor/version.rb +1 -1
  26. data/lib/active_record_doctor.rb +8 -3
  27. data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +34 -21
  28. data/lib/tasks/active_record_doctor.rake +9 -18
  29. data/test/active_record_doctor/config/loader_test.rb +120 -0
  30. data/test/active_record_doctor/config_test.rb +116 -0
  31. data/test/active_record_doctor/detectors/disable_test.rb +30 -0
  32. data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +165 -8
  33. data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +48 -5
  34. data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +288 -12
  35. data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +105 -0
  36. data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +82 -0
  37. data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +50 -4
  38. data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +172 -24
  39. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +111 -14
  40. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +223 -10
  41. data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +72 -0
  42. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +34 -21
  43. data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +118 -8
  44. data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +56 -4
  45. data/test/active_record_doctor/runner_test.rb +42 -0
  46. data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +131 -0
  47. data/test/model_factory.rb +73 -23
  48. data/test/setup.rb +65 -71
  49. metadata +43 -7
  50. data/lib/active_record_doctor/printers/io_printer.rb +0 -133
  51. data/lib/active_record_doctor/task.rb +0 -28
  52. 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 test_presence_true_and_null_true
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(<<OUTPUT)
12
- The following columns should be marked as `null: false`:
13
- users: name
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 test_association_presence_true_and_null_true
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(<<OUTPUT)
26
- The following columns should be marked as `null: false`:
27
- users: company_id
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 test_presence_true_and_null_false
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 test_presence_false_and_null_true
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 test_presence_false_and_null_false
65
+ def test_validators_with_if_on_optional_columns_are_allowed
58
66
  create_table(:users) do |t|
59
- t.string :name, null: false
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 test_presence_true_with_if
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, if: -> { false }
79
+ validates :name, presence: true, unless: -> { false }
72
80
  end
73
81
 
74
82
  refute_problems
75
83
  end
76
84
 
77
- def test_presence_true_with_unless
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, unless: -> { false }
89
+ validates :name, presence: true, allow_nil: true
82
90
  end
83
91
 
84
92
  refute_problems
85
93
  end
86
94
 
87
- def test_presence_true_with_allow_nil
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, allow_nil: 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 test_models_with_non_existent_tables_are_skipped
98
- create_model(:users)
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(<<OUTPUT)
20
- The following models and columns should have presence validations:
21
- ModelFactory::Models::User: name
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(<<OUTPUT)
54
- The following models and columns should have presence validations:
55
- ModelFactory::Models::User: active
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(<<OUTPUT)
87
- The following models and columns should have presence validations:
88
- ModelFactory::Models::User: active
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(:users)
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(<<OUTPUT)
13
- The following indexes should be created to back model-level uniqueness validations:
14
- users: email
15
- OUTPUT
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 test_missing_unique_index_with_scope
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(<<OUTPUT)
40
- The following indexes should be created to back model-level uniqueness validations:
41
- users: company_id, department_id, email
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 test_present_unique_index_with_scope
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