active_record_doctor 1.11.0 → 1.13.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -14
  3. data/lib/active_record_doctor/config/loader.rb +1 -1
  4. data/lib/active_record_doctor/detectors/base.rb +30 -15
  5. data/lib/active_record_doctor/detectors/extraneous_indexes.rb +14 -9
  6. data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +1 -1
  7. data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +44 -31
  8. data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +1 -1
  9. data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +2 -2
  10. data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +73 -23
  11. data/lib/active_record_doctor/detectors/short_primary_key_type.rb +3 -3
  12. data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +34 -7
  13. data/lib/active_record_doctor/logger/hierarchical.rb +1 -1
  14. data/lib/active_record_doctor/railtie.rb +1 -1
  15. data/lib/active_record_doctor/runner.rb +1 -1
  16. data/lib/active_record_doctor/utils.rb +21 -0
  17. data/lib/active_record_doctor/version.rb +1 -1
  18. data/lib/active_record_doctor.rb +2 -0
  19. data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +14 -14
  20. data/lib/tasks/active_record_doctor.rake +2 -2
  21. metadata +11 -47
  22. data/test/active_record_doctor/config/loader_test.rb +0 -120
  23. data/test/active_record_doctor/config_test.rb +0 -116
  24. data/test/active_record_doctor/detectors/disable_test.rb +0 -30
  25. data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +0 -224
  26. data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +0 -79
  27. data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +0 -472
  28. data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +0 -107
  29. data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +0 -116
  30. data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +0 -70
  31. data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +0 -273
  32. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +0 -232
  33. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +0 -327
  34. data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +0 -72
  35. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +0 -55
  36. data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +0 -177
  37. data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +0 -78
  38. data/test/active_record_doctor/runner_test.rb +0 -41
  39. data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +0 -131
  40. data/test/setup.rb +0 -124
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveRecordDoctor::RunnerTest < Minitest::Test
4
- def setup
5
- @io = StringIO.new
6
- @runner = ActiveRecordDoctor::Runner.new(
7
- config: load_config,
8
- logger: ActiveRecordDoctor::Logger::Dummy.new,
9
- io: @io
10
- )
11
- end
12
-
13
- def test_run_one_raises_on_unknown_detectors
14
- assert_raises(KeyError) do
15
- @runner.run_one(:performance_issues)
16
- end
17
- end
18
-
19
- def test_run_all_returns_true_when_no_errors
20
- assert(@runner.run_all)
21
- assert(@io.string.blank?)
22
- end
23
-
24
- def test_run_all_returns_false_when_errors
25
- # Create a model without its underlying table to trigger an error.
26
- define_model(:User)
27
-
28
- refute(@runner.run_all)
29
- refute(@io.string.blank?)
30
- end
31
-
32
- def test_help_prints_help
33
- ActiveRecordDoctor.detectors.each do |name, _|
34
- @io.truncate(0)
35
-
36
- @runner.help(name)
37
-
38
- refute(@io.string.blank?, "expected help for #{name}")
39
- end
40
- end
41
- end
@@ -1,131 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/generators"
4
-
5
- require "generators/active_record_doctor/add_indexes/add_indexes_generator"
6
-
7
- class ActiveRecordDoctor::AddIndexesGeneratorTest < Minitest::Test
8
- TIMESTAMP = Time.new(2021, 2, 1, 13, 15, 30)
9
-
10
- def test_create_migrations
11
- create_table(:users) do |t|
12
- t.integer :organization_id, null: false
13
- t.integer :account_id, null: false
14
- end
15
- create_table(:organizations) do |t|
16
- t.integer :owner_id
17
- end
18
-
19
- Dir.mktmpdir do |dir|
20
- Dir.chdir(dir)
21
-
22
- path = File.join(dir, "indexes.txt")
23
- File.write(path, <<~INDEXES)
24
- add an index on users.organization_id - foreign keys are often used in database lookups and should be indexed for performance reasons
25
- add an index on users.account_id - foreign keys are often used in database lookups and should be indexed for performance reasons
26
- add an index on organizations.owner_id - foreign keys are often used in database lookups and should be indexed for performance reasons
27
- INDEXES
28
-
29
- capture_io do
30
- Time.stub(:now, TIMESTAMP) do
31
- ActiveRecordDoctor::AddIndexesGenerator.start([path])
32
-
33
- load(File.join("db", "migrate", "20210201131530_index_foreign_keys_in_users.rb"))
34
- IndexForeignKeysInUsers.migrate(:up)
35
-
36
- load(File.join("db", "migrate", "20210201131531_index_foreign_keys_in_organizations.rb"))
37
- IndexForeignKeysInOrganizations.migrate(:up)
38
-
39
- ::Object.send(:remove_const, :IndexForeignKeysInUsers)
40
- ::Object.send(:remove_const, :IndexForeignKeysInOrganizations)
41
- end
42
- end
43
-
44
- assert_indexes([
45
- ["users", ["organization_id"]],
46
- ["users", ["account_id"]],
47
- ["organizations", ["owner_id"]]
48
- ])
49
-
50
- assert_equal(4, Dir.entries("./db/migrate").size)
51
- end
52
- end
53
-
54
- def test_create_migrations_raises_when_malformed_inpout
55
- Tempfile.create do |file|
56
- file.write(<<~INDEXES)
57
- add an index on users. - foreign keys are often used in database lookups and should be indexed for performance reasons
58
- INDEXES
59
- file.flush
60
-
61
- assert_raises(RuntimeError) do
62
- capture_io do
63
- ActiveRecordDoctor::AddIndexesGenerator.start([file.path])
64
- end
65
- end
66
- end
67
- end
68
-
69
- def test_create_migrations_skips_blank_lines
70
- Dir.mktmpdir do |dir|
71
- Dir.chdir(dir)
72
-
73
- path = File.join(dir, "indexes.txt")
74
- File.write(path, "\n")
75
-
76
- capture_io do
77
- Time.stub(:now, TIMESTAMP) do
78
- # Not raising an exception is considered success.
79
- ActiveRecordDoctor::AddIndexesGenerator.start([path])
80
- end
81
- end
82
- end
83
- end
84
-
85
- def test_create_migrations_truncates_long_index_names
86
- # Both the table and column names must be quite long. Otherwise, the
87
- # we might reach table or column name length limits and fail to generate an
88
- # index name that's long enough.
89
- create_table(:organizations_migrated_from_legacy_app) do |t|
90
- t.integer :legacy_owner_id_compatible_with_v1_to_v8
91
- end
92
-
93
- Dir.mktmpdir do |dir|
94
- Dir.chdir(dir)
95
-
96
- path = File.join(dir, "indexes.txt")
97
- File.write(path, <<~INDEXES)
98
- add an index on organizations_migrated_from_legacy_app.legacy_owner_id_compatible_with_v1_to_v8 - foreign keys are often used in database lookups and should be indexed for performance reasons
99
- INDEXES
100
-
101
- capture_io do
102
- Time.stub(:now, TIMESTAMP) do
103
- # If no exceptions are raised then we consider this to be a success.
104
- ActiveRecordDoctor::AddIndexesGenerator.start([path])
105
-
106
- load(File.join(
107
- "db",
108
- "migrate",
109
- "20210201131530_index_foreign_keys_in_organizations_migrated_from_legacy_app.rb"
110
- ))
111
- ::IndexForeignKeysInOrganizationsMigratedFromLegacyApp.migrate(:up)
112
-
113
- ::Object.send(:remove_const, :IndexForeignKeysInOrganizationsMigratedFromLegacyApp)
114
- end
115
- end
116
- end
117
- end
118
-
119
- private
120
-
121
- def assert_indexes(expected_indexes)
122
- actual_indexes =
123
- ActiveRecord::Base.connection.tables.map do |table|
124
- ActiveRecord::Base.connection.indexes(table).map do |index|
125
- [index.table, index.columns]
126
- end
127
- end.flatten(1)
128
-
129
- assert_equal(expected_indexes.sort, actual_indexes.sort)
130
- end
131
- end
data/test/setup.rb DELETED
@@ -1,124 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Configure Active Record.
4
-
5
- # We must import "uri" explicitly as otherwsie URI won't be accessible in
6
- # Ruby 2.7.2 / Rails 6.
7
- require "uri"
8
-
9
- require "active_record"
10
- require "pg"
11
- require "mysql2"
12
-
13
- adapter = ENV.fetch("DATABASE_ADAPTER")
14
- ActiveRecord::Base.establish_connection(
15
- adapter: adapter,
16
- host: ENV["DATABASE_HOST"],
17
- port: ENV["DATABASE_PORT"],
18
- username: ENV["DATABASE_USERNAME"],
19
- password: ENV["DATABASE_PASSWORD"],
20
- database: "active_record_doctor_test"
21
- )
22
-
23
- puts "Using #{adapter}"
24
-
25
- # We need to call #connection to enfore Active Record to actually establish
26
- # the connection.
27
- ActiveRecord::Base.connection
28
-
29
- # Load Active Record Doctor.
30
- require "active_record_doctor"
31
-
32
- # Configure the test suite.
33
- require "minitest"
34
- require "minitest/autorun"
35
- require "minitest/fork_executor"
36
- require "transient_record"
37
-
38
- # Filter out Minitest backtrace while allowing backtrace from other libraries
39
- # to be shown.
40
- Minitest.backtrace_filter = Minitest::BacktraceFilter.new
41
-
42
- # Uncomment in case there's test case interference.
43
- Minitest.parallel_executor = Minitest::ForkExecutor.new
44
-
45
- # Prepare the test class.
46
- class Minitest::Test
47
- include TransientRecord
48
-
49
- def setup
50
- # Delete remnants (models and tables) of previous test case runs.
51
- TransientRecord.cleanup
52
- end
53
-
54
- def teardown
55
- @config_path = nil
56
-
57
- if @previous_dir
58
- Dir.chdir(@previous_dir)
59
- @previous_dir = nil
60
- end
61
-
62
- # Ensure all remnants of previous test runs, most likely in form of tables,
63
- # are removed.
64
- TransientRecord.cleanup
65
- end
66
-
67
- private
68
-
69
- attr_reader :config_path
70
-
71
- def config_file(content)
72
- @previous_dir = Dir.pwd
73
-
74
- directory = Dir.mktmpdir("active_record_doctor")
75
- @config_path = File.join(directory, ".active_record_doctor")
76
- File.write(@config_path, content)
77
- Dir.chdir(directory)
78
-
79
- @config_path
80
- end
81
-
82
- def postgresql?
83
- ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
84
- end
85
-
86
- def mysql?
87
- ActiveRecord::Base.connection.adapter_name == "Mysql2"
88
- end
89
-
90
- def detector_name
91
- self.class.name.sub(/Test$/, "").demodulize.underscore.to_sym
92
- end
93
-
94
- def run_detector
95
- io = StringIO.new
96
- runner = ActiveRecordDoctor::Runner.new(
97
- config: load_config,
98
- logger: ActiveRecordDoctor::Logger::Dummy.new,
99
- io: io
100
- )
101
- success = runner.run_one(detector_name)
102
- [success, io.string]
103
- end
104
-
105
- def load_config
106
- ActiveRecordDoctor.load_config_with_defaults(@config_path)
107
- end
108
-
109
- def assert_problems(expected_output)
110
- success, output = run_detector
111
- assert_equal(sort_lines(expected_output), sort_lines(output))
112
- refute(success, "Expected the detector to return failure.")
113
- end
114
-
115
- def refute_problems(expected_output = "")
116
- success, output = run_detector
117
- assert_equal(sort_lines(expected_output), sort_lines(output))
118
- assert(success, "Expected the detector to return success.")
119
- end
120
-
121
- def sort_lines(string)
122
- string.split("\n").sort
123
- end
124
- end