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.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +152 -12
  3. data/lib/active_record_doctor.rb +20 -2
  4. data/lib/active_record_doctor/detectors.rb +13 -0
  5. data/lib/active_record_doctor/detectors/base.rb +64 -0
  6. data/lib/active_record_doctor/{tasks → detectors}/extraneous_indexes.rb +12 -29
  7. data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +40 -0
  8. data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +71 -0
  9. data/lib/active_record_doctor/{tasks → detectors}/missing_foreign_keys.rb +17 -35
  10. data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +60 -0
  11. data/lib/active_record_doctor/detectors/missing_presence_validation.rb +78 -0
  12. data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +61 -0
  13. data/lib/active_record_doctor/detectors/undefined_table_references.rb +34 -0
  14. data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +29 -0
  15. data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +48 -0
  16. data/lib/active_record_doctor/printers.rb +3 -1
  17. data/lib/active_record_doctor/printers/io_printer.rb +101 -26
  18. data/lib/active_record_doctor/railtie.rb +3 -1
  19. data/lib/active_record_doctor/task.rb +28 -0
  20. data/lib/active_record_doctor/version.rb +3 -1
  21. data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +15 -11
  22. data/lib/tasks/active_record_doctor.rake +33 -0
  23. data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +67 -0
  24. data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +36 -0
  25. data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +117 -0
  26. data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +24 -0
  27. data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +102 -0
  28. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +107 -0
  29. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +114 -0
  30. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +44 -0
  31. data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +67 -0
  32. data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +26 -0
  33. data/test/active_record_doctor/printers/io_printer_test.rb +23 -10
  34. data/test/model_factory.rb +78 -0
  35. data/test/setup.rb +126 -0
  36. metadata +93 -149
  37. data/Rakefile +0 -28
  38. data/lib/active_record_doctor/compatibility.rb +0 -11
  39. data/lib/active_record_doctor/tasks.rb +0 -4
  40. data/lib/active_record_doctor/tasks/undefined_table_references.rb +0 -34
  41. data/lib/active_record_doctor/tasks/unindexed_deleted_at.rb +0 -40
  42. data/lib/active_record_doctor/tasks/unindexed_foreign_keys.rb +0 -66
  43. data/lib/tasks/active_record_doctor_tasks.rake +0 -27
  44. data/test/active_record_doctor/tasks/extraneous_indexes_test.rb +0 -27
  45. data/test/active_record_doctor/tasks/missing_foreign_keys_test.rb +0 -19
  46. data/test/active_record_doctor/tasks/undefined_table_references_test.rb +0 -19
  47. data/test/active_record_doctor/tasks/unindexed_deleted_at_test.rb +0 -19
  48. data/test/active_record_doctor/tasks/unindexed_foreign_keys_test.rb +0 -19
  49. data/test/dummy/README.rdoc +0 -28
  50. data/test/dummy/Rakefile +0 -6
  51. data/test/dummy/app/assets/javascripts/application.js +0 -13
  52. data/test/dummy/app/assets/stylesheets/application.css +0 -15
  53. data/test/dummy/app/controllers/application_controller.rb +0 -5
  54. data/test/dummy/app/helpers/application_helper.rb +0 -2
  55. data/test/dummy/app/models/application_record.rb +0 -3
  56. data/test/dummy/app/models/comment.rb +0 -3
  57. data/test/dummy/app/models/contract.rb +0 -3
  58. data/test/dummy/app/models/employer.rb +0 -2
  59. data/test/dummy/app/models/profile.rb +0 -2
  60. data/test/dummy/app/models/user.rb +0 -3
  61. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  62. data/test/dummy/bin/bundle +0 -3
  63. data/test/dummy/bin/rails +0 -4
  64. data/test/dummy/bin/rake +0 -4
  65. data/test/dummy/bin/setup +0 -29
  66. data/test/dummy/config.ru +0 -4
  67. data/test/dummy/config/application.rb +0 -23
  68. data/test/dummy/config/boot.rb +0 -5
  69. data/test/dummy/config/database.yml +0 -19
  70. data/test/dummy/config/database.yml.travis +0 -5
  71. data/test/dummy/config/environment.rb +0 -5
  72. data/test/dummy/config/environments/development.rb +0 -41
  73. data/test/dummy/config/environments/production.rb +0 -79
  74. data/test/dummy/config/environments/test.rb +0 -47
  75. data/test/dummy/config/initializers/assets.rb +0 -11
  76. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  77. data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
  78. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  79. data/test/dummy/config/initializers/inflections.rb +0 -16
  80. data/test/dummy/config/initializers/mime_types.rb +0 -4
  81. data/test/dummy/config/initializers/session_store.rb +0 -3
  82. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  83. data/test/dummy/config/locales/en.yml +0 -23
  84. data/test/dummy/config/routes.rb +0 -56
  85. data/test/dummy/config/secrets.yml +0 -22
  86. data/test/dummy/db/migrate/20160213101213_create_employers.rb +0 -15
  87. data/test/dummy/db/migrate/20160213101221_create_users.rb +0 -23
  88. data/test/dummy/db/migrate/20160213101232_create_profiles.rb +0 -15
  89. data/test/dummy/db/migrate/20160604081452_create_comments.rb +0 -11
  90. data/test/dummy/db/migrate/base_migration.rb +0 -5
  91. data/test/dummy/db/schema.rb +0 -68
  92. data/test/dummy/log/development.log +0 -532
  93. data/test/dummy/log/test.log +0 -2699
  94. data/test/dummy/public/404.html +0 -67
  95. data/test/dummy/public/422.html +0 -67
  96. data/test/dummy/public/500.html +0 -66
  97. data/test/dummy/public/favicon.ico +0 -0
  98. data/test/support/spy_printer.rb +0 -52
  99. data/test/test_helper.rb +0 -20
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecordDoctor
2
- module Printers
4
+ module Printers # :nodoc:
3
5
  end
4
6
  end
@@ -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: STDOUT)
7
+ def initialize(io = $stdout)
5
8
  @io = io
6
9
  end
7
10
 
8
- def print_unindexed_foreign_keys(unindexed_foreign_keys)
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 print_extraneous_indexes(extraneous_indexes)
15
- if extraneous_indexes.empty?
16
- @io.puts("No indexes are extraneous.")
17
- else
18
- @io.puts("The following indexes are extraneous and can be removed:")
19
- extraneous_indexes.each do |index, details|
20
- reason, *params = details
21
- case reason
22
- when :multi_column
23
- @io.puts(" #{index} (can be handled by #{params.join(', ')})")
24
- when :primary_key
25
- @io.puts(" #{index} (is a primary key of #{params[0]})")
26
- else
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 print_missing_foreign_keys(missing_foreign_keys)
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 print_undefined_table_references(models)
46
+ def undefined_table_references(models, options)
40
47
  return if models.empty?
41
48
 
42
- @io.puts('The following models reference undefined tables:')
43
- models.each do |model|
44
- @io.puts(" #{model.name} (the table #{model.table_name} is undefined)")
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 print_unindexed_deleted_at(indexes)
64
+ def unindexed_deleted_at(indexes, _options)
49
65
  return if indexes.empty?
50
66
 
51
- @io.puts('The following indexes should include `deleted_at IS NULL`:')
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
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecordDoctor
2
4
  class Railtie < Rails::Railtie
3
5
  rake_tasks do
4
- load "tasks/active_record_doctor_tasks.rake"
6
+ load "tasks/active_record_doctor.rake"
5
7
  end
6
8
  end
7
9
  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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecordDoctor
2
- VERSION = "1.5.0"
4
+ VERSION = "1.8.0"
3
5
  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 'Generate migrations for the specified indexes'
6
- argument :path, type: :string, default: nil, banner: 'PATH'
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
- fail("No table name in #{path} on line #{index + 1}. Ensure the line doesn't start with whitespace.")
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
- fail("No columns for table #{table} in #{path} on line #{index + 1}.")
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
- <<EOF
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
- EOF
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
- major = ActiveRecord::VERSION::MAJOR
58
- minor = ActiveRecord::VERSION::MINOR
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