active_record_doctor 1.7.2 → 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 +4 -4
- data/README.md +29 -0
- data/lib/active_record_doctor.rb +16 -12
- 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 +11 -7
- data/lib/active_record_doctor/{tasks → detectors}/incorrect_boolean_presence_validation.rb +9 -6
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +71 -0
- data/lib/active_record_doctor/{tasks → detectors}/missing_foreign_keys.rb +13 -10
- data/lib/active_record_doctor/{tasks → detectors}/missing_non_null_constraint.rb +11 -7
- data/lib/active_record_doctor/{tasks → detectors}/missing_presence_validation.rb +11 -8
- data/lib/active_record_doctor/{tasks → detectors}/missing_unique_indexes.rb +8 -4
- data/lib/active_record_doctor/{tasks → detectors}/undefined_table_references.rb +11 -12
- data/lib/active_record_doctor/{tasks → detectors}/unindexed_deleted_at.rb +12 -6
- data/lib/active_record_doctor/{tasks → detectors}/unindexed_foreign_keys.rb +13 -10
- data/lib/active_record_doctor/printers.rb +3 -1
- data/lib/active_record_doctor/printers/io_printer.rb +63 -35
- data/lib/active_record_doctor/railtie.rb +2 -0
- 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 +25 -25
- 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 +14 -9
- data/test/model_factory.rb +78 -0
- data/test/setup.rb +69 -40
- metadata +70 -64
- data/lib/active_record_doctor/tasks.rb +0 -10
- data/lib/active_record_doctor/tasks/base.rb +0 -86
- data/test/active_record_doctor/tasks/extraneous_indexes_test.rb +0 -77
- data/test/active_record_doctor/tasks/incorrect_boolean_presence_validation_test.rb +0 -38
- data/test/active_record_doctor/tasks/missing_foreign_keys_test.rb +0 -23
- data/test/active_record_doctor/tasks/missing_non_null_constraint_test.rb +0 -113
- data/test/active_record_doctor/tasks/missing_presence_validation_test.rb +0 -115
- data/test/active_record_doctor/tasks/missing_unique_indexes_test.rb +0 -126
- data/test/active_record_doctor/tasks/undefined_table_references_test.rb +0 -47
- data/test/active_record_doctor/tasks/unindexed_deleted_at_test.rb +0 -59
- data/test/active_record_doctor/tasks/unindexed_foreign_keys_test.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 296a8bbb4a326fd78afb08638435754646e22a4416dc18b02dc66f54198a9130
|
4
|
+
data.tar.gz: 5548b71ee3bb95344e34896a7c972cf39ce33f8a4bd1b03a1a2bf02b4081a1dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48c447ca18f35ee44db354414980f54d0ee9416ecc81b6b3fef9401afbaeb44b2951f0fabe43c89027d8783430bdbb6b055ca1000779e37d3729a171649ce795
|
7
|
+
data.tar.gz: 89ab1d3e4cca4df9c94022e0f11217b41239a3b5f2b3c579d8e466a4fe337909ab48f0b05d28eb5a67fc93afa4afe88a47c7c0ebbd3dc7d45c6d0259db22de40
|
data/README.md
CHANGED
@@ -12,6 +12,7 @@ can:
|
|
12
12
|
* detect missing non-`NULL` constraints - [`active_record_doctor:missing_non_null_constraint`](#detecting-missing-non-null-constraints)
|
13
13
|
* detect missing presence validations - [`active_record_doctor:missing_presence_validation`](#detecting-missing-presence-validations)
|
14
14
|
* detect incorrect presence validations on boolean columns - [`active_record_doctor:incorrect_boolean_presence_validation`](#detecting-incorrect-presence-validations-on-boolean-columns)
|
15
|
+
* detect incorrect values of `dependent` on associations - [`active_record_doctor:incorrect_dependent_option`](#detecting-incorrect-dependent-option-on-associations)
|
15
16
|
|
16
17
|
More features coming soon!
|
17
18
|
|
@@ -284,6 +285,34 @@ This means `active` is validated with `presence: true` instead of
|
|
284
285
|
|
285
286
|
This validator skips models whose corresponding database tables don't exist.
|
286
287
|
|
288
|
+
### Detecting Incorrect `dependent` Option on Associations
|
289
|
+
|
290
|
+
Cascading model deletions can be sped up with `dependent: :delete_all` (to
|
291
|
+
delete all dependent models with one SQL query) but only if the deleted models
|
292
|
+
have no callbacks as they're skipped.
|
293
|
+
|
294
|
+
This can lead to two types of errors:
|
295
|
+
|
296
|
+
- Using `delete_all` when dependent models define callbacks - they will NOT be
|
297
|
+
invoked.
|
298
|
+
- Using `destroy` when dependent models define no callbacks - dependent models
|
299
|
+
will be loaded one-by-one with no reason
|
300
|
+
|
301
|
+
In order to detect associations affected by the two aforementioned problems run
|
302
|
+
the following command:
|
303
|
+
|
304
|
+
```
|
305
|
+
bundle exec rake active_record_doctor:incorrect_dependent_option
|
306
|
+
```
|
307
|
+
|
308
|
+
The output of the command looks like this:
|
309
|
+
|
310
|
+
```
|
311
|
+
The following associations might be using invalid dependent settings:
|
312
|
+
Company: users loads models one-by-one to invoke callbacks even though the related model defines none - consider using `dependent: :delete_all`
|
313
|
+
Post: comments skips callbacks that are defined on the associated model - consider changing to `dependent: :destroy` or similar
|
314
|
+
```
|
315
|
+
|
287
316
|
## Ruby and Rails Compatibility Policy
|
288
317
|
|
289
318
|
The goal of the policy is to ensure proper functioning in reasonable
|
data/lib/active_record_doctor.rb
CHANGED
@@ -1,18 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_record_doctor/printers"
|
2
4
|
require "active_record_doctor/printers/io_printer"
|
3
5
|
require "active_record_doctor/railtie" if defined?(Rails) && defined?(Rails::Railtie)
|
4
|
-
require "active_record_doctor/
|
5
|
-
require "active_record_doctor/
|
6
|
-
require "active_record_doctor/
|
7
|
-
require "active_record_doctor/
|
8
|
-
require "active_record_doctor/
|
9
|
-
require "active_record_doctor/
|
10
|
-
require "active_record_doctor/
|
11
|
-
require "active_record_doctor/
|
12
|
-
require "active_record_doctor/
|
13
|
-
require "active_record_doctor/
|
14
|
-
require "active_record_doctor/
|
6
|
+
require "active_record_doctor/detectors"
|
7
|
+
require "active_record_doctor/detectors/base"
|
8
|
+
require "active_record_doctor/detectors/missing_presence_validation"
|
9
|
+
require "active_record_doctor/detectors/missing_foreign_keys"
|
10
|
+
require "active_record_doctor/detectors/missing_unique_indexes"
|
11
|
+
require "active_record_doctor/detectors/incorrect_boolean_presence_validation"
|
12
|
+
require "active_record_doctor/detectors/extraneous_indexes"
|
13
|
+
require "active_record_doctor/detectors/unindexed_deleted_at"
|
14
|
+
require "active_record_doctor/detectors/undefined_table_references"
|
15
|
+
require "active_record_doctor/detectors/missing_non_null_constraint"
|
16
|
+
require "active_record_doctor/detectors/unindexed_foreign_keys"
|
17
|
+
require "active_record_doctor/detectors/incorrect_dependent_option"
|
18
|
+
require "active_record_doctor/task"
|
15
19
|
require "active_record_doctor/version"
|
16
20
|
|
17
|
-
module ActiveRecordDoctor
|
21
|
+
module ActiveRecordDoctor # :nodoc:
|
18
22
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext/class/subclasses"
|
5
|
+
|
6
|
+
module ActiveRecordDoctor
|
7
|
+
# Container module for all detectors, implemented as separate classes.
|
8
|
+
module Detectors
|
9
|
+
def self.all
|
10
|
+
ActiveRecordDoctor::Detectors::Base.subclasses
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordDoctor
|
4
|
+
module Detectors
|
5
|
+
# Base class for all active_record_doctor detectors.
|
6
|
+
class Base
|
7
|
+
class << self
|
8
|
+
attr_reader :description
|
9
|
+
|
10
|
+
def run
|
11
|
+
new.run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def problems(problems, options = {})
|
18
|
+
[problems, options]
|
19
|
+
end
|
20
|
+
|
21
|
+
def connection
|
22
|
+
@connection ||= ActiveRecord::Base.connection
|
23
|
+
end
|
24
|
+
|
25
|
+
def indexes(table_name)
|
26
|
+
connection.indexes(table_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def tables
|
30
|
+
connection.tables
|
31
|
+
end
|
32
|
+
|
33
|
+
def table_exists?(table_name)
|
34
|
+
connection.table_exists?(table_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def views
|
38
|
+
@views ||=
|
39
|
+
if connection.respond_to?(:views)
|
40
|
+
connection.views
|
41
|
+
elsif connection.adapter_name == "PostgreSQL"
|
42
|
+
ActiveRecord::Base.connection.execute(<<-SQL).map { |tuple| tuple.fetch("relname") }
|
43
|
+
SELECT c.relname FROM pg_class c WHERE c.relkind IN ('m', 'v')
|
44
|
+
SQL
|
45
|
+
else # rubocop:disable Style/EmptyElse
|
46
|
+
# We don't support this Rails/database combination yet.
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash_from_pairs(pairs)
|
52
|
+
Hash[*pairs.flatten(1)]
|
53
|
+
end
|
54
|
+
|
55
|
+
def eager_load!
|
56
|
+
Rails.application.eager_load!
|
57
|
+
end
|
58
|
+
|
59
|
+
def models
|
60
|
+
ActiveRecord::Base.descendants
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,19 +1,23 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_doctor/detectors/base"
|
2
4
|
|
3
5
|
module ActiveRecordDoctor
|
4
|
-
module
|
6
|
+
module Detectors
|
7
|
+
# Detect indexes whose function can be overtaken by other indexes. For example, an index on columns A, B, and C
|
8
|
+
# can also serve as an index on A and A, B.
|
5
9
|
class ExtraneousIndexes < Base
|
6
|
-
@description =
|
10
|
+
@description = "Detect extraneous indexes"
|
7
11
|
|
8
12
|
def run
|
9
|
-
|
13
|
+
problems(subindexes_of_multi_column_indexes + indexed_primary_keys)
|
10
14
|
end
|
11
15
|
|
12
16
|
private
|
13
17
|
|
14
18
|
def subindexes_of_multi_column_indexes
|
15
19
|
tables.reject do |table|
|
16
|
-
"schema_migrations"
|
20
|
+
table == "schema_migrations"
|
17
21
|
end.flat_map do |table|
|
18
22
|
indexes = indexes(table)
|
19
23
|
maximum_indexes = indexes.select do |index|
|
@@ -38,7 +42,7 @@ module ActiveRecordDoctor
|
|
38
42
|
|
39
43
|
def indexed_primary_keys
|
40
44
|
@indexed_primary_keys ||= tables.reject do |table|
|
41
|
-
"schema_migrations"
|
45
|
+
table == "schema_migrations"
|
42
46
|
end.map do |table|
|
43
47
|
[
|
44
48
|
table,
|
@@ -77,7 +81,7 @@ module ActiveRecordDoctor
|
|
77
81
|
end
|
78
82
|
|
79
83
|
def indexes(table_name)
|
80
|
-
super.select { |index| index.columns.
|
84
|
+
super.select { |index| index.columns.is_a?(Array) }
|
81
85
|
end
|
82
86
|
end
|
83
87
|
end
|
@@ -1,16 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_doctor/detectors/base"
|
2
4
|
|
3
5
|
module ActiveRecordDoctor
|
4
|
-
module
|
6
|
+
module Detectors
|
7
|
+
# Find instances of boolean column presence validations that use presence/absence instead of includes/excludes.
|
5
8
|
class IncorrectBooleanPresenceValidation < Base
|
6
|
-
@description =
|
9
|
+
@description = "Detect boolean columns with presence/absence instead of includes/excludes validators"
|
7
10
|
|
8
11
|
def run
|
9
12
|
eager_load!
|
10
13
|
|
11
|
-
|
14
|
+
problems(hash_from_pairs(models.reject do |model|
|
12
15
|
model.table_name.nil? ||
|
13
|
-
model.table_name ==
|
16
|
+
model.table_name == "schema_migrations" ||
|
14
17
|
!table_exists?(model.table_name)
|
15
18
|
end.map do |model|
|
16
19
|
[
|
@@ -20,7 +23,7 @@ module ActiveRecordDoctor
|
|
20
23
|
has_presence_validator?(model, column)
|
21
24
|
end.map(&:name)
|
22
25
|
]
|
23
|
-
end.reject do |
|
26
|
+
end.reject do |_model_name, columns|
|
24
27
|
columns.empty?
|
25
28
|
end))
|
26
29
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_doctor/detectors/base"
|
4
|
+
|
5
|
+
module ActiveRecordDoctor
|
6
|
+
module Detectors
|
7
|
+
# Find has_many/has_one associations with dependent options not taking the
|
8
|
+
# related model's callbacks into account.
|
9
|
+
class IncorrectDependentOption < Base
|
10
|
+
# rubocop:disable Layout/LineLength
|
11
|
+
@description = "Detect associations that should use a different dependent option based on callbacks on the related model"
|
12
|
+
# rubocop:enable Layout/LineLength
|
13
|
+
|
14
|
+
def run
|
15
|
+
eager_load!
|
16
|
+
|
17
|
+
problems(hash_from_pairs(models.reject do |model|
|
18
|
+
model.table_name.nil?
|
19
|
+
end.map do |model|
|
20
|
+
[
|
21
|
+
model.name,
|
22
|
+
associations_with_incorrect_dependent_options(model)
|
23
|
+
]
|
24
|
+
end.reject do |_model_name, associations|
|
25
|
+
associations.empty?
|
26
|
+
end))
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def associations_with_incorrect_dependent_options(model)
|
32
|
+
reflections = model.reflect_on_all_associations(:has_many) + model.reflect_on_all_associations(:has_one)
|
33
|
+
reflections.map do |reflection|
|
34
|
+
if callback_action(reflection) == :invoke && !defines_destroy_callbacks?(reflection.klass)
|
35
|
+
suggestion =
|
36
|
+
case reflection.macro
|
37
|
+
when :has_many then :suggest_delete_all
|
38
|
+
when :has_one then :suggest_delete
|
39
|
+
else raise("unsupported association type #{reflection.macro}")
|
40
|
+
end
|
41
|
+
|
42
|
+
[reflection.name, suggestion]
|
43
|
+
elsif callback_action(reflection) == :skip && defines_destroy_callbacks?(reflection.klass)
|
44
|
+
[reflection.name, :suggest_destroy]
|
45
|
+
end
|
46
|
+
end.compact
|
47
|
+
end
|
48
|
+
|
49
|
+
def callback_action(reflection)
|
50
|
+
case reflection.options[:dependent]
|
51
|
+
when :delete_all then :skip
|
52
|
+
when :destroy then :invoke
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def defines_destroy_callbacks?(model)
|
57
|
+
# Destroying an associated model involves loading it first hence
|
58
|
+
# initialize and find are present. If they are defined on the model
|
59
|
+
# being deleted then theoretically we can't use :delete_all. It's a bit
|
60
|
+
# of an edge case as they usually are either absent or have no side
|
61
|
+
# effects but we're being pedantic -- they could be used for audit
|
62
|
+
# trial, for instance, and we don't want to skip that.
|
63
|
+
model._initialize_callbacks.present? ||
|
64
|
+
model._find_callbacks.present? ||
|
65
|
+
model._destroy_callbacks.present? ||
|
66
|
+
model._commit_callbacks.present? ||
|
67
|
+
model._rollback_callbacks.present?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,13 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_doctor/detectors/base"
|
2
4
|
|
3
5
|
module ActiveRecordDoctor
|
4
|
-
module
|
6
|
+
module Detectors
|
7
|
+
# Find foreign-key like columns lacking an actual foreign key constraint.
|
5
8
|
class MissingForeignKeys < Base
|
6
|
-
@description =
|
9
|
+
@description = "Detect association columns without a foreign key constraint"
|
7
10
|
|
8
11
|
def run
|
9
|
-
|
10
|
-
"schema_migrations"
|
12
|
+
problems(hash_from_pairs(tables.reject do |table|
|
13
|
+
table == "schema_migrations"
|
11
14
|
end.map do |table|
|
12
15
|
[
|
13
16
|
table,
|
@@ -15,19 +18,19 @@ module ActiveRecordDoctor
|
|
15
18
|
# We need to skip polymorphic associations as they can reference
|
16
19
|
# multiple tables but a foreign key constraint can reference
|
17
20
|
# a single predefined table.
|
18
|
-
|
21
|
+
named_like_foreign_key?(column) &&
|
19
22
|
!foreign_key?(table, column) &&
|
20
23
|
!polymorphic_foreign_key?(table, column)
|
21
24
|
end.map(&:name)
|
22
25
|
]
|
23
|
-
end.
|
24
|
-
|
26
|
+
end.reject do |_table, columns|
|
27
|
+
columns.empty?
|
25
28
|
end))
|
26
29
|
end
|
27
30
|
|
28
31
|
private
|
29
32
|
|
30
|
-
def
|
33
|
+
def named_like_foreign_key?(column)
|
31
34
|
column.name.end_with?("_id")
|
32
35
|
end
|
33
36
|
|
@@ -38,7 +41,7 @@ module ActiveRecordDoctor
|
|
38
41
|
end
|
39
42
|
|
40
43
|
def polymorphic_foreign_key?(table, column)
|
41
|
-
type_column_name = column.name.sub(/_id\Z/,
|
44
|
+
type_column_name = column.name.sub(/_id\Z/, "_type")
|
42
45
|
connection.columns(table).any? do |another_column|
|
43
46
|
another_column.name == type_column_name
|
44
47
|
end
|
@@ -1,16 +1,20 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_doctor/detectors/base"
|
2
4
|
|
3
5
|
module ActiveRecordDoctor
|
4
|
-
module
|
6
|
+
module Detectors
|
7
|
+
# Detect model-level presence validators on columns that lack a non-NULL constraint thus allowing potentially
|
8
|
+
# invalid insertions.
|
5
9
|
class MissingNonNullConstraint < Base
|
6
|
-
@description =
|
10
|
+
@description = "Detect presence validators not backed by a non-NULL constraint"
|
7
11
|
|
8
12
|
def run
|
9
13
|
eager_load!
|
10
14
|
|
11
|
-
|
15
|
+
problems(hash_from_pairs(models.reject do |model|
|
12
16
|
model.table_name.nil? ||
|
13
|
-
model.table_name ==
|
17
|
+
model.table_name == "schema_migrations" ||
|
14
18
|
!table_exists?(model.table_name)
|
15
19
|
end.map do |model|
|
16
20
|
[
|
@@ -21,7 +25,7 @@ module ActiveRecordDoctor
|
|
21
25
|
column.null
|
22
26
|
end.map(&:name)
|
23
27
|
]
|
24
|
-
end.reject do |
|
28
|
+
end.reject do |_model_name, columns|
|
25
29
|
columns.empty?
|
26
30
|
end))
|
27
31
|
end
|
@@ -29,7 +33,7 @@ module ActiveRecordDoctor
|
|
29
33
|
private
|
30
34
|
|
31
35
|
def validator_needed?(model, column)
|
32
|
-
![model.primary_key,
|
36
|
+
![model.primary_key, "created_at", "updated_at"].include?(column.name)
|
33
37
|
end
|
34
38
|
|
35
39
|
def has_mandatory_presence_validator?(model, column)
|
@@ -1,16 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_doctor/detectors/base"
|
2
4
|
|
3
5
|
module ActiveRecordDoctor
|
4
|
-
module
|
6
|
+
module Detectors
|
7
|
+
# Detect models with non-NULL columns that lack the corresponding model-level validator.
|
5
8
|
class MissingPresenceValidation < Base
|
6
|
-
@description =
|
9
|
+
@description = "Detect non-NULL columns without a presence validator"
|
7
10
|
|
8
11
|
def run
|
9
12
|
eager_load!
|
10
13
|
|
11
|
-
|
14
|
+
problems(hash_from_pairs(models.reject do |model|
|
12
15
|
model.table_name.nil? ||
|
13
|
-
model.table_name ==
|
16
|
+
model.table_name == "schema_migrations" ||
|
14
17
|
!table_exists?(model.table_name)
|
15
18
|
end.map do |model|
|
16
19
|
[
|
@@ -20,7 +23,7 @@ module ActiveRecordDoctor
|
|
20
23
|
!validator_present?(model, column)
|
21
24
|
end.map(&:name)
|
22
25
|
]
|
23
|
-
end.reject do |
|
26
|
+
end.reject do |_model_name, columns|
|
24
27
|
columns.empty?
|
25
28
|
end))
|
26
29
|
end
|
@@ -28,7 +31,7 @@ module ActiveRecordDoctor
|
|
28
31
|
private
|
29
32
|
|
30
33
|
def validator_needed?(model, column)
|
31
|
-
![model.primary_key,
|
34
|
+
![model.primary_key, "created_at", "updated_at"].include?(column.name) &&
|
32
35
|
!column.null
|
33
36
|
end
|
34
37
|
|
@@ -53,7 +56,7 @@ module ActiveRecordDoctor
|
|
53
56
|
model.validators.any? do |validator|
|
54
57
|
validator.is_a?(ActiveModel::Validations::ExclusionValidator) &&
|
55
58
|
validator.attributes.include?(column.name.to_sym) &&
|
56
|
-
|
59
|
+
validator.options.fetch(:in, []).include?(nil)
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|