database_consistency 0.8.10 → 1.1.0

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