database_consistency 1.1.3 → 1.1.7

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: 9e4c24aeb235c8283ce761d7b4d1e3511246eae28c761a55b4f8ef180a090b22
4
- data.tar.gz: d8e3b783eae3e68e88126c7f9d77de062748201652ec46492522eda6af0722bf
3
+ metadata.gz: 2a742a08f374228e004b155fef15ec5ddaa23404c5aacf42b462eb2cb82ba09a
4
+ data.tar.gz: 8359e199cdbb89fc9d4b8700728bbef938d2d90a1474e4c2400c2aaa6d31f28f
5
5
  SHA512:
6
- metadata.gz: 005c1fa13f5aab3d833c9fd0690907f5df81a0d34ee2dcb5ea19ace51ed41f1438c803d56b95d3573eaa0938b8d9295a04dd5e2dafb6a6ab6ca48c6fcba3af4e
7
- data.tar.gz: fab16372c3782df28f84aac35b997c31ccbb68d3be8ee0b8442e09061c8a85797da584153fc3de151ff1761227348b17041302260daede249b3b10753a1d610b
6
+ metadata.gz: 962c814731ed2c85427e4b3e765df897c8cd8574f6075e3566e65b72af5effa370a14593019748ff9833a9658a0e55841219acdf3fe9caf212aa9db4bc426df3
7
+ data.tar.gz: df3aab9a1b6b5d5b2a815ad237b76ff3ffe127cd7921050c09a57e5f53e08087344ad5777224a603c52978382f6568c0c8a19c16038863cb759cac147bb7f9be
@@ -1,27 +1,52 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require_relative '../lib/database_consistency/configuration'
4
+
5
+ default_config = DatabaseConsistency::Configuration::DEFAULT_PATH
6
+
3
7
  if ARGV.include?('install')
4
- require_relative '../lib/database_consistency/configuration'
5
- config_filename = DatabaseConsistency::Configuration::CONFIGURATION_PATH
6
- file_exists = File.exists?(config_filename)
8
+ require 'pathname'
9
+
10
+ file_exists = File.exists?(default_config)
7
11
  rules = Pathname.new(__FILE__).dirname.join('..', 'lib', 'database_consistency', 'templates', 'rails_defaults.yml').read
8
- if file_exists && File.foreach(config_filename).grep(Regexp.new(rules.lines.first.chomp)).any?
9
- puts "#{config_filename} is already present"
12
+ if file_exists && File.foreach(default_config).grep(Regexp.new(rules.lines.first.chomp)).any?
13
+ puts "#{default_config} is already present"
10
14
  else
11
- File.open(config_filename, 'a') do |file|
15
+ File.open(default_config, 'a') do |file|
12
16
  file << "\n" * 2 if file_exists
13
17
  file << rules
14
18
  end
15
- puts "#{config_filename} #{file_exists ? 'updated' : 'added'}"
19
+ puts "#{default_config} #{file_exists ? 'updated' : 'added'}"
16
20
  end
17
21
  exit 0
18
- else
19
- base_dir = File.join(Dir.pwd, ARGV.first.to_s)
20
- unless File.realpath(base_dir).start_with?(Dir.pwd)
21
- puts "\nWarning! You are going out of current directory, ruby version may be wrong and some gems may be missing.\n"
22
+ end
23
+
24
+ require 'optparse'
25
+
26
+ config = [default_config]
27
+ opt_parser = OptionParser.new do |opts|
28
+ opts.banner = <<-DESC
29
+ Usage: database_consistency install - run installation
30
+ database_consistency [options]
31
+ DESC
32
+
33
+ opts.on('-cFILE', '--config=FILE', 'Use additional configuration file') do |f|
34
+ config << f
35
+ end
36
+
37
+ opts.on('-h', '--help', 'Prints this help') do
38
+ puts opts
39
+ exit
22
40
  end
23
41
  end
24
42
 
43
+ opt_parser.parse!
44
+
45
+ base_dir = File.join(Dir.pwd, ARGV.first.to_s)
46
+ unless File.realpath(base_dir).start_with?(Dir.pwd)
47
+ puts "\nWarning! You are going out of current directory, ruby version may be wrong and some gems may be missing.\n"
48
+ end
49
+
25
50
  # Load Rails project
26
51
  begin
27
52
  require File.join(base_dir, 'config', 'boot')
@@ -41,5 +66,5 @@ $LOAD_PATH.unshift(File.expand_path('lib', __dir__))
41
66
  require 'database_consistency'
42
67
 
43
68
  # Process checks
44
- code = DatabaseConsistency.run
69
+ code = DatabaseConsistency.run(config)
45
70
  exit code
@@ -2,18 +2,23 @@
2
2
 
3
3
  module DatabaseConsistency
4
4
  module Checkers
5
- # This class checks if required +belongs_to+ has foreign key constraint
6
- class BelongsToPresenceChecker < ValidatorChecker
7
- MISSING_FOREIGN_KEY = 'model should have proper foreign key in the database'
5
+ # This class checks if non polymorphic +belongs_to+ association has foreign key constraint
6
+ class ForeignKeyChecker < AssociationChecker
7
+ MISSING_FOREIGN_KEY = 'should have foreign key in the database'
8
8
 
9
9
  private
10
10
 
11
11
  # We skip check when:
12
- # - validator is a not a presence validator
13
- # - there is no belongs_to association with given name
14
- # - belongs_to association is polymorphic
12
+ # - association isn't belongs_to association
13
+ # - association is polymorphic
15
14
  def preconditions
16
- validator.kind == :presence && association && association.belongs_to? && !association.polymorphic?
15
+ supported? && association.belongs_to? && !association.polymorphic?
16
+ end
17
+
18
+ def supported?
19
+ return false if ActiveRecord::VERSION::MAJOR < 5 && ActiveRecord::Base.connection_config[:adapter] == 'sqlite3'
20
+
21
+ true
17
22
  end
18
23
 
19
24
  # Table of possible statuses
@@ -28,10 +33,6 @@ module DatabaseConsistency
28
33
  report_template(:fail, MISSING_FOREIGN_KEY)
29
34
  end
30
35
  end
31
-
32
- def association
33
- @association ||= model.reflect_on_association(attribute)
34
- end
35
36
  end
36
37
  end
37
38
  end
@@ -42,6 +42,8 @@ module DatabaseConsistency
42
42
  else
43
43
  report_template(:fail, render_text)
44
44
  end
45
+ rescue Errors::MissingField => e
46
+ report_template(:fail, e.message)
45
47
  end
46
48
 
47
49
  # @return [String]
@@ -101,8 +103,8 @@ module DatabaseConsistency
101
103
 
102
104
  # @return [String]
103
105
  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."
106
+ "association (#{association.name}) of class (#{association.active_record}) relies on "\
107
+ "field (#{column_name}) of table (#{table_name}) but it is missing"
106
108
  end
107
109
 
108
110
  # @param [ActiveRecord::ConnectionAdapters::Column] column
@@ -6,6 +6,8 @@ module DatabaseConsistency
6
6
  class NullConstraintChecker < ColumnChecker
7
7
  # Message templates
8
8
  VALIDATOR_MISSING = 'column is required in the database but do not have presence validator'
9
+ ASSOCIATION_VALIDATOR_MISSING = 'column is required in the database but do '\
10
+ 'not have presence validator for association (%a_n)'
9
11
 
10
12
  private
11
13
 
@@ -26,21 +28,23 @@ module DatabaseConsistency
26
28
  # | missing | fail |
27
29
  #
28
30
  # We consider PresenceValidation, InclusionValidation, ExclusionValidation, NumericalityValidator with nil,
29
- # or BelongsTo association using this column
31
+ # or required BelongsTo association using this column
30
32
  def check
31
33
  if valid?
32
34
  report_template(:ok)
35
+ elsif belongs_to_association
36
+ report_template(:fail, ASSOCIATION_VALIDATOR_MISSING.gsub('%a_n', belongs_to_association.name.to_s))
33
37
  else
34
38
  report_template(:fail, VALIDATOR_MISSING)
35
39
  end
36
40
  end
37
41
 
38
42
  def valid?
39
- validator?(ActiveModel::Validations::PresenceValidator) ||
40
- validator?(ActiveModel::Validations::InclusionValidator) ||
43
+ validator?(:presence, column.name) ||
44
+ validator?(:inclusion, column.name) ||
41
45
  numericality_validator_without_allow_nil? ||
42
46
  nil_exclusion_validator? ||
43
- belongs_to_association?
47
+ (belongs_to_association && validator?(:presence, belongs_to_association.name))
44
48
  end
45
49
 
46
50
  def primary_field?
@@ -52,28 +56,30 @@ module DatabaseConsistency
52
56
  end
53
57
 
54
58
  def nil_exclusion_validator?
55
- model.validators.grep(ActiveModel::Validations::ExclusionValidator).any? do |validator|
56
- Helper.check_inclusion?(validator.attributes, column.name) &&
59
+ model.validators.any? do |validator|
60
+ validator.kind == :exclusion &&
61
+ Helper.check_inclusion?(validator.attributes, column.name) &&
57
62
  validator.options[:in].include?(nil)
58
63
  end
59
64
  end
60
65
 
61
66
  def numericality_validator_without_allow_nil?
62
- model.validators.grep(ActiveModel::Validations::NumericalityValidator).any? do |validator|
63
- Helper.check_inclusion?(validator.attributes, column.name) &&
67
+ model.validators.any? do |validator|
68
+ validator.kind == :numericality &&
69
+ Helper.check_inclusion?(validator.attributes, column.name) &&
64
70
  !validator.options[:allow_nil]
65
71
  end
66
72
  end
67
73
 
68
- def validator?(validator_class)
69
- model.validators.grep(validator_class).any? do |validator|
70
- Helper.check_inclusion?(validator.attributes, column.name)
74
+ def validator?(kind, attribute)
75
+ model.validators.any? do |validator|
76
+ validator.kind == kind && Helper.check_inclusion?(validator.attributes, attribute)
71
77
  end
72
78
  end
73
79
 
74
- def belongs_to_association?
75
- model.reflect_on_all_associations.grep(ActiveRecord::Reflection::BelongsToReflection).any? do |r|
76
- Helper.check_inclusion?([r.foreign_key, r.foreign_type], column.name)
80
+ def belongs_to_association
81
+ @belongs_to_association ||= model.reflect_on_all_associations.find do |r|
82
+ r.belongs_to? && Helper.check_inclusion?([r.foreign_key, r.foreign_type], column.name)
77
83
  end
78
84
  end
79
85
  end
@@ -29,8 +29,18 @@ module DatabaseConsistency
29
29
  # | all missing | required | ok |
30
30
  # | all missing | optional | fail |
31
31
  def check
32
+ report_message
33
+ rescue Errors::MissingField => e
34
+ report_template(:fail, e.message)
35
+ end
36
+
37
+ def has_weak_option?
38
+ validators.all? { |validator| validator.options.slice(*WEAK_OPTIONS).any? }
39
+ end
40
+
41
+ def report_message
32
42
  can_be_null = column.null
33
- has_weak_option = validators.all? { |validator| validator.options.slice(*WEAK_OPTIONS).any? }
43
+ has_weak_option = has_weak_option?
34
44
 
35
45
  return report_template(:ok) if can_be_null == has_weak_option
36
46
  return report_template(:fail, POSSIBLE_NULL) unless can_be_null
@@ -44,7 +54,7 @@ module DatabaseConsistency
44
54
 
45
55
  def column
46
56
  @column ||= regular_column || association_reference_column ||
47
- (raise Errors::MissingField, "Missing column in #{model.table_name} for #{attribute}")
57
+ (raise Errors::MissingField, "column (#{attribute}) is missing in table (#{model.table_name}) but used for presence validation")
48
58
  end
49
59
 
50
60
  def regular_column
@@ -5,15 +5,20 @@ require 'yaml'
5
5
  module DatabaseConsistency
6
6
  # The class to access configuration options
7
7
  class Configuration
8
- CONFIGURATION_PATH = '.database_consistency.yml'
9
-
10
- def initialize(filepath = CONFIGURATION_PATH)
11
- @configuration = if filepath && File.exist?(filepath)
12
- data = YAML.load_file(filepath)
13
- data.is_a?(Hash) ? data : {}
14
- else
15
- {}
16
- end
8
+ DEFAULT_PATH = '.database_consistency.yml'
9
+
10
+ def initialize(filepaths = DEFAULT_PATH)
11
+ @configuration = Array(filepaths).each_with_object({}) do |filepath, result|
12
+ content =
13
+ if filepath && File.exist?(filepath)
14
+ data = YAML.load_file(filepath)
15
+ data.is_a?(Hash) ? data : {}
16
+ else
17
+ {}
18
+ end
19
+
20
+ combine_configs!(result, content)
21
+ end
17
22
  end
18
23
 
19
24
  def debug?
@@ -48,6 +53,16 @@ module DatabaseConsistency
48
53
 
49
54
  attr_reader :configuration
50
55
 
56
+ def combine_configs!(config, new_config)
57
+ config.merge!(new_config) do |_key, val, new_val|
58
+ if val.is_a?(Hash) && new_val.is_a?(Hash)
59
+ combine_configs!(val, new_val)
60
+ else
61
+ new_val
62
+ end
63
+ end
64
+ end
65
+
51
66
  def settings
52
67
  @settings ||= configuration['DatabaseConsistencySettings']
53
68
  end
@@ -6,6 +6,7 @@ module DatabaseConsistency
6
6
  class AssociationsProcessor < BaseProcessor
7
7
  CHECKERS = [
8
8
  Checkers::MissingIndexChecker,
9
+ Checkers::ForeignKeyChecker,
9
10
  Checkers::ForeignKeyTypeChecker
10
11
  ].freeze
11
12
 
@@ -5,7 +5,6 @@ module DatabaseConsistency
5
5
  # The class to process validators
6
6
  class ValidatorsProcessor < BaseProcessor
7
7
  CHECKERS = [
8
- Checkers::BelongsToPresenceChecker,
9
8
  Checkers::MissingUniqueIndexChecker
10
9
  ].freeze
11
10
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatabaseConsistency
4
- VERSION = '1.1.3'
4
+ VERSION = '1.1.7'
5
5
  end
@@ -19,6 +19,7 @@ require 'database_consistency/checkers/base_checker'
19
19
 
20
20
  require 'database_consistency/checkers/association_checkers/association_checker'
21
21
  require 'database_consistency/checkers/association_checkers/missing_index_checker'
22
+ require 'database_consistency/checkers/association_checkers/foreign_key_checker'
22
23
  require 'database_consistency/checkers/association_checkers/foreign_key_type_checker'
23
24
 
24
25
  require 'database_consistency/checkers/column_checkers/column_checker'
@@ -27,7 +28,6 @@ require 'database_consistency/checkers/column_checkers/length_constraint_checker
27
28
  require 'database_consistency/checkers/column_checkers/primary_key_type_checker'
28
29
 
29
30
  require 'database_consistency/checkers/validator_checkers/validator_checker'
30
- require 'database_consistency/checkers/validator_checkers/belongs_to_presence_checker'
31
31
  require 'database_consistency/checkers/validator_checkers/missing_unique_index_checker'
32
32
 
33
33
  require 'database_consistency/checkers/validators_fraction_checkers/validators_fraction_checker'
@@ -48,8 +48,8 @@ require 'database_consistency/processors/indexes_processor'
48
48
  # The root module
49
49
  module DatabaseConsistency
50
50
  class << self
51
- def run
52
- configuration = Configuration.new
51
+ def run(*args)
52
+ configuration = Configuration.new(*args)
53
53
  reports = Processors.reports(configuration)
54
54
 
55
55
  Writers::SimpleWriter.write(
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: 1.1.3
4
+ version: 1.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeniy Demin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-25 00:00:00.000000000 Z
11
+ date: 2021-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -133,6 +133,7 @@ files:
133
133
  - bin/database_consistency
134
134
  - lib/database_consistency.rb
135
135
  - lib/database_consistency/checkers/association_checkers/association_checker.rb
136
+ - lib/database_consistency/checkers/association_checkers/foreign_key_checker.rb
136
137
  - lib/database_consistency/checkers/association_checkers/foreign_key_type_checker.rb
137
138
  - lib/database_consistency/checkers/association_checkers/missing_index_checker.rb
138
139
  - lib/database_consistency/checkers/base_checker.rb
@@ -144,7 +145,6 @@ files:
144
145
  - lib/database_consistency/checkers/index_checkers/redundant_index_checker.rb
145
146
  - lib/database_consistency/checkers/index_checkers/redundant_unique_index_checker.rb
146
147
  - lib/database_consistency/checkers/index_checkers/unique_index_checker.rb
147
- - lib/database_consistency/checkers/validator_checkers/belongs_to_presence_checker.rb
148
148
  - lib/database_consistency/checkers/validator_checkers/missing_unique_index_checker.rb
149
149
  - lib/database_consistency/checkers/validator_checkers/validator_checker.rb
150
150
  - lib/database_consistency/checkers/validators_fraction_checkers/column_presence_checker.rb