active_record_doctor 1.5.0 → 1.8.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 +5 -5
- data/README.md +152 -12
- data/lib/active_record_doctor.rb +20 -2
- data/lib/active_record_doctor/detectors.rb +13 -0
- data/lib/active_record_doctor/detectors/base.rb +64 -0
- data/lib/active_record_doctor/{tasks → detectors}/extraneous_indexes.rb +12 -29
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +40 -0
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +71 -0
- data/lib/active_record_doctor/{tasks → detectors}/missing_foreign_keys.rb +17 -35
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +60 -0
- data/lib/active_record_doctor/detectors/missing_presence_validation.rb +78 -0
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +61 -0
- data/lib/active_record_doctor/detectors/undefined_table_references.rb +34 -0
- data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +29 -0
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +48 -0
- data/lib/active_record_doctor/printers.rb +3 -1
- data/lib/active_record_doctor/printers/io_printer.rb +101 -26
- data/lib/active_record_doctor/railtie.rb +3 -1
- data/lib/active_record_doctor/task.rb +28 -0
- data/lib/active_record_doctor/version.rb +3 -1
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +15 -11
- data/lib/tasks/active_record_doctor.rake +33 -0
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +67 -0
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +36 -0
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +117 -0
- data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +24 -0
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +102 -0
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +107 -0
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +114 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +44 -0
- data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +67 -0
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +26 -0
- data/test/active_record_doctor/printers/io_printer_test.rb +23 -10
- data/test/model_factory.rb +78 -0
- data/test/setup.rb +126 -0
- metadata +93 -149
- data/Rakefile +0 -28
- data/lib/active_record_doctor/compatibility.rb +0 -11
- data/lib/active_record_doctor/tasks.rb +0 -4
- data/lib/active_record_doctor/tasks/undefined_table_references.rb +0 -34
- data/lib/active_record_doctor/tasks/unindexed_deleted_at.rb +0 -40
- data/lib/active_record_doctor/tasks/unindexed_foreign_keys.rb +0 -66
- data/lib/tasks/active_record_doctor_tasks.rake +0 -27
- data/test/active_record_doctor/tasks/extraneous_indexes_test.rb +0 -27
- data/test/active_record_doctor/tasks/missing_foreign_keys_test.rb +0 -19
- data/test/active_record_doctor/tasks/undefined_table_references_test.rb +0 -19
- data/test/active_record_doctor/tasks/unindexed_deleted_at_test.rb +0 -19
- data/test/active_record_doctor/tasks/unindexed_foreign_keys_test.rb +0 -19
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- 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/models/comment.rb +0 -3
- data/test/dummy/app/models/contract.rb +0 -3
- data/test/dummy/app/models/employer.rb +0 -2
- data/test/dummy/app/models/profile.rb +0 -2
- data/test/dummy/app/models/user.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.ru +0 -4
- 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/db/migrate/20160213101213_create_employers.rb +0 -15
- data/test/dummy/db/migrate/20160213101221_create_users.rb +0 -23
- data/test/dummy/db/migrate/20160213101232_create_profiles.rb +0 -15
- data/test/dummy/db/migrate/20160604081452_create_comments.rb +0 -11
- data/test/dummy/db/migrate/base_migration.rb +0 -5
- data/test/dummy/db/schema.rb +0 -68
- data/test/dummy/log/development.log +0 -532
- data/test/dummy/log/test.log +0 -2699
- 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/spy_printer.rb +0 -52
- data/test/test_helper.rb +0 -20
@@ -1,58 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecordDoctor
|
2
4
|
module Printers
|
5
|
+
# Default printer for displaying messages produced by active_record_doctor.
|
3
6
|
class IOPrinter
|
4
|
-
def initialize(io
|
7
|
+
def initialize(io = $stdout)
|
5
8
|
@io = io
|
6
9
|
end
|
7
10
|
|
8
|
-
def
|
11
|
+
def unindexed_foreign_keys(unindexed_foreign_keys, _options)
|
12
|
+
return if unindexed_foreign_keys.empty?
|
13
|
+
|
14
|
+
@io.puts("The following foreign keys should be indexed for performance reasons:")
|
9
15
|
@io.puts(unindexed_foreign_keys.sort.map do |table, columns|
|
10
|
-
"#{table} #{columns.sort.join(' ')}"
|
16
|
+
" #{table} #{columns.sort.join(' ')}"
|
11
17
|
end.join("\n"))
|
12
18
|
end
|
13
19
|
|
14
|
-
def
|
15
|
-
if extraneous_indexes.empty?
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
fail("unknown reason #{reason.inspect}")
|
28
|
-
end
|
20
|
+
def extraneous_indexes(extraneous_indexes, _options)
|
21
|
+
return if extraneous_indexes.empty?
|
22
|
+
|
23
|
+
@io.puts("The following indexes are extraneous and can be removed:")
|
24
|
+
extraneous_indexes.each do |index, details|
|
25
|
+
reason, *params = details
|
26
|
+
case reason
|
27
|
+
when :multi_column
|
28
|
+
@io.puts(" #{index} (can be handled by #{params.join(', ')})")
|
29
|
+
when :primary_key
|
30
|
+
@io.puts(" #{index} (is a primary key of #{params[0]})")
|
31
|
+
else
|
32
|
+
raise("unknown reason #{reason.inspect}")
|
29
33
|
end
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
|
-
def
|
37
|
+
def missing_foreign_keys(missing_foreign_keys, _options)
|
38
|
+
return if missing_foreign_keys.empty?
|
39
|
+
|
40
|
+
@io.puts("The following columns lack a foreign key constraint:")
|
34
41
|
@io.puts(missing_foreign_keys.sort.map do |table, columns|
|
35
|
-
"#{table} #{columns.sort.join(' ')}"
|
42
|
+
" #{table} #{columns.sort.join(' ')}"
|
36
43
|
end.join("\n"))
|
37
44
|
end
|
38
45
|
|
39
|
-
def
|
46
|
+
def undefined_table_references(models, options)
|
40
47
|
return if models.empty?
|
41
48
|
|
42
|
-
|
43
|
-
|
44
|
-
|
49
|
+
unless options.fetch(:views_checked)
|
50
|
+
@io.puts(<<WARNING)
|
51
|
+
WARNING: Models backed by database views are supported only in Rails 5+ OR
|
52
|
+
Rails 4.2 + PostgreSQL. It seems this is NOT your setup. Therefore, such models
|
53
|
+
will be erroneously reported below as not having their underlying tables/views.
|
54
|
+
Consider upgrading Rails or disabling this task temporarily.
|
55
|
+
WARNING
|
56
|
+
end
|
57
|
+
|
58
|
+
@io.puts("The following models reference undefined tables:")
|
59
|
+
models.each do |model_name, table_name|
|
60
|
+
@io.puts(" #{model_name} (the table #{table_name} is undefined)")
|
45
61
|
end
|
46
62
|
end
|
47
63
|
|
48
|
-
def
|
64
|
+
def unindexed_deleted_at(indexes, _options)
|
49
65
|
return if indexes.empty?
|
50
66
|
|
51
|
-
@io.puts(
|
67
|
+
@io.puts("The following indexes should include `deleted_at IS NULL`:")
|
52
68
|
indexes.each do |index|
|
53
69
|
@io.puts(" #{index}")
|
54
70
|
end
|
55
71
|
end
|
72
|
+
|
73
|
+
def missing_unique_indexes(indexes, _options)
|
74
|
+
return if indexes.empty?
|
75
|
+
|
76
|
+
@io.puts("The following indexes should be created to back model-level uniqueness validations:")
|
77
|
+
indexes.each do |table, arrays_of_columns|
|
78
|
+
arrays_of_columns.each do |columns|
|
79
|
+
@io.puts(" #{table}: #{columns.join(', ')}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def missing_presence_validation(missing_presence_validators, _options)
|
85
|
+
return if missing_presence_validators.empty?
|
86
|
+
|
87
|
+
@io.puts("The following models and columns should have presence validations:")
|
88
|
+
missing_presence_validators.each do |model_name, array_of_columns|
|
89
|
+
@io.puts(" #{model_name}: #{array_of_columns.join(', ')}")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def missing_non_null_constraint(missing_non_null_constraints, _options)
|
94
|
+
return if missing_non_null_constraints.empty?
|
95
|
+
|
96
|
+
@io.puts("The following columns should be marked as `null: false`:")
|
97
|
+
missing_non_null_constraints.each do |table, columns|
|
98
|
+
@io.puts(" #{table}: #{columns.join(', ')}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def incorrect_boolean_presence_validation(incorrect_boolean_presence_validations, _options)
|
103
|
+
return if incorrect_boolean_presence_validations.empty?
|
104
|
+
|
105
|
+
@io.puts("The presence of the following boolean columns is validated incorrectly:")
|
106
|
+
incorrect_boolean_presence_validations.each do |table, columns|
|
107
|
+
@io.puts(" #{table}: #{columns.join(', ')}")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def incorrect_dependent_option(problems, _options)
|
112
|
+
return if problems.empty?
|
113
|
+
|
114
|
+
@io.puts("The following associations might be using invalid dependent settings:")
|
115
|
+
problems.each do |model, associations|
|
116
|
+
associations.each do |(name, problem)|
|
117
|
+
# rubocop:disable Layout/LineLength
|
118
|
+
message =
|
119
|
+
case problem
|
120
|
+
when :suggest_destroy then "skips callbacks that are defined on the associated model - consider changing to `dependent: :destroy` or similar"
|
121
|
+
when :suggest_delete then "loads the associated model before deleting it - consider using `dependent: :delete`"
|
122
|
+
when :suggest_delete_all then "loads models one-by-one to invoke callbacks even though the related model defines none - consider using `dependent: :delete_all`"
|
123
|
+
else next
|
124
|
+
end
|
125
|
+
# rubocop:enable Layout/LineLength
|
126
|
+
|
127
|
+
@io.puts(" #{model}: #{name} #{message}")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
56
131
|
end
|
57
132
|
end
|
58
133
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordDoctor
|
4
|
+
# Rake task for running a detector and reporting its results.
|
5
|
+
class Task
|
6
|
+
DEFAULT_PRINTER = ActiveRecordDoctor::Printers::IOPrinter.new
|
7
|
+
|
8
|
+
def initialize(detector_class, printer = DEFAULT_PRINTER)
|
9
|
+
@detector_class = detector_class
|
10
|
+
@printer = printer
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
@detector_class.name.demodulize.underscore.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def description
|
18
|
+
@detector_class.description
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
problems, options = @detector_class.run
|
23
|
+
@printer.public_send(name, problems, options)
|
24
|
+
|
25
|
+
problems.empty?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,9 +1,12 @@
|
|
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
6
|
MigrationDescription = Struct.new(:table, :columns)
|
4
7
|
|
5
|
-
desc
|
6
|
-
argument :path, type: :string, default: nil, banner:
|
8
|
+
desc "Generate migrations for the specified indexes"
|
9
|
+
argument :path, type: :string, default: nil, banner: "PATH"
|
7
10
|
|
8
11
|
def create_migrations
|
9
12
|
migration_descriptions = read_migration_descriptions(path)
|
@@ -23,10 +26,10 @@ module ActiveRecordDoctor
|
|
23
26
|
table, *columns = line.split(/\s+/)
|
24
27
|
|
25
28
|
if table.empty?
|
26
|
-
|
29
|
+
raise("No table name in #{path} on line #{index + 1}. Ensure the line doesn't start with whitespace.")
|
27
30
|
end
|
28
31
|
if columns.empty?
|
29
|
-
|
32
|
+
raise("No columns for table #{table} in #{path} on line #{index + 1}.")
|
30
33
|
end
|
31
34
|
|
32
35
|
MigrationDescription.new(table, columns)
|
@@ -34,13 +37,16 @@ module ActiveRecordDoctor
|
|
34
37
|
end
|
35
38
|
|
36
39
|
def content(migration_description)
|
37
|
-
|
40
|
+
# In order to properly indent the resulting code, we must disable the
|
41
|
+
# rubocop rule below.
|
42
|
+
|
43
|
+
<<MIGRATION
|
38
44
|
class IndexForeignKeysIn#{migration_description.table.camelize} < ActiveRecord::Migration#{migration_version}
|
39
45
|
def change
|
40
46
|
#{add_indexes(migration_description)}
|
41
47
|
end
|
42
48
|
end
|
43
|
-
|
49
|
+
MIGRATION
|
44
50
|
end
|
45
51
|
|
46
52
|
def add_indexes(migration_description)
|
@@ -54,12 +60,10 @@ EOF
|
|
54
60
|
end
|
55
61
|
|
56
62
|
def migration_version
|
57
|
-
|
58
|
-
|
59
|
-
if major >= 5 && minor >= 1
|
60
|
-
"[#{major}.#{minor}]"
|
63
|
+
if ActiveRecord::VERSION::STRING >= "5.1"
|
64
|
+
"[#{version}]"
|
61
65
|
else
|
62
|
-
|
66
|
+
""
|
63
67
|
end
|
64
68
|
end
|
65
69
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
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/task"
|
15
|
+
|
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
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
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
|
+
The following indexes are extraneous and can be removed:
|
11
|
+
index_users_on_id (is a primary key of users)
|
12
|
+
OUTPUT
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_non_unique_version_of_index_is_duplicate
|
16
|
+
create_table(:users) do |t|
|
17
|
+
t.string :email
|
18
|
+
t.index :email, unique: true, name: "unique_index_on_users_email"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Rails 4.2 compatibility - can't be pulled into the block above.
|
22
|
+
ActiveRecord::Base.connection.add_index :users, :email, name: "index_users_on_email"
|
23
|
+
|
24
|
+
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)
|
27
|
+
OUTPUT
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_single_column_covered_by_unique_and_non_unique_multi_column_is_duplicate
|
31
|
+
create_table(:users) do |t|
|
32
|
+
t.string :first_name
|
33
|
+
t.string :last_name
|
34
|
+
t.string :email
|
35
|
+
t.index [:last_name, :first_name, :email]
|
36
|
+
t.index [:last_name, :first_name],
|
37
|
+
unique: true,
|
38
|
+
name: "unique_index_on_users_last_name_and_first_name"
|
39
|
+
t.index :last_name
|
40
|
+
end
|
41
|
+
|
42
|
+
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)
|
45
|
+
OUTPUT
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_multi_column_covered_by_unique_and_non_unique_multi_column_is_duplicate
|
49
|
+
create_table(:users) do |t|
|
50
|
+
t.string :first_name
|
51
|
+
t.string :last_name
|
52
|
+
t.string :email
|
53
|
+
t.index [:last_name, :first_name, :email]
|
54
|
+
t.index [:last_name, :first_name],
|
55
|
+
unique: true,
|
56
|
+
name: "unique_index_on_users_last_name_and_first_name"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Rails 4.2 compatibility - can't be pulled into the block above.
|
60
|
+
ActiveRecord::Base.connection.add_index :users, [:last_name, :first_name]
|
61
|
+
|
62
|
+
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)
|
65
|
+
OUTPUT
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveRecordDoctor::Detectors::IncorrectBooleanPresenceValidationTest < Minitest::Test
|
4
|
+
def test_presence_true_is_reported_on_boolean_only
|
5
|
+
create_table(:users) do |t|
|
6
|
+
t.string :email, null: false
|
7
|
+
t.boolean :active, null: false
|
8
|
+
end.create_model do
|
9
|
+
# email is a non-boolean column whose presence CAN be validated in the
|
10
|
+
# usual way. We include it in the test model to ensure the detector reports
|
11
|
+
# only boolean columns.
|
12
|
+
validates :email, :active, presence: true
|
13
|
+
end
|
14
|
+
|
15
|
+
assert_problems(<<OUTPUT)
|
16
|
+
The presence of the following boolean columns is validated incorrectly:
|
17
|
+
ModelFactory::Models::User: active
|
18
|
+
OUTPUT
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_inclusion_is_not_reported
|
22
|
+
create_table(:users) do |t|
|
23
|
+
t.boolean :active, null: false
|
24
|
+
end.create_model do
|
25
|
+
validates :active, inclusion: { in: [true, false] }
|
26
|
+
end
|
27
|
+
|
28
|
+
refute_problems
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_models_with_non_existent_tables_are_skipped
|
32
|
+
create_model(:users)
|
33
|
+
|
34
|
+
refute_problems
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveRecordDoctor::Detectors::IncorrectDependentOptionTest < Minitest::Test
|
4
|
+
def test_invoking_no_callbacks_suggests_delete_all
|
5
|
+
create_table(:companies) do
|
6
|
+
end.create_model do
|
7
|
+
has_many :users, dependent: :destroy
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table(:users) do |t|
|
11
|
+
t.references :companies
|
12
|
+
end.create_model do
|
13
|
+
belongs_to :company
|
14
|
+
end
|
15
|
+
|
16
|
+
assert_problems(<<OUTPUT)
|
17
|
+
The following associations might be using invalid dependent settings:
|
18
|
+
ModelFactory::Models::Company: users loads models one-by-one to invoke callbacks even though the related model defines none - consider using `dependent: :delete_all`
|
19
|
+
OUTPUT
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_invoking_callbacks_does_not_suggest_delete_all
|
23
|
+
create_table(:companies) do
|
24
|
+
end.create_model do
|
25
|
+
has_many :users, dependent: :destroy
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table(:users) do |t|
|
29
|
+
t.references :companies
|
30
|
+
end.create_model do
|
31
|
+
belongs_to :company
|
32
|
+
|
33
|
+
before_destroy :log
|
34
|
+
|
35
|
+
def log
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
refute_problems
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_skipping_callbacks_suggests_destroy
|
43
|
+
create_table(:companies) do
|
44
|
+
end.create_model do
|
45
|
+
has_many :users, dependent: :delete_all
|
46
|
+
end
|
47
|
+
|
48
|
+
create_table(:users) do |t|
|
49
|
+
t.references :companies
|
50
|
+
end.create_model do
|
51
|
+
belongs_to :company
|
52
|
+
|
53
|
+
before_destroy :log
|
54
|
+
|
55
|
+
def log
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_problems(<<OUTPUT)
|
60
|
+
The following associations might be using invalid dependent settings:
|
61
|
+
ModelFactory::Models::Company: users skips callbacks that are defined on the associated model - consider changing to `dependent: :destroy` or similar
|
62
|
+
OUTPUT
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_invoking_callbacks_does_not_suggest_destroy
|
66
|
+
create_table(:companies) do
|
67
|
+
end.create_model do
|
68
|
+
has_many :users, dependent: :destroy
|
69
|
+
end
|
70
|
+
|
71
|
+
create_table(:users) do |t|
|
72
|
+
t.references :companies
|
73
|
+
end.create_model do
|
74
|
+
belongs_to :company
|
75
|
+
|
76
|
+
before_destroy :log
|
77
|
+
|
78
|
+
def log
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
refute_problems
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_works_on_has_one
|
86
|
+
create_table(:companies) do
|
87
|
+
end.create_model do
|
88
|
+
has_one :owner, class_name: "ModelFactory::Models::User", dependent: :destroy
|
89
|
+
end
|
90
|
+
|
91
|
+
create_table(:users) do |t|
|
92
|
+
t.references :companies
|
93
|
+
end.create_model do
|
94
|
+
belongs_to :company
|
95
|
+
end
|
96
|
+
|
97
|
+
assert_problems(<<OUTPUT)
|
98
|
+
The following associations might be using invalid dependent settings:
|
99
|
+
ModelFactory::Models::Company: owner loads the associated model before deleting it - consider using `dependent: :delete`
|
100
|
+
OUTPUT
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_no_dependent_suggests_nothing
|
104
|
+
create_table(:companies) do
|
105
|
+
end.create_model do
|
106
|
+
has_many :users
|
107
|
+
end
|
108
|
+
|
109
|
+
create_table(:users) do |t|
|
110
|
+
t.references :companies
|
111
|
+
end.create_model do
|
112
|
+
belongs_to :company
|
113
|
+
end
|
114
|
+
|
115
|
+
refute_problems
|
116
|
+
end
|
117
|
+
end
|