active_record_doctor 1.7.0 → 1.9.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +287 -43
- data/lib/active_record_doctor/config/default.rb +59 -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 +155 -0
- data/lib/active_record_doctor/detectors/extraneous_indexes.rb +114 -0
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +49 -0
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +107 -0
- data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +45 -0
- data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +60 -0
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +72 -0
- data/lib/active_record_doctor/{tasks → detectors}/missing_presence_validation.rb +35 -21
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +71 -0
- data/lib/active_record_doctor/detectors/short_primary_key_type.rb +41 -0
- data/lib/active_record_doctor/detectors/undefined_table_references.rb +33 -0
- data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +55 -0
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +59 -0
- data/lib/active_record_doctor/detectors.rb +21 -0
- data/lib/active_record_doctor/errors.rb +226 -0
- data/lib/active_record_doctor/help.rb +39 -0
- data/lib/active_record_doctor/printers.rb +3 -1
- data/lib/active_record_doctor/railtie.rb +2 -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 +3 -1
- data/lib/active_record_doctor.rb +24 -2
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +46 -29
- data/lib/tasks/active_record_doctor.rake +21 -29
- 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/extraneous_indexes_test.rb +190 -0
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +79 -0
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +295 -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 +70 -0
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +216 -0
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +168 -0
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +163 -0
- data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +64 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +57 -0
- data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +171 -0
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +78 -0
- 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 +128 -0
- data/test/setup.rb +116 -0
- metadata +105 -156
- data/Rakefile +0 -28
- data/lib/active_record_doctor/printers/io_printer.rb +0 -105
- data/lib/active_record_doctor/tasks/base.rb +0 -78
- data/lib/active_record_doctor/tasks/extraneous_indexes.rb +0 -82
- data/lib/active_record_doctor/tasks/incorrect_boolean_presence_validation.rb +0 -33
- data/lib/active_record_doctor/tasks/missing_foreign_keys.rb +0 -46
- data/lib/active_record_doctor/tasks/missing_non_null_constraint.rb +0 -49
- data/lib/active_record_doctor/tasks/missing_unique_indexes.rb +0 -56
- data/lib/active_record_doctor/tasks/undefined_table_references.rb +0 -33
- data/lib/active_record_doctor/tasks/unindexed_deleted_at.rb +0 -19
- data/lib/active_record_doctor/tasks/unindexed_foreign_keys.rb +0 -43
- data/lib/active_record_doctor/tasks.rb +0 -7
- data/test/active_record_doctor/printers/io_printer_test.rb +0 -20
- data/test/active_record_doctor/tasks/extraneous_indexes_test.rb +0 -81
- data/test/active_record_doctor/tasks/incorrect_boolean_presence_validation_test.rb +0 -33
- data/test/active_record_doctor/tasks/missing_foreign_keys_test.rb +0 -27
- data/test/active_record_doctor/tasks/missing_non_null_constraint_test.rb +0 -102
- data/test/active_record_doctor/tasks/missing_presence_validation_test.rb +0 -110
- data/test/active_record_doctor/tasks/missing_unique_indexes_test.rb +0 -95
- data/test/active_record_doctor/tasks/undefined_table_references_test.rb +0 -51
- data/test/active_record_doctor/tasks/unindexed_deleted_at_test.rb +0 -34
- data/test/active_record_doctor/tasks/unindexed_foreign_keys_test.rb +0 -27
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/config/manifest.js +0 -1
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/models/application_record.rb +0 -3
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/bin/setup +0 -29
- data/test/dummy/config/application.rb +0 -23
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/database.yml +0 -19
- data/test/dummy/config/database.yml.travis +0 -5
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -41
- data/test/dummy/config/environments/production.rb +0 -79
- data/test/dummy/config/environments/test.rb +0 -47
- data/test/dummy/config/initializers/assets.rb +0 -11
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -4
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/config/secrets.yml +0 -22
- data/test/dummy/config.ru +0 -4
- data/test/dummy/db/migrate/base_migration.rb +0 -5
- data/test/dummy/db/schema.rb +0 -19
- data/test/dummy/log/development.log +0 -111
- data/test/dummy/log/test.log +0 -67508
- data/test/dummy/public/404.html +0 -67
- data/test/dummy/public/422.html +0 -67
- data/test/dummy/public/500.html +0 -66
- data/test/dummy/public/favicon.ico +0 -0
- data/test/support/assertions.rb +0 -11
- data/test/support/temping.rb +0 -25
- data/test/test_helper.rb +0 -17
data/lib/active_record_doctor.rb
CHANGED
@@ -1,4 +1,26 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "active_record_doctor/railtie" if defined?(Rails) && defined?(Rails::Railtie)
|
4
|
+
require "active_record_doctor/detectors"
|
5
|
+
require "active_record_doctor/detectors/base"
|
6
|
+
require "active_record_doctor/detectors/missing_presence_validation"
|
7
|
+
require "active_record_doctor/detectors/missing_foreign_keys"
|
8
|
+
require "active_record_doctor/detectors/missing_unique_indexes"
|
9
|
+
require "active_record_doctor/detectors/incorrect_boolean_presence_validation"
|
10
|
+
require "active_record_doctor/detectors/extraneous_indexes"
|
11
|
+
require "active_record_doctor/detectors/unindexed_deleted_at"
|
12
|
+
require "active_record_doctor/detectors/undefined_table_references"
|
13
|
+
require "active_record_doctor/detectors/missing_non_null_constraint"
|
14
|
+
require "active_record_doctor/detectors/unindexed_foreign_keys"
|
15
|
+
require "active_record_doctor/detectors/incorrect_dependent_option"
|
16
|
+
require "active_record_doctor/detectors/short_primary_key_type"
|
17
|
+
require "active_record_doctor/detectors/mismatched_foreign_key_type"
|
18
|
+
require "active_record_doctor/errors"
|
19
|
+
require "active_record_doctor/help"
|
20
|
+
require "active_record_doctor/runner"
|
21
|
+
require "active_record_doctor/version"
|
22
|
+
require "active_record_doctor/config"
|
23
|
+
require "active_record_doctor/config/loader"
|
24
|
+
|
25
|
+
module ActiveRecordDoctor # :nodoc:
|
4
26
|
end
|
@@ -1,65 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecordDoctor
|
4
|
+
# Generate migrations that add missing indexes to the database.
|
2
5
|
class AddIndexesGenerator < Rails::Generators::Base
|
3
|
-
|
4
|
-
|
5
|
-
desc 'Generate migrations for the specified indexes'
|
6
|
-
argument :path, type: :string, default: nil, banner: 'PATH'
|
6
|
+
desc "Generate migrations for the specified indexes"
|
7
|
+
argument :path, type: :string, default: nil, banner: "PATH"
|
7
8
|
|
8
9
|
def create_migrations
|
9
10
|
migration_descriptions = read_migration_descriptions(path)
|
10
11
|
now = Time.now
|
11
12
|
|
12
|
-
migration_descriptions.each_with_index do |
|
13
|
+
migration_descriptions.each_with_index do |(table, columns), index|
|
13
14
|
timestamp = (now + index).strftime("%Y%m%d%H%M%S")
|
14
|
-
file_name = "db/migrate/#{timestamp}_index_foreign_keys_in_#{
|
15
|
-
create_file(file_name, content(
|
15
|
+
file_name = "db/migrate/#{timestamp}_index_foreign_keys_in_#{table}.rb"
|
16
|
+
create_file(file_name, content(table, columns).tap { |x| puts x })
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
20
|
private
|
20
21
|
|
22
|
+
INPUT_LINE = /^add an index on (\w+)\.(\w+) - .*$/
|
23
|
+
private_constant :INPUT_LINE
|
24
|
+
|
21
25
|
def read_migration_descriptions(path)
|
22
|
-
|
23
|
-
table, *columns = line.split(/\s+/)
|
26
|
+
tables_to_columns = Hash.new { |hash, table| hash[table] = [] }
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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}")
|
30
34
|
end
|
31
35
|
|
32
|
-
|
36
|
+
table = match[1]
|
37
|
+
column = match[2]
|
38
|
+
|
39
|
+
tables_to_columns[table] << column
|
33
40
|
end
|
41
|
+
|
42
|
+
tables_to_columns
|
34
43
|
end
|
35
44
|
|
36
|
-
def content(
|
37
|
-
|
38
|
-
|
45
|
+
def content(table, columns)
|
46
|
+
# In order to properly indent the resulting code, we must disable the
|
47
|
+
# rubocop rule below.
|
48
|
+
|
49
|
+
<<MIGRATION
|
50
|
+
class IndexForeignKeysIn#{table.camelize} < ActiveRecord::Migration#{migration_version}
|
39
51
|
def change
|
40
|
-
#{add_indexes(
|
52
|
+
#{add_indexes(table, columns)}
|
41
53
|
end
|
42
54
|
end
|
43
|
-
|
55
|
+
MIGRATION
|
44
56
|
end
|
45
57
|
|
46
|
-
def add_indexes(
|
47
|
-
|
48
|
-
add_index(
|
58
|
+
def add_indexes(table, columns)
|
59
|
+
columns.map do |column|
|
60
|
+
add_index(table, column)
|
49
61
|
end.join("\n")
|
50
62
|
end
|
51
63
|
|
52
64
|
def add_index(table, column)
|
53
|
-
|
65
|
+
index_name = Class.new.extend(ActiveRecord::ConnectionAdapters::SchemaStatements).index_name table, column
|
66
|
+
# rubocop:disable Layout/LineLength
|
67
|
+
if index_name.size > ActiveRecord::Base.connection.allowed_index_name_length
|
68
|
+
" add_index :#{table}, :#{column}, name: '#{index_name.first ActiveRecord::Base.connection.allowed_index_name_length}'"
|
69
|
+
else
|
70
|
+
" add_index :#{table}, :#{column}"
|
71
|
+
end
|
72
|
+
# rubocop:enable Layout/LineLength
|
54
73
|
end
|
55
74
|
|
56
75
|
def migration_version
|
57
|
-
|
58
|
-
|
59
|
-
if major >= 5 && minor >= 1
|
60
|
-
"[#{major}.#{minor}]"
|
76
|
+
if ActiveRecord::VERSION::STRING >= "5.1"
|
77
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
61
78
|
else
|
62
|
-
|
79
|
+
""
|
63
80
|
end
|
64
81
|
end
|
65
82
|
end
|
@@ -1,32 +1,24 @@
|
|
1
|
-
|
2
|
-
require "active_record_doctor/tasks/unindexed_foreign_keys"
|
3
|
-
require "active_record_doctor/tasks/extraneous_indexes"
|
4
|
-
require "active_record_doctor/tasks/missing_foreign_keys"
|
5
|
-
require "active_record_doctor/tasks/undefined_table_references"
|
6
|
-
require "active_record_doctor/tasks/unindexed_deleted_at"
|
7
|
-
require "active_record_doctor/tasks/missing_unique_indexes"
|
8
|
-
require "active_record_doctor/tasks/missing_presence_validation"
|
9
|
-
require "active_record_doctor/tasks/missing_non_null_constraint"
|
10
|
-
require "active_record_doctor/tasks/incorrect_boolean_presence_validation"
|
1
|
+
# frozen_string_literal: true
|
11
2
|
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
require "active_record_doctor/detectors"
|
4
|
+
require "active_record_doctor/detectors/unindexed_foreign_keys"
|
5
|
+
require "active_record_doctor/detectors/extraneous_indexes"
|
6
|
+
require "active_record_doctor/detectors/missing_foreign_keys"
|
7
|
+
require "active_record_doctor/detectors/undefined_table_references"
|
8
|
+
require "active_record_doctor/detectors/unindexed_deleted_at"
|
9
|
+
require "active_record_doctor/detectors/missing_unique_indexes"
|
10
|
+
require "active_record_doctor/detectors/missing_presence_validation"
|
11
|
+
require "active_record_doctor/detectors/missing_non_null_constraint"
|
12
|
+
require "active_record_doctor/detectors/incorrect_boolean_presence_validation"
|
13
|
+
require "active_record_doctor/detectors/incorrect_dependent_option"
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# nil doesn't indicate a failure but rather no explicit result. We assume
|
24
|
-
# success by default hence only false results in an erroneous exit code.
|
25
|
-
exit(1) if success == false
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
ActiveRecordDoctor::Tasks.all.each do |task|
|
30
|
-
mount task
|
31
|
-
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! }
|
32
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,190 @@
|
|
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 - coincides with the primary key on the table
|
11
|
+
OUTPUT
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_non_unique_version_of_index_is_duplicate
|
15
|
+
create_table(:users) do |t|
|
16
|
+
t.string :email
|
17
|
+
t.index :email, unique: true, name: "unique_index_on_users_email"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Rails 4.2 compatibility - can't be pulled into the block above.
|
21
|
+
ActiveRecord::Base.connection.add_index :users, :email, name: "index_users_on_email"
|
22
|
+
|
23
|
+
assert_problems(<<OUTPUT)
|
24
|
+
remove index_users_on_email - can be replaced by unique_index_on_users_email
|
25
|
+
OUTPUT
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_single_column_covered_by_unique_and_non_unique_multi_column_is_duplicate
|
29
|
+
create_table(:users) do |t|
|
30
|
+
t.string :first_name
|
31
|
+
t.string :last_name
|
32
|
+
t.string :email
|
33
|
+
t.index [:last_name, :first_name, :email]
|
34
|
+
t.index [:last_name, :first_name],
|
35
|
+
unique: true,
|
36
|
+
name: "unique_index_on_users_last_name_and_first_name"
|
37
|
+
t.index :last_name
|
38
|
+
end
|
39
|
+
|
40
|
+
assert_problems(<<OUTPUT)
|
41
|
+
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
|
42
|
+
OUTPUT
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_multi_column_covered_by_unique_and_non_unique_multi_column_is_duplicate
|
46
|
+
create_table(:users) do |t|
|
47
|
+
t.string :first_name
|
48
|
+
t.string :last_name
|
49
|
+
t.string :email
|
50
|
+
t.index [:last_name, :first_name, :email]
|
51
|
+
t.index [:last_name, :first_name],
|
52
|
+
unique: true,
|
53
|
+
name: "unique_index_on_users_last_name_and_first_name"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Rails 4.2 compatibility - can't be pulled into the block above.
|
57
|
+
ActiveRecord::Base.connection.add_index :users, [:last_name, :first_name]
|
58
|
+
|
59
|
+
assert_problems(<<OUTPUT)
|
60
|
+
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
|
61
|
+
OUTPUT
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_not_covered_by_different_index_type
|
65
|
+
create_table(:users) do |t|
|
66
|
+
t.string :first_name
|
67
|
+
t.string :last_name
|
68
|
+
t.index [:last_name, :first_name], using: :btree
|
69
|
+
|
70
|
+
if mysql?
|
71
|
+
t.index :last_name, type: :fulltext
|
72
|
+
else
|
73
|
+
t.index :last_name, using: :hash
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
refute_problems
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_not_covered_by_partial_index
|
81
|
+
skip("MySQL doesn't support partial indexes") if mysql?
|
82
|
+
|
83
|
+
create_table(:users) do |t|
|
84
|
+
t.string :first_name
|
85
|
+
t.string :last_name
|
86
|
+
t.boolean :active
|
87
|
+
t.index [:last_name, :first_name], where: "active"
|
88
|
+
t.index :last_name
|
89
|
+
end
|
90
|
+
|
91
|
+
refute_problems
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_not_covered_with_different_opclasses
|
95
|
+
skip("ActiveRecord < 5.2 doesn't support operator classes") if ActiveRecord::VERSION::STRING < "5.2"
|
96
|
+
skip("MySQL doesn't support operator classes") if mysql?
|
97
|
+
|
98
|
+
create_table(:users) do |t|
|
99
|
+
t.string :first_name
|
100
|
+
t.string :last_name
|
101
|
+
t.index [:last_name, :first_name], opclass: :varchar_pattern_ops
|
102
|
+
t.index :last_name
|
103
|
+
end
|
104
|
+
|
105
|
+
refute_problems
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_config_ignore_tables
|
109
|
+
# The detector recognizes two kinds of errors and both must take
|
110
|
+
# ignore_tables into account. We trigger those errors by indexing the
|
111
|
+
# primary key (the first extraneous index) and then indexing email twice
|
112
|
+
# (index2... is the other extraneous index).
|
113
|
+
create_table(:users) do |t|
|
114
|
+
t.index :id
|
115
|
+
t.string :email
|
116
|
+
|
117
|
+
t.index :email, name: "index1_on_users_email"
|
118
|
+
t.index :email, name: "index2_on_users_email"
|
119
|
+
end
|
120
|
+
|
121
|
+
config_file(<<-CONFIG)
|
122
|
+
ActiveRecordDoctor.configure do |config|
|
123
|
+
config.detector :extraneous_indexes,
|
124
|
+
ignore_tables: ["users"]
|
125
|
+
end
|
126
|
+
CONFIG
|
127
|
+
|
128
|
+
refute_problems
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_config_global_ignore_tables
|
132
|
+
create_table(:users) do |t|
|
133
|
+
t.index :id
|
134
|
+
t.string :email
|
135
|
+
|
136
|
+
t.index :email, name: "index1_on_users_email"
|
137
|
+
t.index :email, name: "index2_on_users_email"
|
138
|
+
end
|
139
|
+
|
140
|
+
config_file(<<-CONFIG)
|
141
|
+
ActiveRecordDoctor.configure do |config|
|
142
|
+
config.global :ignore_tables, ["users"]
|
143
|
+
end
|
144
|
+
CONFIG
|
145
|
+
|
146
|
+
refute_problems
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_config_global_ignore_indexes
|
150
|
+
create_table(:users) do |t|
|
151
|
+
t.index :id
|
152
|
+
t.string :email
|
153
|
+
|
154
|
+
t.index :email, name: "index1_on_users_email"
|
155
|
+
t.index :email, name: "index2_on_users_email"
|
156
|
+
end
|
157
|
+
|
158
|
+
config_file(<<-CONFIG)
|
159
|
+
ActiveRecordDoctor.configure do |config|
|
160
|
+
config.global :ignore_indexes, [
|
161
|
+
"index1_on_users_email",
|
162
|
+
"index2_on_users_email",
|
163
|
+
"index_users_on_id",
|
164
|
+
]
|
165
|
+
end
|
166
|
+
CONFIG
|
167
|
+
|
168
|
+
refute_problems
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_config_detector_ignore_indexes
|
172
|
+
create_table(:users) do |t|
|
173
|
+
t.index :id
|
174
|
+
t.string :email
|
175
|
+
t.string :api_key
|
176
|
+
|
177
|
+
t.index :email, name: "index_on_users_email"
|
178
|
+
t.index [:email, :api_key], name: "index_on_users_email_and_api_key"
|
179
|
+
end
|
180
|
+
|
181
|
+
config_file(<<-CONFIG)
|
182
|
+
ActiveRecordDoctor.configure do |config|
|
183
|
+
config.detector :extraneous_indexes,
|
184
|
+
ignore_indexes: ["index_users_on_id", "index_on_users_email"]
|
185
|
+
end
|
186
|
+
CONFIG
|
187
|
+
|
188
|
+
refute_problems
|
189
|
+
end
|
190
|
+
end
|