database_consistency 0.8.0 → 0.8.5

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: 4f029e8151f93b3824ea0ba0b7cb0eff4d2a1d22dc76597da6169957bffb7611
4
- data.tar.gz: b05b3a118134910eabdbf26a936185ad2cfc45df3417b7d8fad3b0f03d505466
3
+ metadata.gz: ed9a2103f6e588628f52a00ddd933b0dc899fac0d6e8752a3bf1111a3894fb17
4
+ data.tar.gz: d8bf6a551c55fc3e4a33d837354f1b3d777c6171b48aa84d2cce5bbba478e0d5
5
5
  SHA512:
6
- metadata.gz: '05827f5dbbbb745ff76721881eb78669492a3fbadc3949362076b50736f61f0fa1a6c1750780098a12e6fb61389a8e75deb05603e7a3a4c6d7269f3bad3ba78e'
7
- data.tar.gz: 57796525845caf0652456e5141ec464146b3fd88ddb44ad51247b2a497b0afd108585edea38489c21da59055ffd5045f11e713e64a9e2107fdbe5b213d4d783f
6
+ metadata.gz: a1c59e4de257fdaa7aae63725abcc82f7da1f4ea98f51fc804162b8c5488021c3c665f7e7298b135eaddd50560415b563f0cd608fdaaee81519e44af74966bb2
7
+ data.tar.gz: 516f5102afb3234eb88d8a8039b584c9210b33d78401290423032cbd017df5218aa6a455ae5faeffb54dd9bad7ea82a17a40f1729255c7ea2ed988924bad3ea9
@@ -1,9 +1,25 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- base_dir = File.join(Dir.pwd, ARGV.first.to_s)
4
-
5
- unless File.realpath(base_dir).start_with?(Dir.pwd)
6
- puts "\nWarning! You are going out of current directory, ruby version may be wrong and some gems may be missing.\n"
3
+ 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)
7
+ 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"
10
+ else
11
+ File.open(config_filename, 'a') do |file|
12
+ file << "\n" * 2 if file_exists
13
+ file << rules
14
+ end
15
+ puts "#{config_filename} #{file_exists ? 'updated' : 'added'}"
16
+ end
17
+ 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
7
23
  end
8
24
 
9
25
  # Load Rails project
@@ -10,10 +10,15 @@ require 'database_consistency/rescue_error'
10
10
  require 'database_consistency/writers/base_writer'
11
11
  require 'database_consistency/writers/simple_writer'
12
12
 
13
+ require 'database_consistency/databases/factory'
14
+ require 'database_consistency/databases/types/base'
15
+ require 'database_consistency/databases/types/sqlite'
16
+
13
17
  require 'database_consistency/checkers/base_checker'
14
18
 
15
19
  require 'database_consistency/checkers/association_checkers/association_checker'
16
20
  require 'database_consistency/checkers/association_checkers/missing_index_checker'
21
+ require 'database_consistency/checkers/association_checkers/foreign_key_type_checker'
17
22
 
18
23
  require 'database_consistency/checkers/column_checkers/column_checker'
19
24
  require 'database_consistency/checkers/column_checkers/null_constraint_checker'
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Checkers
5
+ # This class checks if association's foreign key type is the same as associated model's primary key
6
+ class ForeignKeyTypeChecker < AssociationChecker
7
+ INCONSISTENT_TYPE = 'associated model key (%a_f) with type (%a_t) mismatches key (%b_f) with type (%b_t)'
8
+
9
+ TYPES = {
10
+ 'serial' => 'integer',
11
+ 'integer' => 'integer',
12
+ 'int' => 'integer',
13
+ 'bigserial' => 'bigint',
14
+ 'bigint' => 'bigint',
15
+ 'bigint unsigned' => 'bigint unsigned'
16
+ }.freeze
17
+
18
+ private
19
+
20
+ def preconditions
21
+ !association.polymorphic? && association.klass.present?
22
+ rescue NameError
23
+ false
24
+ end
25
+
26
+ # Table of possible statuses
27
+ # | type | status |
28
+ # | ------------ | ------ |
29
+ # | consistent | ok |
30
+ # | inconsistent | fail |
31
+ def check
32
+ if converted_type(base_column) == converted_type(associated_column)
33
+ report_template(:ok)
34
+ else
35
+ report_template(:fail, render_text)
36
+ end
37
+ end
38
+
39
+ # @return [String]
40
+ def render_text
41
+ INCONSISTENT_TYPE
42
+ .gsub('%a_t', type(associated_column))
43
+ .gsub('%a_f', associated_key)
44
+ .gsub('%b_t', type(base_column))
45
+ .gsub('%b_f', base_key)
46
+ end
47
+
48
+ # @return [String]
49
+ def base_key
50
+ @base_key ||= belongs_to_association? ? association.foreign_key : association.active_record_primary_key
51
+ end
52
+
53
+ # @return [String]
54
+ def associated_key
55
+ @associated_key ||= belongs_to_association? ? association.active_record_primary_key : association.foreign_key
56
+ end
57
+
58
+ # @return [ActiveRecord::ConnectionAdapters::Column]
59
+ def base_column
60
+ @base_column ||= column(association.active_record, base_key)
61
+ end
62
+
63
+ # @return [ActiveRecord::ConnectionAdapters::Column]
64
+ def associated_column
65
+ @associated_column ||= column(association.klass, associated_key)
66
+ end
67
+
68
+ # @return [DatabaseConsistency::Databases::Factory]
69
+ def database_factory
70
+ @database_factory ||= Databases::Factory.new(association.active_record.connection.adapter_name)
71
+ end
72
+
73
+ # @param [ActiveRecord::Base] model
74
+ # @param [String] column_name
75
+ #
76
+ # @return [ActiveRecord::ConnectionAdapters::Column]
77
+ def column(model, column_name)
78
+ model.connection.columns(model.table_name).find { |column| column.name == column_name }
79
+ end
80
+
81
+ # @param [ActiveRecord::ConnectionAdapters::Column] column
82
+ #
83
+ # @return [String]
84
+ def type(column)
85
+ column.sql_type
86
+ end
87
+
88
+ # @param [ActiveRecord::ConnectionAdapters::Column]
89
+ #
90
+ # @return [String]
91
+ def converted_type(column)
92
+ database_factory.type(type(column)).convert
93
+ end
94
+
95
+ # @return [Boolean]
96
+ def supported_type?(column)
97
+ database_factory.type(type(column)).supported?
98
+ end
99
+
100
+ # @return [Boolean]
101
+ def belongs_to_association?
102
+ association.macro == :belongs_to
103
+ end
104
+ end
105
+ end
106
+ end
@@ -21,8 +21,14 @@ module DatabaseConsistency
21
21
  # We skip check when:
22
22
  # - column hasn't limit constraint
23
23
  # - column insn't string nor text
24
+ # - column is array (PostgreSQL only)
24
25
  def preconditions
25
- !column.limit.nil? && %i[string text].include?(column.type)
26
+ !column.limit.nil? && %i[string text].include?(column.type) && !postgresql_array?
27
+ end
28
+
29
+ # @return [Boolean] true if it is an array (PostgreSQL only)
30
+ def postgresql_array?
31
+ column.respond_to?(:array) && column.array
26
32
  end
27
33
 
28
34
  # Table of possible statuses
@@ -25,7 +25,7 @@ module DatabaseConsistency
25
25
  # | provided | ok |
26
26
  # | missing | fail |
27
27
  #
28
- # We consider PresenceValidation, InclusionValidation, ExclusionValidation with nil,
28
+ # We consider PresenceValidation, InclusionValidation, ExclusionValidation, NumericalityValidator with nil,
29
29
  # or BelongsTo association using this column
30
30
  def check
31
31
  if valid?
@@ -38,6 +38,7 @@ module DatabaseConsistency
38
38
  def valid?
39
39
  validator?(ActiveModel::Validations::PresenceValidator) ||
40
40
  validator?(ActiveModel::Validations::InclusionValidator) ||
41
+ numericality_validator_without_allow_nil? ||
41
42
  nil_exclusion_validator? ||
42
43
  belongs_to_association?
43
44
  end
@@ -57,6 +58,13 @@ module DatabaseConsistency
57
58
  end
58
59
  end
59
60
 
61
+ def numericality_validator_without_allow_nil?
62
+ model.validators.grep(ActiveModel::Validations::NumericalityValidator).any? do |validator|
63
+ Helper.check_inclusion?(validator.attributes, column.name) &&
64
+ !validator.options[:allow_nil]
65
+ end
66
+ end
67
+
60
68
  def validator?(validator_class)
61
69
  model.validators.grep(validator_class).any? do |validator|
62
70
  Helper.check_inclusion?(validator.attributes, column.name)
@@ -48,7 +48,13 @@ module DatabaseConsistency
48
48
  end
49
49
 
50
50
  def index_columns
51
- @index_columns ||= ([wrapped_attribute_name] + Array.wrap(validator.options[:scope])).map(&:to_s)
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
52
58
  end
53
59
 
54
60
  def sorted_index_columns
@@ -4,7 +4,7 @@ module DatabaseConsistency
4
4
  module Checkers
5
5
  # This class checks if presence validator has non-null constraint in the database
6
6
  class ColumnPresenceChecker < ValidatorsFractionChecker
7
- WEAK_OPTIONS = %i[allow_nil allow_blank if unless].freeze
7
+ WEAK_OPTIONS = %i[allow_nil allow_blank if unless on].freeze
8
8
  # Message templates
9
9
  CONSTRAINT_MISSING = 'column should be required in the database'
10
10
  POSSIBLE_NULL = 'column is required but there is possible null value insert'
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Databases
5
+ # Factory for database adapters
6
+ class Factory
7
+ attr_reader :adapter
8
+
9
+ # @param [String] adapter
10
+ def initialize(adapter)
11
+ @adapter = adapter
12
+ end
13
+
14
+ # @return [DatabaseConsistency::Databases::Types::Base]
15
+ def type(type)
16
+ sqlite? ? Types::Sqlite.new(type) : Types::Base.new(type)
17
+ end
18
+
19
+ private
20
+
21
+ # @return [Boolean]
22
+ def sqlite?
23
+ adapter == 'SQLite'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Databases
5
+ module Types
6
+ # Base wrapper for database types
7
+ class Base
8
+ attr_reader :type
9
+
10
+ # @param [String] type
11
+ def initialize(type)
12
+ @type = type
13
+ end
14
+
15
+ # @return [String]
16
+ def convert
17
+ type
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseConsistency
4
+ module Databases
5
+ module Types
6
+ # Wraps types for SQLite database
7
+ class Sqlite < Base
8
+ TYPES = {
9
+ 'bigserial' => 'bigint',
10
+ 'bigint' => 'bigint',
11
+ 'serial' => 'integer',
12
+ 'integer' => 'integer'
13
+ }.freeze
14
+
15
+ # @return [String]
16
+ def convert
17
+ TYPES[type]
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -5,7 +5,8 @@ module DatabaseConsistency
5
5
  # The class to process associations
6
6
  class AssociationsProcessor < BaseProcessor
7
7
  CHECKERS = [
8
- Checkers::MissingIndexChecker
8
+ Checkers::MissingIndexChecker,
9
+ Checkers::ForeignKeyTypeChecker
9
10
  ].freeze
10
11
 
11
12
  private
@@ -33,7 +33,7 @@ module DatabaseConsistency
33
33
  private
34
34
 
35
35
  def filename
36
- "database_consistency_#{Time.now.strftime('%Y_%m_%d_%H_%M_%S')}"
36
+ @filename ||= "database_consistency_#{Time.now.strftime('%Y_%m_%d_%H_%M_%S')}"
37
37
  end
38
38
  end
39
39
  end
@@ -0,0 +1,7 @@
1
+ # Ignore false positive from Rails' ActionText and ActiveStorage
2
+ ActionText::RichText:
3
+ enabled: false
4
+ ActiveStorage::Attachment:
5
+ enabled: false
6
+ ActiveStorage::Blob:
7
+ enabled: false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatabaseConsistency
4
- VERSION = '0.8.0'
4
+ VERSION = '0.8.5'
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.0
4
+ version: 0.8.5
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-03-02 00:00:00.000000000 Z
11
+ date: 2020-09-21 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:
@@ -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_type_checker.rb
136
137
  - lib/database_consistency/checkers/association_checkers/missing_index_checker.rb
137
138
  - lib/database_consistency/checkers/base_checker.rb
138
139
  - lib/database_consistency/checkers/column_checkers/column_checker.rb
@@ -145,6 +146,9 @@ files:
145
146
  - lib/database_consistency/checkers/validators_fraction_checkers/column_presence_checker.rb
146
147
  - lib/database_consistency/checkers/validators_fraction_checkers/validators_fraction_checker.rb
147
148
  - lib/database_consistency/configuration.rb
149
+ - lib/database_consistency/databases/factory.rb
150
+ - lib/database_consistency/databases/types/base.rb
151
+ - lib/database_consistency/databases/types/sqlite.rb
148
152
  - lib/database_consistency/helper.rb
149
153
  - lib/database_consistency/processors/associations_processor.rb
150
154
  - lib/database_consistency/processors/base_processor.rb
@@ -152,6 +156,7 @@ files:
152
156
  - lib/database_consistency/processors/validators_fractions_processor.rb
153
157
  - lib/database_consistency/processors/validators_processor.rb
154
158
  - lib/database_consistency/rescue_error.rb
159
+ - lib/database_consistency/templates/rails_defaults.yml
155
160
  - lib/database_consistency/version.rb
156
161
  - lib/database_consistency/writers/base_writer.rb
157
162
  - lib/database_consistency/writers/simple_writer.rb
@@ -159,7 +164,7 @@ homepage: https://github.com/djezzzl/database_consistency
159
164
  licenses:
160
165
  - MIT
161
166
  metadata: {}
162
- post_install_message:
167
+ post_install_message:
163
168
  rdoc_options: []
164
169
  require_paths:
165
170
  - lib
@@ -174,9 +179,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
174
179
  - !ruby/object:Gem::Version
175
180
  version: '0'
176
181
  requirements: []
177
- rubyforge_project:
178
- rubygems_version: 2.7.9
179
- signing_key:
182
+ rubygems_version: 3.0.8
183
+ signing_key:
180
184
  specification_version: 4
181
185
  summary: Provide an easy way to check the consistency of the database constraints
182
186
  with the application validations.