active_record_doctor 1.10.0 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/active_record_doctor/detectors/base.rb +180 -50
  4. data/lib/active_record_doctor/detectors/extraneous_indexes.rb +24 -27
  5. data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +2 -5
  6. data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +63 -21
  7. data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +7 -10
  8. data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +16 -9
  9. data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +2 -4
  10. data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +13 -11
  11. data/lib/active_record_doctor/detectors/missing_presence_validation.rb +14 -7
  12. data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +5 -11
  13. data/lib/active_record_doctor/detectors/short_primary_key_type.rb +1 -1
  14. data/lib/active_record_doctor/detectors/undefined_table_references.rb +2 -2
  15. data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +5 -13
  16. data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +2 -4
  17. data/lib/active_record_doctor/logger/dummy.rb +11 -0
  18. data/lib/active_record_doctor/logger/hierarchical.rb +22 -0
  19. data/lib/active_record_doctor/logger.rb +6 -0
  20. data/lib/active_record_doctor/rake/task.rb +10 -1
  21. data/lib/active_record_doctor/runner.rb +8 -3
  22. data/lib/active_record_doctor/version.rb +1 -1
  23. data/lib/active_record_doctor.rb +3 -0
  24. data/test/active_record_doctor/detectors/disable_test.rb +1 -1
  25. data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +7 -7
  26. data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +136 -57
  27. data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +16 -14
  28. data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +35 -1
  29. data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +46 -23
  30. data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +55 -27
  31. data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +36 -36
  32. data/test/active_record_doctor/detectors/undefined_table_references_test.rb +11 -13
  33. data/test/active_record_doctor/runner_test.rb +18 -19
  34. data/test/setup.rb +10 -6
  35. metadata +19 -4
  36. data/test/model_factory.rb +0 -128
@@ -3,17 +3,17 @@
3
3
  class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Test
4
4
  def test_model_backed_by_table
5
5
  create_table(:users) do
6
- end.create_model do
6
+ end.define_model do
7
7
  end
8
8
 
9
9
  refute_problems
10
10
  end
11
11
 
12
12
  def test_model_backed_by_non_existent_table
13
- create_model(:User)
13
+ define_model(:User)
14
14
 
15
15
  assert_problems(<<~OUTPUT)
16
- ModelFactory::Models::User references a non-existent table or view named users
16
+ TransientRecord::Models::User references a non-existent table or view named users
17
17
  OUTPUT
18
18
  end
19
19
 
@@ -21,22 +21,20 @@ class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Te
21
21
  # We replace the underlying table with a view. The view doesn't have to be
22
22
  # backed by an actual table - it can simply return a predefined tuple.
23
23
  ActiveRecord::Base.connection.execute("CREATE VIEW users AS SELECT 1")
24
- create_model(:User)
24
+ define_model(:User)
25
25
 
26
- begin
27
- refute_problems
28
- ensure
29
- ActiveRecord::Base.connection.execute("DROP VIEW users")
30
- end
26
+ refute_problems
27
+ ensure
28
+ ActiveRecord::Base.connection.execute("DROP VIEW users")
31
29
  end
32
30
 
33
31
  def test_config_ignore_tables
34
- create_model(:User)
32
+ define_model(:User)
35
33
 
36
34
  config_file(<<-CONFIG)
37
35
  ActiveRecordDoctor.configure do |config|
38
36
  config.detector :undefined_table_references,
39
- ignore_models: ["ModelFactory::Models::User"]
37
+ ignore_models: ["TransientRecord::Models::User"]
40
38
  end
41
39
  CONFIG
42
40
 
@@ -44,11 +42,11 @@ class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Te
44
42
  end
45
43
 
46
44
  def test_global_ignore_tables
47
- create_model(:User)
45
+ define_model(:User)
48
46
 
49
47
  config_file(<<-CONFIG)
50
48
  ActiveRecordDoctor.configure do |config|
51
- config.global :ignore_models, ["ModelFactory::Models::User"]
49
+ config.global :ignore_models, ["TransientRecord::Models::User"]
52
50
  end
53
51
  CONFIG
54
52
 
@@ -1,42 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ActiveRecordDoctor::RunnerTest < Minitest::Test
4
- def test_run_one_raises_on_unknown_detectors
5
- io = StringIO.new
6
- runner = ActiveRecordDoctor::Runner.new(load_config, io)
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
7
12
 
13
+ def test_run_one_raises_on_unknown_detectors
8
14
  assert_raises(KeyError) do
9
- runner.run_one(:performance_issues)
15
+ @runner.run_one(:performance_issues)
10
16
  end
11
17
  end
12
18
 
13
19
  def test_run_all_returns_true_when_no_errors
14
- io = StringIO.new
15
- runner = ActiveRecordDoctor::Runner.new(load_config, io)
16
-
17
- assert(runner.run_all)
18
- assert(io.string.blank?)
20
+ assert(@runner.run_all)
21
+ assert(@io.string.blank?)
19
22
  end
20
23
 
21
24
  def test_run_all_returns_false_when_errors
22
25
  # Create a model without its underlying table to trigger an error.
23
- create_model(:User)
24
-
25
- io = StringIO.new
26
- runner = ActiveRecordDoctor::Runner.new(load_config, io)
26
+ define_model(:User)
27
27
 
28
- refute(runner.run_all)
29
- refute(io.string.blank?)
28
+ refute(@runner.run_all)
29
+ refute(@io.string.blank?)
30
30
  end
31
31
 
32
32
  def test_help_prints_help
33
33
  ActiveRecordDoctor.detectors.each do |name, _|
34
- io = StringIO.new
35
- runner = ActiveRecordDoctor::Runner.new(load_config, io)
34
+ @io.truncate(0)
36
35
 
37
- runner.help(name)
36
+ @runner.help(name)
38
37
 
39
- refute(io.string.blank?, "expected help for #{name}")
38
+ refute(@io.string.blank?, "expected help for #{name}")
40
39
  end
41
40
  end
42
41
  end
data/test/setup.rb CHANGED
@@ -33,7 +33,7 @@ require "active_record_doctor"
33
33
  require "minitest"
34
34
  require "minitest/autorun"
35
35
  require "minitest/fork_executor"
36
- require_relative "model_factory"
36
+ require "transient_record"
37
37
 
38
38
  # Filter out Minitest backtrace while allowing backtrace from other libraries
39
39
  # to be shown.
@@ -44,11 +44,11 @@ Minitest.parallel_executor = Minitest::ForkExecutor.new
44
44
 
45
45
  # Prepare the test class.
46
46
  class Minitest::Test
47
- include ModelFactory
47
+ include TransientRecord
48
48
 
49
49
  def setup
50
50
  # Delete remnants (models and tables) of previous test case runs.
51
- cleanup_models
51
+ TransientRecord.cleanup
52
52
  end
53
53
 
54
54
  def teardown
@@ -61,7 +61,7 @@ class Minitest::Test
61
61
 
62
62
  # Ensure all remnants of previous test runs, most likely in form of tables,
63
63
  # are removed.
64
- cleanup_models
64
+ TransientRecord.cleanup
65
65
  end
66
66
 
67
67
  private
@@ -93,7 +93,11 @@ class Minitest::Test
93
93
 
94
94
  def run_detector
95
95
  io = StringIO.new
96
- runner = ActiveRecordDoctor::Runner.new(load_config, io)
96
+ runner = ActiveRecordDoctor::Runner.new(
97
+ config: load_config,
98
+ logger: ActiveRecordDoctor::Logger::Dummy.new,
99
+ io: io
100
+ )
97
101
  success = runner.run_one(detector_name)
98
102
  [success, io.string]
99
103
  end
@@ -115,6 +119,6 @@ class Minitest::Test
115
119
  end
116
120
 
117
121
  def sort_lines(string)
118
- string.split("\n").sort.join("\n")
122
+ string.split("\n").sort
119
123
  end
120
124
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_doctor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Navis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-07 00:00:00.000000000 Z
11
+ date: 2023-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 12.3.3
97
+ - !ruby/object:Gem::Dependency
98
+ name: transient_record
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.0.0.rc1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.0.0.rc1
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rubocop
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -138,6 +152,9 @@ files:
138
152
  - lib/active_record_doctor/detectors/unindexed_foreign_keys.rb
139
153
  - lib/active_record_doctor/errors.rb
140
154
  - lib/active_record_doctor/help.rb
155
+ - lib/active_record_doctor/logger.rb
156
+ - lib/active_record_doctor/logger/dummy.rb
157
+ - lib/active_record_doctor/logger/hierarchical.rb
141
158
  - lib/active_record_doctor/printers.rb
142
159
  - lib/active_record_doctor/railtie.rb
143
160
  - lib/active_record_doctor/rake/task.rb
@@ -164,7 +181,6 @@ files:
164
181
  - test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb
165
182
  - test/active_record_doctor/runner_test.rb
166
183
  - test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb
167
- - test/model_factory.rb
168
184
  - test/setup.rb
169
185
  homepage: https://github.com/gregnavis/active_record_doctor
170
186
  licenses:
@@ -208,5 +224,4 @@ test_files:
208
224
  - test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb
209
225
  - test/active_record_doctor/runner_test.rb
210
226
  - test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb
211
- - test/model_factory.rb
212
227
  - test/setup.rb
@@ -1,128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ModelFactory
4
- def create_table(*args, &block)
5
- ModelFactory.create_table(*args, &block)
6
- end
7
-
8
- def create_model(*args, &block)
9
- ModelFactory.create_model(*args, &block)
10
- end
11
-
12
- def cleanup_models
13
- ModelFactory.cleanup_models
14
- end
15
-
16
- def self.cleanup_models
17
- delete_models
18
- drop_all_tables
19
- GC.start
20
- end
21
-
22
- def self.drop_all_tables
23
- connection = ActiveRecord::Base.connection
24
- loop do
25
- before = connection.data_sources.size
26
- break if before.zero?
27
-
28
- try_drop_all_tables_and_views(connection)
29
- remaining_data_sources = connection.data_sources
30
- after = remaining_data_sources.size
31
-
32
- # rubocop:disable Style/Next
33
- if before == after
34
- raise(<<~ERROR)
35
- Cannot delete temporary tables - most likely due to failing constraints. Remaining tables and views:
36
-
37
- #{remaining_data_sources.join("\n")}
38
- ERROR
39
- end
40
- # rubocop:enable Style/Next
41
- end
42
- end
43
-
44
- def self.try_drop_all_tables_and_views(connection)
45
- connection.data_sources.each do |table_name|
46
- try_drop_table(connection, table_name) || try_drop_view(connection, table_name)
47
- end
48
- end
49
-
50
- def self.try_drop_table(connection, table_name)
51
- ActiveRecord::Migration.suppress_messages do
52
- begin
53
- connection.drop_table(table_name, force: :cascade)
54
- true
55
- rescue ActiveRecord::InvalidForeignKey, ActiveRecord::StatementInvalid
56
- # The table cannot be dropped due to foreign key constraints so
57
- # we'll try to drop it on another attempt.
58
- false
59
- end
60
- end
61
- end
62
-
63
- def self.try_drop_view(connection, view_name)
64
- ActiveRecord::Migration.suppress_messages do
65
- begin
66
- connection.execute("DROP VIEW #{view_name}")
67
- true
68
- rescue ActiveRecord::InvalidForeignKey, ActiveRecord::StatementInvalid
69
- # The table cannot be dropped due to foreign key constraints so
70
- # we'll try to drop it on another attempt.
71
- false
72
- end
73
- end
74
- end
75
-
76
- def self.delete_models
77
- Models.empty
78
- end
79
-
80
- def self.create_table(table_name, options = {}, &block)
81
- table_name = table_name.to_sym
82
- ActiveRecord::Migration.suppress_messages do
83
- ActiveRecord::Migration.create_table(table_name, **options, &block)
84
- end
85
- # Return a proxy object allowing the caller to chain #create_model
86
- # right after creating a table so that it can be followed by the model
87
- # definition.
88
- ModelDefinitionProxy.new(table_name)
89
- end
90
-
91
- def self.create_model(model_name, base_class = ActiveRecord::Base, &block)
92
- model_name = model_name.to_sym
93
-
94
- # Normally, when a class is defined via `class MyClass < MySuperclass` the
95
- # .name class method returns the name of the class when called from within
96
- # the class body. However, anonymous classes defined via Class.new DO NOT
97
- # HAVE NAMES. They're assigned names when they're assigned to a constant.
98
- # If we evaluated the class body, passed via block here, in the class
99
- # definition below then some macros would break
100
- # (e.g. has_and_belongs_to_many) due to nil name.
101
- #
102
- # We solve the problem by defining an empty model class first, assigning to
103
- # a constant to ensure a name is assigned, and then reopening the class to
104
- # give it a non-trivial body.
105
- klass = Class.new(base_class)
106
- Models.const_set(model_name, klass)
107
-
108
- klass.class_eval(&block) if block_given?
109
- end
110
-
111
- class ModelDefinitionProxy
112
- def initialize(table_name)
113
- @table_name = table_name
114
- end
115
-
116
- def create_model(&block)
117
- ModelFactory.create_model(@table_name.to_s.classify, &block)
118
- end
119
- end
120
-
121
- module Models
122
- def self.empty
123
- constants.each do |name|
124
- remove_const(name)
125
- end
126
- end
127
- end
128
- end