active_record_doctor 1.8.0 → 1.10.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 +4 -4
- data/README.md +316 -54
- data/lib/active_record_doctor/config/default.rb +76 -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 +142 -21
- data/lib/active_record_doctor/detectors/extraneous_indexes.rb +59 -48
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +31 -23
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +102 -35
- data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +63 -0
- data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +45 -0
- data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +32 -23
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +41 -28
- data/lib/active_record_doctor/detectors/missing_presence_validation.rb +29 -23
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +92 -32
- data/lib/active_record_doctor/detectors/short_primary_key_type.rb +45 -0
- data/lib/active_record_doctor/detectors/undefined_table_references.rb +17 -20
- data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +43 -18
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +31 -20
- data/lib/active_record_doctor/detectors.rb +12 -4
- data/lib/active_record_doctor/errors.rb +226 -0
- data/lib/active_record_doctor/help.rb +39 -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 +1 -1
- data/lib/active_record_doctor.rb +8 -3
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +34 -21
- data/lib/tasks/active_record_doctor.rake +9 -18
- 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/disable_test.rb +30 -0
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +165 -8
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +48 -5
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +288 -12
- data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +105 -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 +50 -4
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +172 -24
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +111 -14
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +223 -10
- data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +72 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +34 -21
- data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +118 -8
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +56 -4
- 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 +73 -23
- data/test/setup.rb +65 -71
- metadata +43 -7
- data/lib/active_record_doctor/printers/io_printer.rb +0 -133
- data/lib/active_record_doctor/task.rb +0 -28
- data/test/active_record_doctor/printers/io_printer_test.rb +0 -33
@@ -4,30 +4,27 @@ require "active_record_doctor/detectors/base"
|
|
4
4
|
|
5
5
|
module ActiveRecordDoctor
|
6
6
|
module Detectors
|
7
|
-
|
8
|
-
|
9
|
-
@
|
7
|
+
class UndefinedTableReferences < Base # :nodoc:
|
8
|
+
@description = "detect models referencing undefined tables or views"
|
9
|
+
@config = {
|
10
|
+
ignore_models: {
|
11
|
+
description: "models whose underlying tables should not be checked for existence",
|
12
|
+
global: true
|
13
|
+
}
|
14
|
+
}
|
10
15
|
|
11
|
-
|
12
|
-
eager_load!
|
16
|
+
private
|
13
17
|
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
existing_views = views
|
18
|
+
def message(model:, table:)
|
19
|
+
"#{model} references a non-existent table or view named #{table}"
|
20
|
+
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
(
|
23
|
-
existing_views.nil? ||
|
24
|
-
!existing_views.include?(model.table_name)
|
25
|
-
)
|
26
|
-
end.map do |model|
|
27
|
-
[model.name, model.table_name]
|
28
|
-
end
|
22
|
+
def detect
|
23
|
+
models(except: config(:ignore_models)).each do |model|
|
24
|
+
next if model.table_exists? || views.include?(model.table_name)
|
29
25
|
|
30
|
-
|
26
|
+
problem!(model: model.name, table: model.table_name)
|
27
|
+
end
|
31
28
|
end
|
32
29
|
end
|
33
30
|
end
|
@@ -4,25 +4,50 @@ require "active_record_doctor/detectors/base"
|
|
4
4
|
|
5
5
|
module ActiveRecordDoctor
|
6
6
|
module Detectors
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
7
|
+
class UnindexedDeletedAt < Base # :nodoc:
|
8
|
+
@description = "detect indexes that exclude deletion timestamp columns"
|
9
|
+
@config = {
|
10
|
+
ignore_tables: {
|
11
|
+
description: "tables whose indexes should not be checked",
|
12
|
+
global: true
|
13
|
+
},
|
14
|
+
ignore_columns: {
|
15
|
+
description: "specific columns, written as table.column, that should not be reported as unindexed"
|
16
|
+
},
|
17
|
+
ignore_indexes: {
|
18
|
+
description: "specific indexes that should not be reported as excluding a timestamp column"
|
19
|
+
},
|
20
|
+
column_names: {
|
21
|
+
description: "deletion timestamp column names"
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def message(index:, column_name:)
|
28
|
+
# rubocop:disable Layout/LineLength
|
29
|
+
"consider adding `WHERE #{column_name} IS NULL` or `WHERE #{column_name} IS NOT NULL` to #{index} - a partial index can speed lookups of soft-deletable models"
|
30
|
+
# rubocop:enable Layout/LineLength
|
31
|
+
end
|
32
|
+
|
33
|
+
def detect
|
34
|
+
tables(except: config(:ignore_tables)).each do |table|
|
35
|
+
timestamp_columns = connection.columns(table).reject do |column|
|
36
|
+
config(:ignore_columns).include?("#{table}.#{column.name}")
|
37
|
+
end.select do |column|
|
38
|
+
config(:column_names).include?(column.name)
|
39
|
+
end
|
40
|
+
|
41
|
+
next if timestamp_columns.empty?
|
42
|
+
|
43
|
+
timestamp_columns.each do |timestamp_column|
|
44
|
+
indexes(table, except: config(:ignore_indexes)).each do |index|
|
45
|
+
next if index.where =~ /\b#{timestamp_column.name}\s+IS\s+(NOT\s+)?NULL\b/i
|
46
|
+
|
47
|
+
problem!(index: index.name, column_name: timestamp_column.name)
|
48
|
+
end
|
24
49
|
end
|
25
|
-
end
|
50
|
+
end
|
26
51
|
end
|
27
52
|
end
|
28
53
|
end
|
@@ -4,29 +4,40 @@ require "active_record_doctor/detectors/base"
|
|
4
4
|
|
5
5
|
module ActiveRecordDoctor
|
6
6
|
module Detectors
|
7
|
-
|
8
|
-
|
9
|
-
@
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
foreign_key?(column) &&
|
19
|
-
!indexed?(table, column) &&
|
20
|
-
!indexed_as_polymorphic?(table, column)
|
21
|
-
end.map(&:name)
|
22
|
-
]
|
23
|
-
end.reject do |_table, columns|
|
24
|
-
columns.empty?
|
25
|
-
end))
|
26
|
-
end
|
7
|
+
class UnindexedForeignKeys < Base # :nodoc:
|
8
|
+
@description = "detect unindexed foreign keys"
|
9
|
+
@config = {
|
10
|
+
ignore_tables: {
|
11
|
+
description: "tables whose foreign keys should not be checked",
|
12
|
+
global: true
|
13
|
+
},
|
14
|
+
ignore_columns: {
|
15
|
+
description: "columns, written as table.column, that should not be checked"
|
16
|
+
}
|
17
|
+
}
|
27
18
|
|
28
19
|
private
|
29
20
|
|
21
|
+
def message(table:, column:)
|
22
|
+
# rubocop:disable Layout/LineLength
|
23
|
+
"add an index on #{table}.#{column} - foreign keys are often used in database lookups and should be indexed for performance reasons"
|
24
|
+
# rubocop:enable Layout/LineLength
|
25
|
+
end
|
26
|
+
|
27
|
+
def detect
|
28
|
+
tables(except: config(:ignore_tables)).each do |table|
|
29
|
+
connection.columns(table).each do |column|
|
30
|
+
next if config(:ignore_columns).include?("#{table}.#{column.name}")
|
31
|
+
|
32
|
+
next unless foreign_key?(column)
|
33
|
+
next if indexed?(table, column)
|
34
|
+
next if indexed_as_polymorphic?(table, column)
|
35
|
+
|
36
|
+
problem!(table: table, column: column.name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
30
41
|
def foreign_key?(column)
|
31
42
|
column.name.end_with?("_id")
|
32
43
|
end
|
@@ -3,11 +3,19 @@
|
|
3
3
|
require "active_support"
|
4
4
|
require "active_support/core_ext/class/subclasses"
|
5
5
|
|
6
|
-
module ActiveRecordDoctor
|
6
|
+
module ActiveRecordDoctor # :nodoc:
|
7
|
+
def self.detectors
|
8
|
+
@detectors ||=
|
9
|
+
begin
|
10
|
+
detectors = {}
|
11
|
+
ActiveRecordDoctor::Detectors::Base.subclasses.each do |detector|
|
12
|
+
detectors[detector.underscored_name] = detector
|
13
|
+
end
|
14
|
+
detectors
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
7
18
|
# Container module for all detectors, implemented as separate classes.
|
8
19
|
module Detectors
|
9
|
-
def self.all
|
10
|
-
ActiveRecordDoctor::Detectors::Base.subclasses
|
11
|
-
end
|
12
20
|
end
|
13
21
|
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordDoctor # :nodoc:
|
4
|
+
def self.handle_exception
|
5
|
+
yield
|
6
|
+
rescue ActiveRecordDoctor::Error => e
|
7
|
+
$stderr.puts(e.user_message)
|
8
|
+
exit(1)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Generic active_record_doctor exception class.
|
12
|
+
class Error < RuntimeError
|
13
|
+
attr_accessor :config_path
|
14
|
+
|
15
|
+
def self.[](*args)
|
16
|
+
new(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def details
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def user_message
|
24
|
+
result =
|
25
|
+
<<-MESSAGE
|
26
|
+
active_record_doctor aborted due to the following error:
|
27
|
+
#{message}
|
28
|
+
|
29
|
+
Configuration file:
|
30
|
+
#{config_path_or_message}
|
31
|
+
MESSAGE
|
32
|
+
|
33
|
+
if details
|
34
|
+
result << (
|
35
|
+
<<-MESSAGE
|
36
|
+
|
37
|
+
Additional information:
|
38
|
+
#{details}
|
39
|
+
MESSAGE
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def config_path_or_message
|
49
|
+
@config_path || "no configuration file in use (using default settings)"
|
50
|
+
end
|
51
|
+
|
52
|
+
def hyphenated_list(items)
|
53
|
+
items.map { |item| " - #{item}" }.join("\n")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Error
|
58
|
+
# We don't need extra documentation for error classes because of their
|
59
|
+
# extensive error messages.
|
60
|
+
# rubocop:disable Style/Documentation
|
61
|
+
|
62
|
+
class ConfigurationFileMissing < Error
|
63
|
+
def initialize
|
64
|
+
super("Configuration file not found")
|
65
|
+
end
|
66
|
+
|
67
|
+
def details
|
68
|
+
<<-MESSAGE
|
69
|
+
active_record_doctor attempted to read a configuration file but could not find
|
70
|
+
it. Please ensure the file exists and is readable (which includes correct
|
71
|
+
permissions are set). If it does not exist or it's readable but you still get
|
72
|
+
this error then consider filing a bug report as active_record_doctor should
|
73
|
+
not attempt to load non-existent configuration files.
|
74
|
+
MESSAGE
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class ConfigurationError < Error
|
79
|
+
def initialize(exc)
|
80
|
+
@exc = exc
|
81
|
+
super("Loading the configuration file resulted in an exception")
|
82
|
+
end
|
83
|
+
|
84
|
+
def details
|
85
|
+
<<-MESSAGE
|
86
|
+
The information below comes from the exception raised when the configuration
|
87
|
+
file was being evaluated. Please try using the details below to fix the error
|
88
|
+
and retry.
|
89
|
+
|
90
|
+
Error class:
|
91
|
+
#{@exc.class.name}
|
92
|
+
|
93
|
+
Error message:
|
94
|
+
#{@exc.message}
|
95
|
+
|
96
|
+
Backtrace:
|
97
|
+
#{@exc.backtrace.join("\n")}
|
98
|
+
MESSAGE
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class ConfigureNotCalled < Error
|
103
|
+
def initialize
|
104
|
+
super("The configuration file did not call ActiveRecordDoctor.configuration")
|
105
|
+
end
|
106
|
+
|
107
|
+
def details
|
108
|
+
<<-MESSAGE
|
109
|
+
active_record_doctor configuration is a Ruby script that MUST call
|
110
|
+
ActiveRecordDoctor.configuration exactly once. That method was NOT called by
|
111
|
+
the configuration file in use. If you intend to provide custom configuration
|
112
|
+
then please ensure that method is called. Otherwise, please delete the
|
113
|
+
configuration file.
|
114
|
+
MESSAGE
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class ConfigureCalledTwice < Error
|
119
|
+
def initialize
|
120
|
+
super("The configuration file called ActiveRecordDoctor.configuration multiple times")
|
121
|
+
end
|
122
|
+
|
123
|
+
def details
|
124
|
+
<<-MESSAGE
|
125
|
+
The configuration file in use has called ActiveRecordDoctor.configure more than
|
126
|
+
once but should do so EXACTLY ONCE. Please ensure that method is called exactly
|
127
|
+
once and retry.
|
128
|
+
MESSAGE
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class DetectorConfiguredTwice < Error
|
133
|
+
def initialize(detector)
|
134
|
+
super("Detector #{detector} was configured multiple times")
|
135
|
+
end
|
136
|
+
|
137
|
+
def details
|
138
|
+
<<-MESSAGE
|
139
|
+
The configuration file configured the same detector more than once which is
|
140
|
+
disallowed. Detector configuration should be either:
|
141
|
+
|
142
|
+
- absent - to use the defaults
|
143
|
+
- present exactly once - to override the defaults
|
144
|
+
|
145
|
+
Please ensure the detector is configured at most once and retry.
|
146
|
+
MESSAGE
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class UnrecognizedDetectorName < Error
|
151
|
+
def initialize(detector, recognized_detectors)
|
152
|
+
@recognized_detectors = recognized_detectors
|
153
|
+
super("Received configuration for an unrecognized detector named #{detector}")
|
154
|
+
end
|
155
|
+
|
156
|
+
def details
|
157
|
+
<<-MESSAGE
|
158
|
+
The configuration file provided configuration for an unknown detector. Please
|
159
|
+
ensure only valid detector names are used and retry.
|
160
|
+
|
161
|
+
Currently, the following detectors are recognized:
|
162
|
+
|
163
|
+
#{hyphenated_list(@recognized_detectors)}
|
164
|
+
MESSAGE
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class UnrecognizedDetectorSettings < Error
|
169
|
+
def initialize(detector, unrecognized_settings, recognized_settings)
|
170
|
+
@detector = detector
|
171
|
+
@unrecognized_settings = unrecognized_settings
|
172
|
+
@recognized_settings = recognized_settings
|
173
|
+
super("Detector #{detector} received unrecognized settings")
|
174
|
+
end
|
175
|
+
|
176
|
+
def details
|
177
|
+
<<-MESSAGE
|
178
|
+
The configuration file provided an unrecognized setting for a detector. Please
|
179
|
+
ensure only recognized settings are used and retry.
|
180
|
+
|
181
|
+
The following settings are not recognized by #{@detector}:
|
182
|
+
|
183
|
+
#{hyphenated_list(@unrecognized_settings)}
|
184
|
+
|
185
|
+
The complete of settings recognized by #{@detector} is:
|
186
|
+
|
187
|
+
#{hyphenated_list(@recognized_settings)}
|
188
|
+
MESSAGE
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class UnrecognizedGlobalSetting < Error
|
193
|
+
def initialize(name, recognized_settings)
|
194
|
+
@recognized_settings = recognized_settings
|
195
|
+
super("Global #{name} is unrecognized")
|
196
|
+
end
|
197
|
+
|
198
|
+
def details
|
199
|
+
<<-MESSAGE
|
200
|
+
The configuration file set an unrecognized global setting. Please ensure that
|
201
|
+
only recognized global settings are used and retry.
|
202
|
+
|
203
|
+
Currently recognized global settings are:
|
204
|
+
|
205
|
+
#{hyphenated_list(@recognized_settings)}
|
206
|
+
MESSAGE
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class DuplicateGlobalSetting < Error
|
211
|
+
def initialize(name)
|
212
|
+
super("Global #{name} was set twice")
|
213
|
+
end
|
214
|
+
|
215
|
+
def details
|
216
|
+
<<-MESSAGE
|
217
|
+
The configuration file set the same global setting twice. Each global setting
|
218
|
+
must be set AT MOST ONCE. Please ensure all global settings are set at most once
|
219
|
+
and retry.
|
220
|
+
MESSAGE
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# rubocop:enable Style/Documentation
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordDoctor
|
4
|
+
# Turn a detector class into a human-readable help text.
|
5
|
+
class Help
|
6
|
+
def initialize(klass)
|
7
|
+
@klass = klass
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
<<-HELP
|
12
|
+
#{klass.underscored_name} - #{klass.description}
|
13
|
+
|
14
|
+
Configuration options:
|
15
|
+
#{config_to_s}
|
16
|
+
HELP
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :klass
|
22
|
+
|
23
|
+
GLOBAL_COMMENT = "local and global"
|
24
|
+
LOCAL_COMMENT = "local only"
|
25
|
+
|
26
|
+
def config_to_s
|
27
|
+
klass.config.map do |key, metadata|
|
28
|
+
type =
|
29
|
+
if metadata[:global]
|
30
|
+
GLOBAL_COMMENT
|
31
|
+
else
|
32
|
+
LOCAL_COMMENT
|
33
|
+
end
|
34
|
+
|
35
|
+
" - #{key} (#{type}) - #{metadata.fetch(:description)}"
|
36
|
+
end.join("\n")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake/tasklib"
|
4
|
+
|
5
|
+
module ActiveRecordDoctor
|
6
|
+
module Rake
|
7
|
+
# A Rake task for calling active_record_doctor detectors.
|
8
|
+
#
|
9
|
+
# The three supported attributes are:
|
10
|
+
#
|
11
|
+
# - deps - project-specific Rake dependencies, e.g. :environment in Rails.
|
12
|
+
# - config_path - active_record_doctor configuration file path.
|
13
|
+
# - setup - a callable (responding to #call) responsible for finishing.
|
14
|
+
# the setup process after deps are invoked, e.g. preloading models.
|
15
|
+
#
|
16
|
+
# The dependencies between Rake tasks are:
|
17
|
+
#
|
18
|
+
# active_record_doctor:<detector> => active_record_doctor:setup => <deps>
|
19
|
+
#
|
20
|
+
# active_record_doctor:setup is where the setup callable is called.
|
21
|
+
class Task < ::Rake::TaskLib
|
22
|
+
attr_accessor :deps, :config_path, :setup
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
super
|
26
|
+
|
27
|
+
@deps = []
|
28
|
+
@config_path = nil
|
29
|
+
@setup = nil
|
30
|
+
|
31
|
+
yield(self)
|
32
|
+
|
33
|
+
define
|
34
|
+
end
|
35
|
+
|
36
|
+
def define
|
37
|
+
namespace :active_record_doctor do
|
38
|
+
task :setup => deps do
|
39
|
+
@setup&.call
|
40
|
+
config
|
41
|
+
end
|
42
|
+
|
43
|
+
ActiveRecordDoctor.detectors.each do |name, detector|
|
44
|
+
desc detector.description
|
45
|
+
task name => :"active_record_doctor:setup" do
|
46
|
+
runner.run_one(name) or exit(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
namespace name do
|
50
|
+
desc "Show help for #{name}"
|
51
|
+
task :help => :"active_record_doctor:setup" do
|
52
|
+
runner.help(name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Run all active_record_doctor detectors"
|
59
|
+
task :active_record_doctor => :"active_record_doctor:setup" do
|
60
|
+
runner.run_all or exit(1)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def runner
|
67
|
+
@runner ||= ActiveRecordDoctor::Runner.new(config)
|
68
|
+
end
|
69
|
+
|
70
|
+
def config
|
71
|
+
@config ||= begin
|
72
|
+
path = config_path && File.exist?(config_path) ? config_path : nil
|
73
|
+
ActiveRecordDoctor.load_config_with_defaults(path)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordDoctor # :nodoc:
|
4
|
+
# An excecution environment for active_record_doctor that provides a config
|
5
|
+
# and an output device for use by detectors.
|
6
|
+
class Runner
|
7
|
+
# io is injected via constructor parameters to facilitate testing.
|
8
|
+
def initialize(config, io = $stdout)
|
9
|
+
@config = config
|
10
|
+
@io = io
|
11
|
+
end
|
12
|
+
|
13
|
+
def run_one(name)
|
14
|
+
ActiveRecordDoctor.handle_exception do
|
15
|
+
ActiveRecordDoctor.detectors.fetch(name).run(config, io)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_all
|
20
|
+
success = true
|
21
|
+
|
22
|
+
# We can't use #all? because of its short-circuit behavior - it stops
|
23
|
+
# iteration and returns false upon the first falsey value. This
|
24
|
+
# prevents other detectors from running if there's a failure.
|
25
|
+
ActiveRecordDoctor.detectors.each do |name, _|
|
26
|
+
success = false if !run_one(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
success
|
30
|
+
end
|
31
|
+
|
32
|
+
def help(name)
|
33
|
+
detector = ActiveRecordDoctor.detectors.fetch(name)
|
34
|
+
io.puts(ActiveRecordDoctor::Help.new(detector))
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :config, :io
|
40
|
+
end
|
41
|
+
end
|
data/lib/active_record_doctor.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_record_doctor/printers"
|
4
|
-
require "active_record_doctor/printers/io_printer"
|
5
3
|
require "active_record_doctor/railtie" if defined?(Rails) && defined?(Rails::Railtie)
|
6
4
|
require "active_record_doctor/detectors"
|
7
5
|
require "active_record_doctor/detectors/base"
|
@@ -9,14 +7,21 @@ require "active_record_doctor/detectors/missing_presence_validation"
|
|
9
7
|
require "active_record_doctor/detectors/missing_foreign_keys"
|
10
8
|
require "active_record_doctor/detectors/missing_unique_indexes"
|
11
9
|
require "active_record_doctor/detectors/incorrect_boolean_presence_validation"
|
10
|
+
require "active_record_doctor/detectors/incorrect_length_validation"
|
12
11
|
require "active_record_doctor/detectors/extraneous_indexes"
|
13
12
|
require "active_record_doctor/detectors/unindexed_deleted_at"
|
14
13
|
require "active_record_doctor/detectors/undefined_table_references"
|
15
14
|
require "active_record_doctor/detectors/missing_non_null_constraint"
|
16
15
|
require "active_record_doctor/detectors/unindexed_foreign_keys"
|
17
16
|
require "active_record_doctor/detectors/incorrect_dependent_option"
|
18
|
-
require "active_record_doctor/
|
17
|
+
require "active_record_doctor/detectors/short_primary_key_type"
|
18
|
+
require "active_record_doctor/detectors/mismatched_foreign_key_type"
|
19
|
+
require "active_record_doctor/errors"
|
20
|
+
require "active_record_doctor/help"
|
21
|
+
require "active_record_doctor/runner"
|
19
22
|
require "active_record_doctor/version"
|
23
|
+
require "active_record_doctor/config"
|
24
|
+
require "active_record_doctor/config/loader"
|
20
25
|
|
21
26
|
module ActiveRecordDoctor # :nodoc:
|
22
27
|
end
|