active_record_doctor 2.0.0 → 2.0.1
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 +9 -10
- data/lib/active_record_doctor/config/default.rb +2 -2
- data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +13 -37
- data/lib/active_record_doctor/detectors/missing_presence_validation.rb +24 -22
- data/lib/active_record_doctor/version.rb +1 -1
- metadata +1 -4
- data/lib/active_record_doctor/adapters/database.rb +0 -17
- data/lib/active_record_doctor/adapters/table.rb +0 -18
- data/lib/active_record_doctor/adapters/validators.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94e700a1c78ee5e9657d1107578c2932a84acba7f592ab6a416e5c063520a311
|
4
|
+
data.tar.gz: 8496adb8a597250ec95276fc9fbad50397667f7de2336f80405203b3a3c2a459
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79803ef2db49147fdf2daf5983a3f36fb895704d150347442bebdc192a6980ecb8cd438c23fe335941284e0a39aee8c3e1a725bfe3845c7a0d3dde03830068dc
|
7
|
+
data.tar.gz: 2feb5b594c369dc3d39e0f85540724474e5caa7573e0d0f18a5dcddd63d12113ec4c670ef374b475f237bd3d75fe7281bca0b0d219aac6582cbddbe59bdc2340
|
data/README.md
CHANGED
@@ -302,16 +302,15 @@ Supported configuration options:
|
|
302
302
|
|
303
303
|
### Detecting Missing Foreign Key Constraints
|
304
304
|
|
305
|
-
If `
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
level.
|
305
|
+
If `User` defines a `belongs_to` association to `Profile` then the underlying
|
306
|
+
column (`users.profile_id` by convention) should be marked as a foreign key in
|
307
|
+
the database. If it's not then it's possible to end up in a situation where a
|
308
|
+
user is referencing a non-existent profile.
|
310
309
|
|
311
310
|
`active_record_doctor` can automatically detect foreign keys that could benefit
|
312
311
|
from a foreign key constraint (a future version will generate a migration that
|
313
|
-
add the constraint; for now, it's your job). You can obtain the list of
|
314
|
-
|
312
|
+
add the constraint; for now, it's your job). You can obtain the list of missing
|
313
|
+
foreign key constraints with the following command:
|
315
314
|
|
316
315
|
```bash
|
317
316
|
bundle exec rake active_record_doctor:missing_foreign_keys
|
@@ -331,9 +330,9 @@ end
|
|
331
330
|
Supported configuration options:
|
332
331
|
|
333
332
|
- `enabled` - set to `false` to disable the detector altogether
|
334
|
-
- `
|
335
|
-
- `
|
336
|
-
checked.
|
333
|
+
- `ignore_models` - models whose associations should not be checked.
|
334
|
+
- `ignore_associations` - associations, written as Model.association, that
|
335
|
+
should not be checked.
|
337
336
|
|
338
337
|
### Detecting Models Referencing Undefined Tables
|
339
338
|
|
@@ -7,12 +7,12 @@ module ActiveRecordDoctor
|
|
7
7
|
class MissingForeignKeys < Base # :nodoc:
|
8
8
|
@description = "detect foreign-key-like columns lacking an actual foreign key constraint"
|
9
9
|
@config = {
|
10
|
-
|
11
|
-
description: "
|
10
|
+
ignore_models: {
|
11
|
+
description: "models whose columns should not be checked",
|
12
12
|
global: true
|
13
13
|
},
|
14
|
-
|
15
|
-
description: "
|
14
|
+
ignore_associations: {
|
15
|
+
description: "associations, written as Model.association, that should not be checked"
|
16
16
|
}
|
17
17
|
}
|
18
18
|
|
@@ -23,42 +23,18 @@ module ActiveRecordDoctor
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def detect
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# multiple tables but a foreign key constraint can reference
|
30
|
-
# a single predefined table.
|
31
|
-
next unless looks_like_foreign_key?(column)
|
32
|
-
next if foreign_key?(table, column)
|
33
|
-
next if polymorphic_foreign_key?(table, column)
|
34
|
-
next if model_destroyed_async?(table, column)
|
26
|
+
each_model(except: config(:ignore_models), existing_tables_only: true) do |model|
|
27
|
+
foreign_keys = connection.foreign_keys(model.table_name)
|
28
|
+
foreign_key_columns = foreign_keys.map { |key| key.options[:column] }
|
35
29
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def foreign_key?(table, column)
|
42
|
-
connection.foreign_keys(table).any? do |foreign_key|
|
43
|
-
foreign_key.options[:column] == column.name
|
44
|
-
end
|
45
|
-
end
|
30
|
+
each_association(model, type: :belongs_to) do |association|
|
31
|
+
next if ignored?("#{model.name}.#{association.name}", config(:ignore_associations))
|
32
|
+
next if association.options[:polymorphic]
|
46
33
|
|
47
|
-
|
48
|
-
|
49
|
-
connection.columns(table).any? do |another_column|
|
50
|
-
another_column.name == type_column_name
|
51
|
-
end
|
52
|
-
end
|
34
|
+
has_foreign_key = foreign_key_columns.include?(association.foreign_key)
|
35
|
+
next if has_foreign_key
|
53
36
|
|
54
|
-
|
55
|
-
# Check if there are any models having `has_many ..., dependent: :destroy_async`
|
56
|
-
# referencing the specified table.
|
57
|
-
models.any? do |model|
|
58
|
-
model.reflect_on_all_associations(:has_many).any? do |reflection|
|
59
|
-
reflection.options[:dependent] == :destroy_async &&
|
60
|
-
reflection.foreign_key == column.name &&
|
61
|
-
reflection.klass.table_name == table
|
37
|
+
problem!(table: model.table_name, column: association.foreign_key)
|
62
38
|
end
|
63
39
|
end
|
64
40
|
end
|
@@ -21,14 +21,14 @@ module ActiveRecordDoctor
|
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
def message(type:,
|
24
|
+
def message(type:, column:, reflection:, model:)
|
25
25
|
case type
|
26
26
|
when :missing_validator
|
27
|
-
"add a `presence` validator to #{model}.#{
|
27
|
+
"add a `presence` validator to #{model}.#{column} - it's NOT NULL but lacks a validator"
|
28
28
|
when :optional_association
|
29
|
-
"add `optional: false` to #{model}.#{
|
29
|
+
"add `optional: false` to #{model}.#{reflection.name} - the foreign key #{reflection.foreign_key} is NOT NULL"
|
30
30
|
when :optional_polymorphic_association
|
31
|
-
"add `optional: false` to #{model}.#{
|
31
|
+
"add `optional: false` to #{model}.#{reflection.name} - the foreign key #{reflection.foreign_key} or type #{reflection.foreign_type} are NOT NULL" # rubocop:disable Layout/LineLength
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -56,7 +56,7 @@ module ActiveRecordDoctor
|
|
56
56
|
(config(:ignore_columns_with_default) && (column.default || column.default_function)) ||
|
57
57
|
|
58
58
|
# Explicitly ignored columns should be skipped.
|
59
|
-
|
59
|
+
ignored?("#{model.name}.#{column.name}", config(:ignore_attributes))
|
60
60
|
end
|
61
61
|
|
62
62
|
# At this point the only columns that are left are those that DO
|
@@ -76,28 +76,30 @@ module ActiveRecordDoctor
|
|
76
76
|
# that are validated directly or via an association name.
|
77
77
|
model.validators.each do |validator|
|
78
78
|
problematic_columns.reject! do |column|
|
79
|
-
|
80
|
-
|
79
|
+
attribute_names = [
|
80
|
+
column.name.to_sym,
|
81
|
+
column_name_to_association_name[column.name]
|
82
|
+
].compact
|
81
83
|
|
82
84
|
case validator
|
83
85
|
|
84
86
|
# A regular presence validator is enough if the column name is
|
85
87
|
# listed among the attributes it's validating.
|
86
88
|
when ActiveRecord::Validations::PresenceValidator
|
87
|
-
validator.attributes.
|
89
|
+
(validator.attributes & attribute_names).present?
|
88
90
|
|
89
91
|
# An inclusion validator ensures the column is not nil if it covers
|
90
92
|
# the column and nil is NOT one of the values it allows.
|
91
93
|
when ActiveModel::Validations::InclusionValidator
|
92
94
|
validator_items = inclusion_or_exclusion_validator_items(validator)
|
93
|
-
validator.attributes.
|
95
|
+
(validator.attributes & attribute_names).present? &&
|
94
96
|
(validator_items.is_a?(Proc) || validator_items.exclude?(nil))
|
95
97
|
|
96
98
|
# An exclusion validator ensures the column is not nil if it covers
|
97
99
|
# the column and excludes nil as an allowed value explicitly.
|
98
100
|
when ActiveModel::Validations::ExclusionValidator
|
99
101
|
validator_items = inclusion_or_exclusion_validator_items(validator)
|
100
|
-
validator.attributes.
|
102
|
+
(validator.attributes & attribute_names).present? &&
|
101
103
|
(validator_items.is_a?(Proc) || validator_items.include?(nil))
|
102
104
|
|
103
105
|
end
|
@@ -109,7 +111,7 @@ module ActiveRecordDoctor
|
|
109
111
|
problematic_associations = []
|
110
112
|
problematic_polymorphic_associations = []
|
111
113
|
|
112
|
-
model.reflect_on_all_associations.each do |reflection|
|
114
|
+
model.reflect_on_all_associations(:belongs_to).each do |reflection|
|
113
115
|
foreign_key_column = problematic_columns.find { |column| column.name == reflection.foreign_key }
|
114
116
|
if reflection.polymorphic?
|
115
117
|
# If the foreign key and type are not one of the columns that lack
|
@@ -125,7 +127,7 @@ module ActiveRecordDoctor
|
|
125
127
|
|
126
128
|
# ... report an error about an incorrectly configured polymorphic
|
127
129
|
# association.
|
128
|
-
problematic_polymorphic_associations << reflection
|
130
|
+
problematic_polymorphic_associations << reflection
|
129
131
|
else
|
130
132
|
# If the foreign key is not one of the columns that lack a
|
131
133
|
# validator then it means the association added a validator and is
|
@@ -137,7 +139,7 @@ module ActiveRecordDoctor
|
|
137
139
|
problematic_columns.delete(foreign_key_column)
|
138
140
|
|
139
141
|
# ... report an error about an incorrectly configured association.
|
140
|
-
problematic_associations << reflection
|
142
|
+
problematic_associations << reflection
|
141
143
|
end
|
142
144
|
end
|
143
145
|
|
@@ -145,22 +147,22 @@ module ActiveRecordDoctor
|
|
145
147
|
# ignored should be removed from the output. It's NOT enough to skip
|
146
148
|
# processing them in the loop above because their underlying foreign
|
147
149
|
# key and type columns must be removed from output, too.
|
148
|
-
problematic_associations.reject! do |
|
149
|
-
config(:ignore_attributes).include?("#{model.name}.#{name}")
|
150
|
+
problematic_associations.reject! do |reflection|
|
151
|
+
config(:ignore_attributes).include?("#{model.name}.#{reflection.name}")
|
150
152
|
end
|
151
|
-
problematic_polymorphic_associations.reject! do |
|
152
|
-
config(:ignore_attributes).include?("#{model.name}.#{name}")
|
153
|
+
problematic_polymorphic_associations.reject! do |reflection|
|
154
|
+
config(:ignore_attributes).include?("#{model.name}.#{reflection.name}")
|
153
155
|
end
|
154
156
|
|
155
157
|
# Job is done and all accumulated errors can be reported.
|
156
|
-
problematic_polymorphic_associations.each do |
|
157
|
-
problem!(type: :optional_polymorphic_association,
|
158
|
+
problematic_polymorphic_associations.each do |reflection|
|
159
|
+
problem!(type: :optional_polymorphic_association, column: nil, reflection: reflection, model: model.name)
|
158
160
|
end
|
159
|
-
problematic_associations.each do |
|
160
|
-
problem!(type: :optional_association,
|
161
|
+
problematic_associations.each do |reflection|
|
162
|
+
problem!(type: :optional_association, column: nil, reflection: reflection, model: model.name)
|
161
163
|
end
|
162
164
|
problematic_columns.each do |column|
|
163
|
-
problem!(type: :missing_validator,
|
165
|
+
problem!(type: :missing_validator, column: column.name, reflection: nil, model: model.name)
|
164
166
|
end
|
165
167
|
end
|
166
168
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_doctor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Navis
|
@@ -144,9 +144,6 @@ files:
|
|
144
144
|
- MIT-LICENSE.txt
|
145
145
|
- README.md
|
146
146
|
- lib/active_record_doctor.rb
|
147
|
-
- lib/active_record_doctor/adapters/database.rb
|
148
|
-
- lib/active_record_doctor/adapters/table.rb
|
149
|
-
- lib/active_record_doctor/adapters/validators.rb
|
150
147
|
- lib/active_record_doctor/config.rb
|
151
148
|
- lib/active_record_doctor/config/default.rb
|
152
149
|
- lib/active_record_doctor/config/loader.rb
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module ActiveRecordDoctor
|
2
|
-
module Adapters
|
3
|
-
class Table
|
4
|
-
def initialize(connection, table_name)
|
5
|
-
@connection = connection
|
6
|
-
@table_name = table_name
|
7
|
-
end
|
8
|
-
|
9
|
-
def columns
|
10
|
-
connection.columns(table_name)
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
attr_reader :connection, :table_name
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|