database_consistency 0.8.10 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3790aaad0e110f0764989efae93b0f0e12e8fd3a915fe8d6ed52fb2dec09646b
4
- data.tar.gz: 7d20a3901b932cc31b5c49d4e02dc78725835f13c2d5c7f147a0fe137af35cd1
3
+ metadata.gz: f37408420931f459ea476d095f441e40256ea2279e07c21ad9d477c0781d5944
4
+ data.tar.gz: b054877a7819333fab57dbe22786456d4a0328b462f9808561e470290b3ae6e7
5
5
  SHA512:
6
- metadata.gz: b6f565eaf656ee1d0b63944ab11ebfec0352286bb0d404070eaab4e58e2ad4e9b810ed95292a0162c8df0119ad75861fd462da188a73b239a89293ac044cfd91
7
- data.tar.gz: d66b391ad1b3e84d1e4ad1b839732ea8a695f335f9bb180cea18c7447981cdd3ae947e1df010d5d7c9de0f209176053eba7ccd6a2fb5c32425fe57e8d7ec038d
6
+ metadata.gz: c2e7a18dabde150f3f1f2ee144b6076e2c0fb944c9c128851379e86007f0aed056f1fb550cc19319b777767ffc165534939a680e248213473032054dfa8e2924
7
+ data.tar.gz: f047ff2a3e5e41137a93f5c64e4a0288e74b0d687670e7eabe5b3656a675abf87df75586fd5a0988f25f26f993d20542addfd2d84b430dac7dd30025f2016415
@@ -33,11 +33,17 @@ require 'database_consistency/checkers/validator_checkers/missing_unique_index_c
33
33
  require 'database_consistency/checkers/validators_fraction_checkers/validators_fraction_checker'
34
34
  require 'database_consistency/checkers/validators_fraction_checkers/column_presence_checker'
35
35
 
36
+ require 'database_consistency/checkers/index_checkers/index_checker'
37
+ require 'database_consistency/checkers/index_checkers/unique_index_checker'
38
+ require 'database_consistency/checkers/index_checkers/redundant_index_checker'
39
+ require 'database_consistency/checkers/index_checkers/redundant_unique_index_checker'
40
+
36
41
  require 'database_consistency/processors/base_processor'
37
42
  require 'database_consistency/processors/associations_processor'
38
43
  require 'database_consistency/processors/validators_processor'
39
44
  require 'database_consistency/processors/columns_processor'
40
45
  require 'database_consistency/processors/validators_fractions_processor'
46
+ require 'database_consistency/processors/indexes_processor'
41
47
 
42
48
  # The root module
43
49
  module DatabaseConsistency
@@ -59,7 +59,7 @@ module DatabaseConsistency
59
59
  if belongs_to_association?
60
60
  association.foreign_key
61
61
  else
62
- association.association_primary_key
62
+ association.active_record_primary_key
63
63
  end
64
64
  ).to_s
65
65
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Checkers
5
+ # The base class for table checkers
6
+ class IndexChecker < BaseChecker
7
+ attr_reader :model, :index
8
+
9
+ def initialize(model, index)
10
+ @model = model
11
+ @index = index
12
+ end
13
+
14
+ def column_or_attribute_name
15
+ @column_or_attribute_name ||= index.name.to_s
16
+ end
17
+
18
+ def table_or_model_name
19
+ @table_or_model_name ||= model.name.to_s
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Checkers
5
+ # This class checks redundant database indexes
6
+ class RedundantIndexChecker < IndexChecker
7
+ # Message templates
8
+ REDUNDANT_INDEX = 'index is redundant as (%index) covers it'
9
+
10
+ private
11
+
12
+ # We skip check when:
13
+ # - index is unique
14
+ def preconditions
15
+ !index.unique
16
+ end
17
+
18
+ # Table of possible statuses
19
+ # | validation | status |
20
+ # | ---------- | ------ |
21
+ # | provided | ok |
22
+ # | redundant | fail |
23
+ #
24
+ def check
25
+ if covered_by_index
26
+ report_template(:fail, render_message)
27
+ else
28
+ report_template(:ok)
29
+ end
30
+ end
31
+
32
+ def render_message
33
+ REDUNDANT_INDEX.sub('%index', covered_by_index.name)
34
+ end
35
+
36
+ def covered_by_index
37
+ @covered_by_index ||=
38
+ model.connection.indexes(model.table_name).find do |another_index|
39
+ next if index.name == another_index.name
40
+
41
+ include_index_as_prefix?(another_index)
42
+ end
43
+ end
44
+
45
+ def include_index_as_prefix?(another_index)
46
+ another_index_columns = Helper.extract_index_columns(another_index.columns)
47
+ index_columns == another_index_columns.first(index_columns.size)
48
+ end
49
+
50
+ def index_columns
51
+ @index_columns ||= Helper.extract_index_columns(index.columns)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Checkers
5
+ # This class checks redundant database indexes
6
+ class RedundantUniqueIndexChecker < IndexChecker
7
+ # Message templates
8
+ REDUNDANT_UNIQUE_INDEX = 'index uniqueness is redundant as (%index) covers it'
9
+
10
+ private
11
+
12
+ # We skip check when:
13
+ # - index is not unique
14
+ def preconditions
15
+ index.unique
16
+ end
17
+
18
+ # Table of possible statuses
19
+ # | validation | status |
20
+ # | ---------- | ------ |
21
+ # | provided | ok |
22
+ # | redundant | fail |
23
+ #
24
+ def check
25
+ if covered_by_index
26
+ report_template(:fail, render_message)
27
+ else
28
+ report_template(:ok)
29
+ end
30
+ end
31
+
32
+ def render_message
33
+ REDUNDANT_UNIQUE_INDEX.sub('%index', covered_by_index.name)
34
+ end
35
+
36
+ def covered_by_index
37
+ @covered_by_index ||=
38
+ model.connection.indexes(model.table_name).find do |another_index|
39
+ next if index.name == another_index.name
40
+
41
+ another_index.unique && contain_index?(another_index)
42
+ end
43
+ end
44
+
45
+ def contain_index?(another_index)
46
+ another_index_columns = Helper.extract_index_columns(another_index.columns)
47
+ index_columns & another_index_columns == another_index_columns
48
+ end
49
+
50
+ def index_columns
51
+ @index_columns ||= Helper.extract_index_columns(index.columns)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Checkers
5
+ # This class checks missing uniqueness validator
6
+ class UniqueIndexChecker < IndexChecker
7
+ # Message templates
8
+ VALIDATOR_MISSING = 'index is unique in the database but do not have uniqueness validator'
9
+
10
+ private
11
+
12
+ # We skip check when:
13
+ # - index is not unique
14
+ def preconditions
15
+ index.unique
16
+ end
17
+
18
+ # Table of possible statuses
19
+ # | validation | status |
20
+ # | ---------- | ------ |
21
+ # | provided | ok |
22
+ # | missing | fail |
23
+ #
24
+ def check
25
+ if valid?
26
+ report_template(:ok)
27
+ else
28
+ report_template(:fail, VALIDATOR_MISSING)
29
+ end
30
+ end
31
+
32
+ def valid?
33
+ uniqueness_validators = model.validators.select { |validator| validator.kind == :uniqueness }
34
+
35
+ uniqueness_validators.any? do |validator|
36
+ validator.attributes.any? do |attribute|
37
+ sorted_index_columns == Helper.sorted_uniqueness_validator_columns(attribute, validator, model)
38
+ end
39
+ end
40
+ end
41
+
42
+ def sorted_index_columns
43
+ @sorted_index_columns ||= Helper.extract_index_columns(index.columns).sort
44
+ end
45
+ end
46
+ end
47
+ end
@@ -7,7 +7,7 @@ module DatabaseConsistency
7
7
  MISSING_INDEX = 'model should have proper unique index in the database'
8
8
 
9
9
  def column_or_attribute_name
10
- @column_or_attribute_name ||= index_columns.join('+')
10
+ @column_or_attribute_name ||= Helper.uniqueness_validator_columns(attribute, validator, model).join('+')
11
11
  end
12
12
 
13
13
  private
@@ -33,42 +33,12 @@ module DatabaseConsistency
33
33
 
34
34
  def unique_index
35
35
  @unique_index ||= model.connection.indexes(model.table_name).find do |index|
36
- index.unique && extract_index_columns(index.columns).sort == sorted_index_columns
36
+ index.unique && Helper.extract_index_columns(index.columns).sort == sorted_uniqueness_validator_columns
37
37
  end
38
38
  end
39
39
 
40
- # @return [Array<String>]
41
- def extract_index_columns(index_columns)
42
- return index_columns unless index_columns.is_a?(String)
43
-
44
- index_columns.split(',')
45
- .map(&:strip)
46
- .map { |str| str.gsub(/lower\(/i, 'lower(') }
47
- .map { |str| str.gsub(/\(([^)]+)\)::\w+/, '\1') }
48
- .map { |str| str.gsub(/'([^)]+)'::\w+/, '\1') }
49
- end
50
-
51
- def index_columns
52
- @index_columns ||= ([wrapped_attribute_name] + scope_columns).map(&:to_s)
53
- end
54
-
55
- def scope_columns
56
- @scope_columns ||= Array.wrap(validator.options[:scope]).map do |scope_item|
57
- model._reflect_on_association(scope_item)&.foreign_key || scope_item
58
- end
59
- end
60
-
61
- def sorted_index_columns
62
- @sorted_index_columns ||= index_columns.sort
63
- end
64
-
65
- # @return [String]
66
- def wrapped_attribute_name
67
- if validator.options[:case_sensitive].nil? || validator.options[:case_sensitive]
68
- attribute
69
- else
70
- "lower(#{attribute})"
71
- end
40
+ def sorted_uniqueness_validator_columns
41
+ @sorted_uniqueness_validator_columns ||= Helper.sorted_uniqueness_validator_columns(attribute, validator, model)
72
42
  end
73
43
  end
74
44
  end
@@ -34,5 +34,39 @@ module DatabaseConsistency
34
34
 
35
35
  associations
36
36
  end
37
+
38
+ # @return [Array<String>]
39
+ def extract_index_columns(index_columns)
40
+ return index_columns unless index_columns.is_a?(String)
41
+
42
+ index_columns.split(',')
43
+ .map(&:strip)
44
+ .map { |str| str.gsub(/lower\(/i, 'lower(') }
45
+ .map { |str| str.gsub(/\(([^)]+)\)::\w+/, '\1') }
46
+ .map { |str| str.gsub(/'([^)]+)'::\w+/, '\1') }
47
+ end
48
+
49
+ def sorted_uniqueness_validator_columns(attribute, validator, model)
50
+ uniqueness_validator_columns(attribute, validator, model).sort
51
+ end
52
+
53
+ def uniqueness_validator_columns(attribute, validator, model)
54
+ ([wrapped_attribute_name(attribute, validator)] + scope_columns(validator, model)).map(&:to_s)
55
+ end
56
+
57
+ def scope_columns(validator, model)
58
+ Array.wrap(validator.options[:scope]).map do |scope_item|
59
+ model._reflect_on_association(scope_item)&.foreign_key || scope_item
60
+ end
61
+ end
62
+
63
+ # @return [String]
64
+ def wrapped_attribute_name(attribute, validator)
65
+ if validator.options[:case_sensitive].nil? || validator.options[:case_sensitive]
66
+ attribute
67
+ else
68
+ "lower(#{attribute})"
69
+ end
70
+ end
37
71
  end
38
72
  end
@@ -8,7 +8,8 @@ module DatabaseConsistency
8
8
  ColumnsProcessor,
9
9
  ValidatorsProcessor,
10
10
  AssociationsProcessor,
11
- ValidatorsFractionsProcessor
11
+ ValidatorsFractionsProcessor,
12
+ IndexesProcessor
12
13
  ].flat_map do |processor|
13
14
  processor.new(configuration).reports
14
15
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Processors
5
+ # The class to process indexes
6
+ class IndexesProcessor < BaseProcessor
7
+ CHECKERS = [
8
+ Checkers::UniqueIndexChecker,
9
+ Checkers::RedundantIndexChecker,
10
+ Checkers::RedundantUniqueIndexChecker
11
+ ].freeze
12
+
13
+ private
14
+
15
+ def check # rubocop:disable Metrics/AbcSize
16
+ Helper.parent_models.flat_map do |model|
17
+ next unless configuration.enabled?(model.name.to_s)
18
+
19
+ indexes = ActiveRecord::Base.connection.indexes(model.table_name)
20
+
21
+ indexes.flat_map do |index|
22
+ enabled_checkers.map do |checker_class|
23
+ checker = checker_class.new(model, index)
24
+ checker.report_if_enabled?(configuration)
25
+ end
26
+ end
27
+ end.compact
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,3 +5,5 @@ ActiveStorage::Attachment:
5
5
  enabled: false
6
6
  ActiveStorage::Blob:
7
7
  enabled: false
8
+ ActiveStorage::VariantRecord:
9
+ enabled: false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatabaseConsistency
4
- VERSION = '0.8.10'
4
+ VERSION = '1.1.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: database_consistency
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.10
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeniy Demin
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-18 00:00:00.000000000 Z
11
+ date: 2021-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -122,7 +122,7 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '1.3'
125
- description:
125
+ description:
126
126
  email:
127
127
  - lawliet.djez@gmail.com
128
128
  executables:
@@ -140,6 +140,10 @@ files:
140
140
  - lib/database_consistency/checkers/column_checkers/length_constraint_checker.rb
141
141
  - lib/database_consistency/checkers/column_checkers/null_constraint_checker.rb
142
142
  - lib/database_consistency/checkers/column_checkers/primary_key_type_checker.rb
143
+ - lib/database_consistency/checkers/index_checkers/index_checker.rb
144
+ - lib/database_consistency/checkers/index_checkers/redundant_index_checker.rb
145
+ - lib/database_consistency/checkers/index_checkers/redundant_unique_index_checker.rb
146
+ - lib/database_consistency/checkers/index_checkers/unique_index_checker.rb
143
147
  - lib/database_consistency/checkers/validator_checkers/belongs_to_presence_checker.rb
144
148
  - lib/database_consistency/checkers/validator_checkers/missing_unique_index_checker.rb
145
149
  - lib/database_consistency/checkers/validator_checkers/validator_checker.rb
@@ -154,6 +158,7 @@ files:
154
158
  - lib/database_consistency/processors/associations_processor.rb
155
159
  - lib/database_consistency/processors/base_processor.rb
156
160
  - lib/database_consistency/processors/columns_processor.rb
161
+ - lib/database_consistency/processors/indexes_processor.rb
157
162
  - lib/database_consistency/processors/validators_fractions_processor.rb
158
163
  - lib/database_consistency/processors/validators_processor.rb
159
164
  - lib/database_consistency/rescue_error.rb
@@ -165,7 +170,7 @@ homepage: https://github.com/djezzzl/database_consistency
165
170
  licenses:
166
171
  - MIT
167
172
  metadata: {}
168
- post_install_message:
173
+ post_install_message:
169
174
  rdoc_options: []
170
175
  require_paths:
171
176
  - lib
@@ -181,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
186
  version: '0'
182
187
  requirements: []
183
188
  rubygems_version: 3.0.8
184
- signing_key:
189
+ signing_key:
185
190
  specification_version: 4
186
191
  summary: Provide an easy way to check the consistency of the database constraints
187
192
  with the application validations.