db_validator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d5b1e93e6a494c74779345e004eb7f6050fa5fd4e15cb27846d48fa8390a78ef
4
+ data.tar.gz: 9ab575840ad1cb722db62b40162b527e4793305ed4255b38eb1166ba5e4786fe
5
+ SHA512:
6
+ metadata.gz: 20cc83d66461dff7faed7ec1eee28b4c5459152d723403c15b23718554e5ae208e965acde7b3ee4bb2ba030e19bdd86c1e4e63bd0072e7e2bfbfdd25048647e9
7
+ data.tar.gz: b8e9a4175498cf0f96eec2ed624a9c1be35c6092206bfd7117ecad929f59760236d361cbf524cce37fb603feaafa4ed981f5cbf0308cb0b79a2b2adb2c39a5d5
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ DbValidator.configure do |config|
4
+ config.only_models = %w[Documents Users]
5
+ config.batch_size = 500
6
+ config.limit = 500
7
+ config.model_limits = {
8
+ "documents" => 500,
9
+ "users" => 1000
10
+ }
11
+ config.report_format = :json
12
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DbValidator
4
+ class Configuration
5
+ attr_accessor :only_models, :ignored_models, :ignored_attributes, :batch_size, :report_format, :auto_fix, :limit
6
+
7
+ def initialize
8
+ @only_models = []
9
+ @ignored_models = []
10
+ @ignored_attributes = {}
11
+ @batch_size = 1000
12
+ @report_format = :text
13
+ @auto_fix = false
14
+ @limit = nil
15
+ end
16
+
17
+ def only_models=(models)
18
+ @only_models = models.map(&:downcase)
19
+ end
20
+
21
+ def ignored_models
22
+ @ignored_models ||= []
23
+ end
24
+
25
+ def ignored_models=(models)
26
+ @ignored_models = models.map(&:downcase)
27
+ end
28
+ end
29
+
30
+ class << self
31
+ def configuration
32
+ @configuration ||= Configuration.new
33
+ end
34
+
35
+ def configure
36
+ yield(configuration)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DbValidator
4
+ class Fixer
5
+ def initialize
6
+ @fixed_records = 0
7
+ @failed_fixes = 0
8
+ end
9
+
10
+ def fix_record(record)
11
+ return false unless record.invalid?
12
+
13
+ success = attempt_fix(record)
14
+ if success
15
+ @fixed_records += 1
16
+ else
17
+ @failed_fixes += 1
18
+ end
19
+ success
20
+ end
21
+
22
+ def statistics
23
+ {
24
+ fixed_records: @fixed_records,
25
+ failed_fixes: @failed_fixes
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def attempt_fix(record)
32
+ record.save
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DbValidator
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :db_validator
6
+
7
+ rake_tasks do
8
+ load "tasks/db_validator_tasks.rake"
9
+ end
10
+
11
+ generators do
12
+ require "generators/db_validator/install_generator"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DbValidator
4
+ class Reporter
5
+ def initialize
6
+ @invalid_records = []
7
+ end
8
+
9
+ def add_invalid_record(record)
10
+ enhanced_errors = record.errors.map do |error|
11
+ field_value = record.send(error.attribute)
12
+ message = error.message
13
+
14
+ if error.options[:in].present?
15
+ "#{error.attribute} #{message} (allowed values: #{error.options[:in].join(', ')}, actual value: #{field_value.inspect})"
16
+ else
17
+ "#{error.attribute} #{message} (actual value: #{field_value.inspect})"
18
+ end
19
+ end
20
+
21
+ @invalid_records << {
22
+ model: record.class.name,
23
+ id: record.id,
24
+ errors: enhanced_errors
25
+ }
26
+ end
27
+
28
+ def generate_report
29
+ case DbValidator.configuration.report_format
30
+ when :json
31
+ generate_json_report
32
+ else
33
+ generate_text_report
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def generate_text_report
40
+ report = StringIO.new
41
+ report.puts "DbValidator Report"
42
+ report.puts "=================="
43
+ report.puts
44
+
45
+ if @invalid_records.empty?
46
+ report.puts "No invalid records found."
47
+ else
48
+ report.puts "Found invalid records:"
49
+ report.puts
50
+
51
+ @invalid_records.group_by { |r| r[:model] }.each do |model, records|
52
+ report.puts "#{model}: #{records.count} invalid records"
53
+ records.each do |record|
54
+ report.puts " ID: #{record[:id]}"
55
+ record[:errors].each do |error|
56
+ report.puts " - #{error}"
57
+ end
58
+ end
59
+ report.puts
60
+ end
61
+ end
62
+
63
+ report.string
64
+ end
65
+
66
+ def generate_json_report
67
+ @invalid_records.to_json
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-progressbar"
4
+ require "colorize"
5
+
6
+ module DbValidator
7
+ class Validator
8
+ def initialize(options = {})
9
+ @options = options
10
+ @reporter = Reporter.new
11
+ @fixer = Fixer.new if DbValidator.configuration.auto_fix
12
+ end
13
+
14
+ def validate_all
15
+ Rails.application.eager_load! if defined?(Rails)
16
+
17
+ models = find_all_models
18
+
19
+ models_to_validate = models.select { |model| should_validate_model?(model) }
20
+ total_models = models_to_validate.size
21
+
22
+ models_to_validate.each_with_index do |model, index|
23
+ Rails.logger.debug "Validating model #{index + 1}/#{total_models}: #{model.name}".colorize(:cyan)
24
+ validate_model(model)
25
+ end
26
+
27
+ @reporter.generate_report
28
+ end
29
+
30
+ private
31
+
32
+ def find_all_models
33
+ # Include all classes inheriting from ActiveRecord::Base
34
+ ObjectSpace.each_object(Class).select do |klass|
35
+ klass < ActiveRecord::Base
36
+ end
37
+ end
38
+
39
+ def should_validate_model?(model)
40
+ return false if model.abstract_class?
41
+ return false unless model.table_exists?
42
+
43
+ config = DbValidator.configuration
44
+ model_name = model.name.downcase
45
+ return config.only_models.include?(model_name) if config.only_models.any?
46
+
47
+ config.ignored_models.exclude?(model_name)
48
+ end
49
+
50
+ def validate_model(model)
51
+ limit = DbValidator.configuration.limit
52
+ total_records = limit || model.count
53
+
54
+ if total_records.zero?
55
+ Rails.logger.debug { "No records to validate for model #{model.name}." }
56
+ return
57
+ end
58
+
59
+ processed_records = 0
60
+
61
+ query = model.all
62
+ query = query.limit(limit) if limit
63
+
64
+ query.find_in_batches(batch_size: DbValidator.configuration.batch_size) do |batch|
65
+ batch.each do |record|
66
+ validate_record(record)
67
+ processed_records += 1
68
+ if (processed_records % 100).zero? || processed_records == total_records
69
+ Rails.logger.debug "Validated #{processed_records}/#{total_records} records for model #{model.name}".colorize(:green)
70
+ end
71
+ end
72
+ rescue StandardError => e
73
+ Rails.logger.debug "Error validating #{model.name}: #{e.message}".colorize(:red)
74
+ end
75
+ rescue ActiveRecord::StatementInvalid => e
76
+ Rails.logger.debug { "Skipping validation for #{model.name}: #{e.message}" }
77
+ end
78
+
79
+ def validate_record(record)
80
+ return if record.valid?
81
+
82
+ @reporter.add_invalid_record(record)
83
+ @fixer&.attempt_fix(record)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DbValidator
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "db_validator/version"
5
+ require "db_validator/configuration"
6
+ require "db_validator/validator"
7
+ require "db_validator/reporter"
8
+ require "db_validator/fixer"
9
+
10
+ module DbValidator
11
+ class Error < StandardError; end
12
+
13
+ class << self
14
+ def validate(options = {})
15
+ validator = Validator.new(options)
16
+ validator.validate_all
17
+ end
18
+ end
19
+ end
20
+
21
+ require "db_validator/railtie" if defined?(Rails)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/base"
5
+
6
+ module DbValidator
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ def create_initializer
12
+ template "initializer.rb", "config/initializers/db_validator.rb"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ DbValidator.configure do |config|
4
+ # Specify specific models to validate
5
+ config.only_models = %w[User Post]
6
+
7
+ # Ignore specific models from validation
8
+ # config.ignored_models = ["AdminUser"]
9
+
10
+ # Ignore specific attributes for specific models
11
+ # config.ignored_attributes = {
12
+ # "User" => ["encrypted_password", "reset_password_token"],
13
+ # "Post" => ["cached_votes"]
14
+ # }
15
+
16
+ # Set the batch size for processing records (default: 1000)
17
+ # config.batch_size = 1000
18
+
19
+ # Set the report format (:text or :json)
20
+ # config.report_format = :text
21
+
22
+ # Enable automatic fixing of simple validation errors
23
+ # config.auto_fix = false
24
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :db_validator do
4
+ desc "Validate all records in the database"
5
+ task validate: :environment do
6
+ if ENV["models"]
7
+ models = ENV["models"].split(",").map(&:strip).map(&:downcase)
8
+ DbValidator.configuration.only_models = models
9
+ end
10
+
11
+ DbValidator.configuration.limit = ENV["limit"].to_i if ENV["limit"]
12
+
13
+ DbValidator.configuration.report_format = ENV["format"].to_sym if ENV["format"]
14
+
15
+ validator = DbValidator::Validator.new
16
+ report = validator.validate_all
17
+ puts report
18
+ end
19
+ end
data/readme.md ADDED
@@ -0,0 +1,111 @@
1
+ # DbValidator
2
+
3
+ DbValidator helps identify invalid records in your Rails application that don't meet model validation requirements.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'db_validator'
11
+ ```
12
+
13
+ Then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself:
20
+
21
+ ```bash
22
+ $ gem install db_validator
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Rake Task
28
+
29
+ The simplest way to run validation is using the provided rake task:
30
+
31
+ #### Validate all models
32
+
33
+ ```bash
34
+ $ rake db_validator:validate
35
+ ```
36
+
37
+ #### Validate specific models
38
+
39
+ ```bash
40
+ $ rake db_validator:validate models=user,post
41
+ ```
42
+
43
+ #### Limit the number of records to validate
44
+
45
+ ```bash
46
+ $ rake db_validator:validate limit=1000
47
+ ```
48
+
49
+ #### Generate JSON report
50
+
51
+ ```bash
52
+ $ rake db_validator:validate format=json
53
+ ```
54
+
55
+ ### Ruby Code
56
+
57
+ You can also run validation from your Ruby code:
58
+
59
+ #### Validate all models
60
+
61
+ ```ruby
62
+ report = DbValidator.validate
63
+ ```
64
+
65
+ #### Validate with options
66
+
67
+ ```ruby
68
+ report = DbValidator.validate(
69
+ only_models: ['User', 'Post'],
70
+ limit: 1000,
71
+ report_format: :json
72
+ )
73
+ ```
74
+
75
+ ## Report Format
76
+
77
+ ### Text Format (Default)
78
+
79
+ ```
80
+ DbValidator Report
81
+ ==================
82
+ Found invalid records:
83
+
84
+ User: 2 invalid records
85
+ ID: 1
86
+ email is invalid (actual value: "invalid-email")
87
+ ID: 2
88
+ name can't be blank (actual value: "")
89
+
90
+ Post: 1 invalid record
91
+ ID: 5
92
+ title can't be blank (actual value: "")
93
+ category is not included in the list (allowed values: news, blog, actual value: "invalid")
94
+ ```
95
+
96
+ ### JSON Format
97
+
98
+ ```json
99
+ [
100
+ {
101
+ "model": "User",
102
+ "id": 1,
103
+ "errors": ["email is invalid (actual value: \"invalid-email\")"]
104
+ },
105
+ {
106
+ "model": "User",
107
+ "id": 2,
108
+ "errors": ["name can't be blank (actual value: \"\")"]
109
+ }
110
+ ]
111
+ ```
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: db_validator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Krzysztof Duda
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: database_cleaner-active_record
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '6.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '6.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: colorize
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.8.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.8.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: ruby-progressbar
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.11'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.11'
97
+ description: A comprehensive solution for validating existing database records in
98
+ Rails applications
99
+ email:
100
+ - duda_krzysztof@outlook.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - config/initializers/db_validator.rb
106
+ - lib/db_validator.rb
107
+ - lib/db_validator/configuration.rb
108
+ - lib/db_validator/fixer.rb
109
+ - lib/db_validator/railtie.rb
110
+ - lib/db_validator/reporter.rb
111
+ - lib/db_validator/validator.rb
112
+ - lib/db_validator/version.rb
113
+ - lib/generators/db_validator/install_generator.rb
114
+ - lib/generators/db_validator/templates/initializer.rb
115
+ - lib/tasks/db_validator_tasks.rake
116
+ - readme.md
117
+ homepage: https://github.com/yourusername/db_validator
118
+ licenses:
119
+ - MIT
120
+ metadata:
121
+ rubygems_mfa_required: 'true'
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 2.7.0
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.5.16
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Database-wide validation for Rails applications
141
+ test_files: []