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
@@ -3,8 +3,6 @@
3
3
  module ActiveRecordDoctor
4
4
  # Generate migrations that add missing indexes to the database.
5
5
  class AddIndexesGenerator < Rails::Generators::Base
6
- MigrationDescription = Struct.new(:table, :columns)
7
-
8
6
  desc "Generate migrations for the specified indexes"
9
7
  argument :path, type: :string, default: nil, banner: "PATH"
10
8
 
@@ -12,56 +10,71 @@ module ActiveRecordDoctor
12
10
  migration_descriptions = read_migration_descriptions(path)
13
11
  now = Time.now
14
12
 
15
- migration_descriptions.each_with_index do |migration_description, index|
13
+ migration_descriptions.each_with_index do |(table, columns), index|
16
14
  timestamp = (now + index).strftime("%Y%m%d%H%M%S")
17
- file_name = "db/migrate/#{timestamp}_index_foreign_keys_in_#{migration_description.table}.rb"
18
- create_file(file_name, content(migration_description))
15
+ file_name = "db/migrate/#{timestamp}_index_foreign_keys_in_#{table}.rb"
16
+ create_file(file_name, content(table, columns).tap { |x| puts x })
19
17
  end
20
18
  end
21
19
 
22
20
  private
23
21
 
22
+ INPUT_LINE = /^add an index on (\w+)\.(\w+) - .*$/.freeze
23
+ private_constant :INPUT_LINE
24
+
24
25
  def read_migration_descriptions(path)
25
- File.readlines(path).each_with_index.map do |line, index|
26
- table, *columns = line.split(/\s+/)
26
+ tables_to_columns = Hash.new { |hash, table| hash[table] = [] }
27
27
 
28
- if table.empty?
29
- raise("No table name in #{path} on line #{index + 1}. Ensure the line doesn't start with whitespace.")
30
- end
31
- if columns.empty?
32
- raise("No columns for table #{table} in #{path} on line #{index + 1}.")
28
+ File.readlines(path).each_with_index do |line, index|
29
+ next if line.blank?
30
+
31
+ match = INPUT_LINE.match(line)
32
+ if match.nil?
33
+ raise("cannot extract table and column name from line #{index + 1}: #{line}")
33
34
  end
34
35
 
35
- MigrationDescription.new(table, columns)
36
+ table = match[1]
37
+ column = match[2]
38
+
39
+ tables_to_columns[table] << column
36
40
  end
41
+
42
+ tables_to_columns
37
43
  end
38
44
 
39
- def content(migration_description)
45
+ def content(table, columns)
40
46
  # In order to properly indent the resulting code, we must disable the
41
47
  # rubocop rule below.
42
48
 
43
49
  <<MIGRATION
44
- class IndexForeignKeysIn#{migration_description.table.camelize} < ActiveRecord::Migration#{migration_version}
50
+ class IndexForeignKeysIn#{table.camelize} < ActiveRecord::Migration#{migration_version}
45
51
  def change
46
- #{add_indexes(migration_description)}
52
+ #{add_indexes(table, columns)}
47
53
  end
48
54
  end
49
55
  MIGRATION
50
56
  end
51
57
 
52
- def add_indexes(migration_description)
53
- migration_description.columns.map do |column|
54
- add_index(migration_description.table, column)
58
+ def add_indexes(table, columns)
59
+ columns.map do |column|
60
+ add_index(table, column)
55
61
  end.join("\n")
56
62
  end
57
63
 
58
64
  def add_index(table, column)
59
- " add_index :#{table}, :#{column}"
65
+ connection = ActiveRecord::Base.connection
66
+
67
+ index_name = connection.index_name(table, column)
68
+ if index_name.size > connection.index_name_length
69
+ " add_index :#{table}, :#{column}, name: '#{index_name.first(connection.index_name_length)}'"
70
+ else
71
+ " add_index :#{table}, :#{column}"
72
+ end
60
73
  end
61
74
 
62
75
  def migration_version
63
76
  if ActiveRecord::VERSION::STRING >= "5.1"
64
- "[#{version}]"
77
+ "[#{ActiveRecord::Migration.current_version}]"
65
78
  else
66
79
  ""
67
80
  end
@@ -11,23 +11,14 @@ require "active_record_doctor/detectors/missing_presence_validation"
11
11
  require "active_record_doctor/detectors/missing_non_null_constraint"
12
12
  require "active_record_doctor/detectors/incorrect_boolean_presence_validation"
13
13
  require "active_record_doctor/detectors/incorrect_dependent_option"
14
- require "active_record_doctor/task"
14
+ require "active_record_doctor/detectors/short_primary_key_type"
15
+ require "active_record_doctor/detectors/mismatched_foreign_key_type"
16
+ require "active_record_doctor/rake/task"
15
17
 
16
- namespace :active_record_doctor do
17
- tasks = ActiveRecordDoctor::Detectors.all.map do |detector_class|
18
- ActiveRecordDoctor::Task.new(detector_class)
19
- end
20
-
21
- tasks.each do |task|
22
- desc task.description
23
- task task.name => :environment do
24
- task.run or exit(1)
25
- end
26
- end
27
-
28
- desc "Run all active_record_doctor tasks"
29
- task :all => :environment do
30
- results = tasks.map { |task| task.run }
31
- results.all? or exit(1)
32
- end
18
+ ActiveRecordDoctor::Rake::Task.new do |task|
19
+ # This file is imported when active_record_doctor is being used as part of a
20
+ # Rails app so it's the right place for all Rails-specific settings.
21
+ task.deps = [:environment]
22
+ task.config_path = ::Rails.root.join(".active_record_doctor")
23
+ task.setup = -> { ::Rails.application.eager_load! }
33
24
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveRecordDoctor::LoaderTest < Minitest::Test
4
+ def test_load_config
5
+ config_path = config_file(<<-CONFIG)
6
+ ActiveRecordDoctor.configure do |config|
7
+ config.global :ignore_tables, ["schema_migrations"]
8
+
9
+ config.detector :extraneous_indexes, ignore_tables: ["users"]
10
+ end
11
+ CONFIG
12
+
13
+ config = ActiveRecordDoctor.load_config(config_path)
14
+
15
+ assert_equal(
16
+ { ignore_tables: ["schema_migrations"] },
17
+ config.globals
18
+ )
19
+ assert_equal(
20
+ { extraneous_indexes: { ignore_tables: ["users"] } },
21
+ config.detectors
22
+ )
23
+ end
24
+
25
+ def test_load_config_raises_when_configuration_file_missing
26
+ exc = assert_raises(ActiveRecordDoctor::Error::ConfigurationFileMissing) do
27
+ ActiveRecordDoctor.load_config("/tmp/config.rb")
28
+ end
29
+ assert_equal("/tmp/config.rb", exc.config_path)
30
+ end
31
+
32
+ def test_load_config_raises_when_configuration_file_raises
33
+ config_path = config_file("1/0")
34
+
35
+ assert_raises(ActiveRecordDoctor::Error::ConfigurationError) do
36
+ ActiveRecordDoctor.load_config(config_path)
37
+ end
38
+ end
39
+
40
+ def test_load_config_raises_when_configure_not_called
41
+ config_path = config_file("# configure is not called")
42
+
43
+ assert_raises(ActiveRecordDoctor::Error::ConfigureNotCalled) do
44
+ ActiveRecordDoctor.load_config(config_path)
45
+ end
46
+ end
47
+
48
+ def test_load_config_raises_when_configure_called_twice
49
+ config_path = config_file(<<-CONFIG)
50
+ ActiveRecordDoctor.configure { |config| }
51
+ ActiveRecordDoctor.configure { |config| }
52
+ CONFIG
53
+
54
+ assert_raises(ActiveRecordDoctor::Error::ConfigureCalledTwice) do
55
+ ActiveRecordDoctor.load_config(config_path)
56
+ end
57
+ end
58
+
59
+ def test_load_config_raises_when_unrecognized_global_set
60
+ config_path = config_file(<<-CONFIG)
61
+ ActiveRecordDoctor.configure do |config|
62
+ config.global :user, "acme"
63
+ end
64
+ CONFIG
65
+
66
+ assert_raises(ActiveRecordDoctor::Error::UnrecognizedGlobalSetting) do
67
+ ActiveRecordDoctor.load_config(config_path)
68
+ end
69
+ end
70
+
71
+ def test_load_config_raises_when_global_set_twice
72
+ config_path = config_file(<<-CONFIG)
73
+ ActiveRecordDoctor.configure do |config|
74
+ config.global :ignore_tables, ["schema_migrations"]
75
+ config.global :ignore_tables, ["schema_migrations"]
76
+ end
77
+ CONFIG
78
+
79
+ assert_raises(ActiveRecordDoctor::Error::DuplicateGlobalSetting) do
80
+ ActiveRecordDoctor.load_config(config_path)
81
+ end
82
+ end
83
+
84
+ def test_load_config_raises_when_configured_unrecognized_detector
85
+ config_path = config_file(<<-CONFIG)
86
+ ActiveRecordDoctor.configure do |config|
87
+ config.detector :other_performance_issues, {}
88
+ end
89
+ CONFIG
90
+
91
+ assert_raises(ActiveRecordDoctor::Error::UnrecognizedDetectorName) do
92
+ ActiveRecordDoctor.load_config(config_path)
93
+ end
94
+ end
95
+
96
+ def test_load_config_raises_when_detector_configured_twice
97
+ config_path = config_file(<<-CONFIG)
98
+ ActiveRecordDoctor.configure do |config|
99
+ config.detector :extraneous_indexes, ignore_tables: ["users"]
100
+ config.detector :extraneous_indexes, ignore_tables: ["users"]
101
+ end
102
+ CONFIG
103
+
104
+ assert_raises(ActiveRecordDoctor::Error::DetectorConfiguredTwice) do
105
+ ActiveRecordDoctor.load_config(config_path)
106
+ end
107
+ end
108
+
109
+ def test_load_config_raises_when_provided_unrecognized_detector_setting
110
+ config_path = config_file(<<-CONFIG)
111
+ ActiveRecordDoctor.configure do |config|
112
+ config.detector :extraneous_indexes, { delay: 1 }
113
+ end
114
+ CONFIG
115
+
116
+ assert_raises(ActiveRecordDoctor::Error::UnrecognizedDetectorSettings) do
117
+ ActiveRecordDoctor.load_config(config_path)
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,116 @@
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
@@ -0,0 +1,30 @@
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.create_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
@@ -7,8 +7,28 @@ class ActiveRecordDoctor::Detectors::ExtraneousIndexesTest < Minitest::Test
7
7
  end
8
8
 
9
9
  assert_problems(<<OUTPUT)
10
- The following indexes are extraneous and can be removed:
11
- index_users_on_id (is a primary key of users)
10
+ remove index_users_on_id - 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 - coincides with the primary key on the table
12
32
  OUTPUT
13
33
  end
14
34
 
@@ -22,8 +42,7 @@ OUTPUT
22
42
  ActiveRecord::Base.connection.add_index :users, :email, name: "index_users_on_email"
23
43
 
24
44
  assert_problems(<<OUTPUT)
25
- The following indexes are extraneous and can be removed:
26
- index_users_on_email (can be handled by unique_index_on_users_email)
45
+ remove index_users_on_email - can be replaced by unique_index_on_users_email
27
46
  OUTPUT
28
47
  end
29
48
 
@@ -40,8 +59,7 @@ OUTPUT
40
59
  end
41
60
 
42
61
  assert_problems(<<OUTPUT)
43
- The following indexes are extraneous and can be removed:
44
- index_users_on_last_name (can be handled by index_users_on_last_name_and_first_name_and_email, unique_index_on_users_last_name_and_first_name)
62
+ remove index_users_on_last_name - can be replaced by index_users_on_last_name_and_first_name_and_email or unique_index_on_users_last_name_and_first_name
45
63
  OUTPUT
46
64
  end
47
65
 
@@ -60,8 +78,147 @@ OUTPUT
60
78
  ActiveRecord::Base.connection.add_index :users, [:last_name, :first_name]
61
79
 
62
80
  assert_problems(<<OUTPUT)
63
- The following indexes are extraneous and can be removed:
64
- index_users_on_last_name_and_first_name (can be handled by index_users_on_last_name_and_first_name_and_email, unique_index_on_users_last_name_and_first_name)
81
+ remove index_users_on_last_name_and_first_name - can be replaced by 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 index_users_on_last_name_and_first_name - can be replaced by index_users_on_first_name
65
95
  OUTPUT
66
96
  end
97
+
98
+ def test_not_covered_by_different_index_type
99
+ create_table(:users) do |t|
100
+ t.string :first_name
101
+ t.string :last_name
102
+ t.index [:last_name, :first_name], using: :btree
103
+
104
+ if mysql?
105
+ t.index :last_name, type: :fulltext
106
+ else
107
+ t.index :last_name, using: :hash
108
+ end
109
+ end
110
+
111
+ refute_problems
112
+ end
113
+
114
+ def test_not_covered_by_partial_index
115
+ skip("MySQL doesn't support partial indexes") if mysql?
116
+
117
+ create_table(:users) do |t|
118
+ t.string :first_name
119
+ t.string :last_name
120
+ t.boolean :active
121
+ t.index [:last_name, :first_name], where: "active"
122
+ t.index :last_name
123
+ end
124
+
125
+ refute_problems
126
+ end
127
+
128
+ def test_not_covered_with_different_opclasses
129
+ skip("ActiveRecord < 5.2 doesn't support operator classes") if ActiveRecord::VERSION::STRING < "5.2"
130
+ skip("MySQL doesn't support operator classes") if mysql?
131
+
132
+ create_table(:users) do |t|
133
+ t.string :first_name
134
+ t.string :last_name
135
+ t.index [:last_name, :first_name], opclass: :varchar_pattern_ops
136
+ t.index :last_name
137
+ end
138
+
139
+ refute_problems
140
+ end
141
+
142
+ def test_config_ignore_tables
143
+ # The detector recognizes two kinds of errors and both must take
144
+ # ignore_tables into account. We trigger those errors by indexing the
145
+ # primary key (the first extraneous index) and then indexing email twice
146
+ # (index2... is the other extraneous index).
147
+ create_table(:users) do |t|
148
+ t.index :id
149
+ t.string :email
150
+
151
+ t.index :email, name: "index1_on_users_email"
152
+ t.index :email, name: "index2_on_users_email"
153
+ end
154
+
155
+ config_file(<<-CONFIG)
156
+ ActiveRecordDoctor.configure do |config|
157
+ config.detector :extraneous_indexes,
158
+ ignore_tables: ["users"]
159
+ end
160
+ CONFIG
161
+
162
+ refute_problems
163
+ end
164
+
165
+ def test_config_global_ignore_tables
166
+ create_table(:users) do |t|
167
+ t.index :id
168
+ t.string :email
169
+
170
+ t.index :email, name: "index1_on_users_email"
171
+ t.index :email, name: "index2_on_users_email"
172
+ end
173
+
174
+ config_file(<<-CONFIG)
175
+ ActiveRecordDoctor.configure do |config|
176
+ config.global :ignore_tables, ["users"]
177
+ end
178
+ CONFIG
179
+
180
+ refute_problems
181
+ end
182
+
183
+ def test_config_global_ignore_indexes
184
+ create_table(:users) do |t|
185
+ t.index :id
186
+ t.string :email
187
+
188
+ t.index :email, name: "index1_on_users_email"
189
+ t.index :email, name: "index2_on_users_email"
190
+ end
191
+
192
+ config_file(<<-CONFIG)
193
+ ActiveRecordDoctor.configure do |config|
194
+ config.global :ignore_indexes, [
195
+ "index1_on_users_email",
196
+ "index2_on_users_email",
197
+ "index_users_on_id",
198
+ ]
199
+ end
200
+ CONFIG
201
+
202
+ refute_problems
203
+ end
204
+
205
+ def test_config_detector_ignore_indexes
206
+ create_table(:users) do |t|
207
+ t.index :id
208
+ t.string :email
209
+ t.string :api_key
210
+
211
+ t.index :email, name: "index_on_users_email"
212
+ t.index [:email, :api_key], name: "index_on_users_email_and_api_key"
213
+ end
214
+
215
+ config_file(<<-CONFIG)
216
+ ActiveRecordDoctor.configure do |config|
217
+ config.detector :extraneous_indexes,
218
+ ignore_indexes: ["index_users_on_id", "index_on_users_email"]
219
+ end
220
+ CONFIG
221
+
222
+ refute_problems
223
+ end
67
224
  end
@@ -12,10 +12,9 @@ class ActiveRecordDoctor::Detectors::IncorrectBooleanPresenceValidationTest < Mi
12
12
  validates :email, :active, presence: true
13
13
  end
14
14
 
15
- assert_problems(<<OUTPUT)
16
- The presence of the following boolean columns is validated incorrectly:
17
- ModelFactory::Models::User: active
18
- OUTPUT
15
+ assert_problems(<<~OUTPUT)
16
+ replace the `presence` validator on ModelFactory::Models::User.active with `inclusion` - `presence` can't be used on booleans
17
+ OUTPUT
19
18
  end
20
19
 
21
20
  def test_inclusion_is_not_reported
@@ -29,7 +28,51 @@ OUTPUT
29
28
  end
30
29
 
31
30
  def test_models_with_non_existent_tables_are_skipped
32
- create_model(:users)
31
+ create_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.create_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.create_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.create_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
33
76
 
34
77
  refute_problems
35
78
  end