database_consistency 0.8.3 → 0.8.8

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: 5015849cbfceb5d46a68ab0c3f9ba0e620fffbf013c16af563d3aa8fd16b140f
4
- data.tar.gz: dd379822c590457db98fd9f23fe99f19868063cf994e804eec11fe61b7311105
3
+ metadata.gz: 406fd80b86af26702bba071b8dabe3d8f238923bd7ef6d04776b014d2c5c5575
4
+ data.tar.gz: b30e6bee8c79233090fb8a94557217a6e2fc72e618b577b858083bf0cd3ba71e
5
5
  SHA512:
6
- metadata.gz: 3ec317d549f0852e6396db14f7263904448a3dda2c2515f83a174946a9d8138163f909b078a235a41c5d36daa01bbc38cd61c03f23921d3ad8704c7742e7fcec
7
- data.tar.gz: 8c41bdf53040283417a3d616e9cf9b13a34138ba17edf65b84df1506e630e758d88995a618ea2b111a8d45884c49bf8b4e5f9b2c38e16fbfdd379328260aa0f1
6
+ metadata.gz: 72147fa9f0eccd7ac75216fe673757541048b143ea31fbab435a83e7cb10f62d3e45ae19a5e0c8e609515d3033fa7d868deba863fd8d7ba152f50d254fb3f534
7
+ data.tar.gz: 6c172f7b74de5face1e235103a2821d41be08f282a840b756caf73f24285e8cf6f7e1f5ecf5e40f26160a8036c947c335edc5338ef3ca274a989d9353220f04d
@@ -6,14 +6,20 @@ 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'
12
13
 
14
+ require 'database_consistency/databases/factory'
15
+ require 'database_consistency/databases/types/base'
16
+ require 'database_consistency/databases/types/sqlite'
17
+
13
18
  require 'database_consistency/checkers/base_checker'
14
19
 
15
20
  require 'database_consistency/checkers/association_checkers/association_checker'
16
21
  require 'database_consistency/checkers/association_checkers/missing_index_checker'
22
+ require 'database_consistency/checkers/association_checkers/foreign_key_type_checker'
17
23
 
18
24
  require 'database_consistency/checkers/column_checkers/column_checker'
19
25
  require 'database_consistency/checkers/column_checkers/null_constraint_checker'
@@ -0,0 +1,128 @@
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
+ # We skip check when:
21
+ # - association is polymorphic association
22
+ # - association is has_and_belongs_to_many
23
+ # - association has `through` option
24
+ # - associated class doesn't exist
25
+ def preconditions
26
+ !association.polymorphic? &&
27
+ association.through_reflection.nil? &&
28
+ association.klass.present? &&
29
+ association.macro != :has_and_belongs_to_many
30
+ rescue NameError
31
+ false
32
+ end
33
+
34
+ # Table of possible statuses
35
+ # | type | status |
36
+ # | ------------ | ------ |
37
+ # | consistent | ok |
38
+ # | inconsistent | fail |
39
+ def check
40
+ if converted_type(base_column) == converted_type(associated_column)
41
+ report_template(:ok)
42
+ else
43
+ report_template(:fail, render_text)
44
+ end
45
+ end
46
+
47
+ # @return [String]
48
+ def render_text
49
+ INCONSISTENT_TYPE
50
+ .gsub('%a_t', type(associated_column))
51
+ .gsub('%a_f', associated_key)
52
+ .gsub('%b_t', type(base_column))
53
+ .gsub('%b_f', base_key)
54
+ end
55
+
56
+ # @return [String]
57
+ def base_key
58
+ @base_key ||= (
59
+ if belongs_to_association?
60
+ association.foreign_key
61
+ else
62
+ association.active_record_primary_key
63
+ end
64
+ ).to_s
65
+ end
66
+
67
+ # @return [String]
68
+ def associated_key
69
+ @associated_key ||= (
70
+ if belongs_to_association?
71
+ association.active_record_primary_key
72
+ else
73
+ association.foreign_key
74
+ end
75
+ ).to_s
76
+ end
77
+
78
+ # @return [ActiveRecord::ConnectionAdapters::Column]
79
+ def base_column
80
+ @base_column ||= column(association.active_record, base_key)
81
+ end
82
+
83
+ # @return [ActiveRecord::ConnectionAdapters::Column]
84
+ def associated_column
85
+ @associated_column ||= column(association.klass, associated_key)
86
+ end
87
+
88
+ # @return [DatabaseConsistency::Databases::Factory]
89
+ def database_factory
90
+ @database_factory ||= Databases::Factory.new(association.active_record.connection.adapter_name)
91
+ end
92
+
93
+ # @param [ActiveRecord::Base] model
94
+ # @param [String] column_name
95
+ #
96
+ # @return [ActiveRecord::ConnectionAdapters::Column]
97
+ def column(model, 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."
106
+ end
107
+
108
+ # @param [ActiveRecord::ConnectionAdapters::Column] column
109
+ #
110
+ # @return [String]
111
+ def type(column)
112
+ column.sql_type
113
+ end
114
+
115
+ # @param [ActiveRecord::ConnectionAdapters::Column]
116
+ #
117
+ # @return [String]
118
+ def converted_type(column)
119
+ database_factory.type(type(column)).convert
120
+ end
121
+
122
+ # @return [Boolean]
123
+ def belongs_to_association?
124
+ association.macro == :belongs_to
125
+ end
126
+ end
127
+ end
128
+ end
@@ -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,
@@ -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
@@ -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] || type
18
+ end
19
+ end
20
+ end
21
+ end
22
+ 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
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatabaseConsistency
4
- VERSION = '0.8.3'
4
+ VERSION = '0.8.8'
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.3
4
+ version: 0.8.8
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-06-24 00:00:00.000000000 Z
11
+ date: 2020-09-24 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_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,10 @@ 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
152
+ - lib/database_consistency/errors.rb
148
153
  - lib/database_consistency/helper.rb
149
154
  - lib/database_consistency/processors/associations_processor.rb
150
155
  - lib/database_consistency/processors/base_processor.rb
@@ -175,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
180
  - !ruby/object:Gem::Version
176
181
  version: '0'
177
182
  requirements: []
178
- rubygems_version: 3.1.2
183
+ rubygems_version: 3.0.8
179
184
  signing_key:
180
185
  specification_version: 4
181
186
  summary: Provide an easy way to check the consistency of the database constraints