active_record_doctor 1.7.2 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|