database_consistency 0.8.7 → 0.8.12

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: b1dff50eb6317680dc36fc0260a90c4071bfada212d1124169437123b93d1f65
4
- data.tar.gz: 1fcd504bfaed4674386b3af7d53d9d2d2bd2b1f28d2fb65cd3ae1c1c0f1f1eae
3
+ metadata.gz: ba8c6b25dd4a98dcc7de0dfad22a1c33e4c3a325ab999aedd037fe743c108bdd
4
+ data.tar.gz: 9c563d73dec087a460e10b9acedc365bbc45059028df5571d59ec6dbfa11f1b1
5
5
  SHA512:
6
- metadata.gz: 22aaae1a55a6c7bc6dd3027852cf8e8b0e94e970c06ac445455e1c5abe787be2a273ed9d7f2b7e9c13b5455f827b1bec82d02eb117eed2599c8e90ebd12719ab
7
- data.tar.gz: e9203782f9364aa086c1ee20e03d6497c92ce17d5e5ece293390549ce4e4f3dbeb0ff07980a9be5548558d59527c4e2a5d0c3928b67991ca27d99fa5f9908dfb
6
+ metadata.gz: f51d16f3806889640ad04fca4cddda635c708063d0c6554a6f0589678ca8cfe290db7a9709df5232fe0bbfb14fd8532b8498bb6204e2a341473dd27ae3c40400
7
+ data.tar.gz: '09097fa4bfec79f64a87df086601253594ce409b4d11b736df0127b3b9af10145f721d761ab90c8a0865cd12c48ecb9429211299d1244e02ddf2a88e11c971a9'
@@ -6,6 +6,7 @@ require 'database_consistency/version'
6
6
  require 'database_consistency/helper'
7
7
  require 'database_consistency/configuration'
8
8
  require 'database_consistency/rescue_error'
9
+ require 'database_consistency/errors'
9
10
 
10
11
  require 'database_consistency/writers/base_writer'
11
12
  require 'database_consistency/writers/simple_writer'
@@ -32,11 +33,15 @@ require 'database_consistency/checkers/validator_checkers/missing_unique_index_c
32
33
  require 'database_consistency/checkers/validators_fraction_checkers/validators_fraction_checker'
33
34
  require 'database_consistency/checkers/validators_fraction_checkers/column_presence_checker'
34
35
 
36
+ require 'database_consistency/checkers/index_checkers/index_checker'
37
+ require 'database_consistency/checkers/index_checkers/unique_index_checker'
38
+
35
39
  require 'database_consistency/processors/base_processor'
36
40
  require 'database_consistency/processors/associations_processor'
37
41
  require 'database_consistency/processors/validators_processor'
38
42
  require 'database_consistency/processors/columns_processor'
39
43
  require 'database_consistency/processors/validators_fractions_processor'
44
+ require 'database_consistency/processors/indexes_processor'
40
45
 
41
46
  # The root module
42
47
  module DatabaseConsistency
@@ -19,10 +19,14 @@ module DatabaseConsistency
19
19
 
20
20
  # We skip check when:
21
21
  # - association is polymorphic association
22
+ # - association is has_and_belongs_to_many
22
23
  # - association has `through` option
23
24
  # - associated class doesn't exist
24
25
  def preconditions
25
- !association.polymorphic? && association.through_reflection.nil? && association.klass.present?
26
+ !association.polymorphic? &&
27
+ association.through_reflection.nil? &&
28
+ association.klass.present? &&
29
+ association.macro != :has_and_belongs_to_many
26
30
  rescue NameError
27
31
  false
28
32
  end
@@ -64,7 +68,7 @@ module DatabaseConsistency
64
68
  def associated_key
65
69
  @associated_key ||= (
66
70
  if belongs_to_association?
67
- association.active_record_primary_key
71
+ association.association_primary_key
68
72
  else
69
73
  association.foreign_key
70
74
  end
@@ -91,7 +95,14 @@ module DatabaseConsistency
91
95
  #
92
96
  # @return [ActiveRecord::ConnectionAdapters::Column]
93
97
  def column(model, column_name)
94
- model.connection.columns(model.table_name).find { |column| column.name == column_name }
98
+ model.connection.columns(model.table_name).find { |column| column.name == column_name } ||
99
+ (raise Errors::MissingField, missing_field_error(model.table_name, column_name))
100
+ end
101
+
102
+ # @return [String]
103
+ def missing_field_error(table_name, column_name)
104
+ "Association (#{association.name}) of class (#{association.active_record}) relies on "\
105
+ "field (#{column_name}) of table (#{table_name}) but it is missing."
95
106
  end
96
107
 
97
108
  # @param [ActiveRecord::ConnectionAdapters::Column] column
@@ -16,16 +16,20 @@ module DatabaseConsistency
16
16
  @checker_name ||= name.split('::').last
17
17
  end
18
18
 
19
- # @return [Hash, nil]
20
- def report
19
+ # @param [Boolean] catch_errors
20
+ #
21
+ # @return [Hash, File, nil]
22
+ def report(catch_errors = true)
21
23
  return unless preconditions
22
24
 
23
25
  @report ||= check
24
26
  rescue StandardError => e
27
+ raise e unless catch_errors
28
+
25
29
  RescueError.call(e)
26
30
  end
27
31
 
28
- # @return [Hash, nil]
32
+ # @return [Hash, File, nil]
29
33
  def report_if_enabled?(configuration)
30
34
  report if enabled?(configuration)
31
35
  end
@@ -62,7 +66,7 @@ module DatabaseConsistency
62
66
  raise NotImplementedError
63
67
  end
64
68
 
65
- # @return [Hash]
69
+ # @return [OpenStruct]
66
70
  def report_template(status, message = nil)
67
71
  OpenStruct.new(
68
72
  checker_name: checker_name,
@@ -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,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,41 +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
- end
49
-
50
- def index_columns
51
- @index_columns ||= ([wrapped_attribute_name] + scope_columns).map(&:to_s)
52
- end
53
-
54
- def scope_columns
55
- @scope_columns ||= Array.wrap(validator.options[:scope]).map do |scope_item|
56
- model._reflect_on_association(scope_item)&.foreign_key || scope_item
57
- end
58
- end
59
-
60
- def sorted_index_columns
61
- @sorted_index_columns ||= index_columns.sort
62
- end
63
-
64
- # @return [String]
65
- def wrapped_attribute_name
66
- if validator.options[:case_sensitive].nil? || validator.options[:case_sensitive]
67
- attribute
68
- else
69
- "lower(#{attribute})"
70
- end
40
+ def sorted_uniqueness_validator_columns
41
+ @sorted_uniqueness_validator_columns ||= Helper.sorted_uniqueness_validator_columns(attribute, validator, model)
71
42
  end
72
43
  end
73
44
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Errors
5
+ # The base error class
6
+ class Base < StandardError; end
7
+
8
+ # The error class for missing field
9
+ class MissingField < Base; end
10
+ end
11
+ end
@@ -8,7 +8,7 @@ module DatabaseConsistency
8
8
  # Returns list of models to check
9
9
  def models
10
10
  ActiveRecord::Base.descendants.delete_if(&:abstract_class?).delete_if do |klass|
11
- !klass.connection.table_exists?(klass.table_name)
11
+ !klass.connection.table_exists?(klass.table_name) || klass.name.include?('HABTM_')
12
12
  end
13
13
  end
14
14
 
@@ -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,29 @@
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
+ ].freeze
10
+
11
+ private
12
+
13
+ def check # rubocop:disable Metrics/AbcSize
14
+ Helper.parent_models.flat_map do |model|
15
+ next unless configuration.enabled?(model.name.to_s)
16
+
17
+ indexes = ActiveRecord::Base.connection.indexes(model.table_name)
18
+
19
+ indexes.flat_map do |index|
20
+ enabled_checkers.map do |checker_class|
21
+ checker = checker_class.new(model, index)
22
+ checker.report_if_enabled?(configuration)
23
+ end
24
+ end
25
+ end.compact
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatabaseConsistency
4
- VERSION = '0.8.7'
4
+ VERSION = '0.8.12'
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.7
4
+ version: 0.8.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeniy Demin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-23 00:00:00.000000000 Z
11
+ date: 2020-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -140,6 +140,8 @@ 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/unique_index_checker.rb
143
145
  - lib/database_consistency/checkers/validator_checkers/belongs_to_presence_checker.rb
144
146
  - lib/database_consistency/checkers/validator_checkers/missing_unique_index_checker.rb
145
147
  - lib/database_consistency/checkers/validator_checkers/validator_checker.rb
@@ -149,10 +151,12 @@ files:
149
151
  - lib/database_consistency/databases/factory.rb
150
152
  - lib/database_consistency/databases/types/base.rb
151
153
  - lib/database_consistency/databases/types/sqlite.rb
154
+ - lib/database_consistency/errors.rb
152
155
  - lib/database_consistency/helper.rb
153
156
  - lib/database_consistency/processors/associations_processor.rb
154
157
  - lib/database_consistency/processors/base_processor.rb
155
158
  - lib/database_consistency/processors/columns_processor.rb
159
+ - lib/database_consistency/processors/indexes_processor.rb
156
160
  - lib/database_consistency/processors/validators_fractions_processor.rb
157
161
  - lib/database_consistency/processors/validators_processor.rb
158
162
  - lib/database_consistency/rescue_error.rb