db_validator 0.2.0 → 0.3.0
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 +4 -4
- data/lib/db_validator/cli.rb +95 -24
- data/lib/db_validator/configuration.rb +2 -1
- data/lib/db_validator/formatters/json_formatter.rb +44 -0
- data/lib/db_validator/reporter.rb +24 -19
- data/lib/db_validator/validator.rb +64 -25
- data/lib/db_validator/version.rb +1 -1
- data/lib/generators/db_validator/templates/initializer.rb +3 -3
- data/lib/tasks/db_validator_tasks.rake +63 -9
- data/readme.md +49 -14
- metadata +13 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 527983d42b2851e593a40202407ff01d8b82cb13c97741239307d21a12416567
|
4
|
+
data.tar.gz: 389a234732560ec7f0fcf1ba9c0bb995d9552a842719d75f20569b9d7f86818e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4cec830738a0de711477620603757c2fade101075d64a9cbf824ecef806a994c7e0da655add3f495e2ac828a15a3e2b7db0143e32718a6aebbfa3fa3911fc70
|
7
|
+
data.tar.gz: e3a6d02dd6ce60e17e0ecdf5a255d0fe9886fc7533e0ad8c7334c0f3d0c03970c0aa4b01bdd90fa8342c3cb4225816b3a3658908e2e23e2ccfae2aa8fd1fc286
|
data/lib/db_validator/cli.rb
CHANGED
@@ -3,11 +3,30 @@
|
|
3
3
|
require "tty-prompt"
|
4
4
|
require "tty-box"
|
5
5
|
require "tty-spinner"
|
6
|
+
require "optparse"
|
7
|
+
require "logger"
|
6
8
|
|
7
9
|
module DbValidator
|
8
10
|
class CLI
|
9
11
|
def initialize
|
10
12
|
@prompt = TTY::Prompt.new
|
13
|
+
@options = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
if ARGV.empty?
|
18
|
+
interactive_mode
|
19
|
+
else
|
20
|
+
parse_command_line_args
|
21
|
+
validate_with_options
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def display_progress(message)
|
26
|
+
spinner = TTY::Spinner.new("[:spinner] #{message}", format: :dots)
|
27
|
+
spinner.auto_spin
|
28
|
+
yield if block_given?
|
29
|
+
spinner.success
|
11
30
|
end
|
12
31
|
|
13
32
|
def select_models(available_models)
|
@@ -40,37 +59,89 @@ module DbValidator
|
|
40
59
|
end
|
41
60
|
end
|
42
61
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
62
|
+
def parse_command_line_args # rubocop:disable Metrics/AbcSize
|
63
|
+
args = ARGV.join(" ").split(/\s+/)
|
64
|
+
args.each do |arg|
|
65
|
+
key, value = arg.split("=")
|
66
|
+
case key
|
67
|
+
when "models"
|
68
|
+
@options[:only_models] = value.split(",").map(&:strip).map(&:classify)
|
69
|
+
when "limit"
|
70
|
+
@options[:limit] = value.to_i
|
71
|
+
when "format"
|
72
|
+
@options[:report_format] = value.to_sym
|
73
|
+
when "show_records"
|
74
|
+
@options[:show_records] = value.to_sym
|
51
75
|
end
|
52
|
-
|
76
|
+
end
|
77
|
+
end
|
53
78
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
79
|
+
def validate_with_options
|
80
|
+
load_rails
|
81
|
+
configure_validator(@options[:only_models], @options)
|
82
|
+
validator = DbValidator::Validator.new
|
83
|
+
report = validator.validate_all
|
84
|
+
Rails.logger.debug { "\n#{report}" }
|
85
|
+
end
|
59
86
|
|
60
|
-
|
87
|
+
def interactive_mode
|
88
|
+
load_rails
|
89
|
+
display_header
|
90
|
+
|
91
|
+
display_progress("Loading models") do
|
92
|
+
Rails.application.eager_load!
|
61
93
|
end
|
62
94
|
|
63
|
-
|
95
|
+
available_models = ActiveRecord::Base.descendants
|
96
|
+
.reject(&:abstract_class?)
|
97
|
+
.select(&:table_exists?)
|
98
|
+
.map(&:name)
|
99
|
+
.sort
|
100
|
+
|
101
|
+
if available_models.empty?
|
102
|
+
Rails.logger.debug "No models found in the application."
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
|
106
|
+
selected_models = select_models(available_models)
|
107
|
+
options = configure_options
|
108
|
+
|
109
|
+
configure_validator(selected_models, options)
|
110
|
+
validator = DbValidator::Validator.new
|
111
|
+
report = validator.validate_all
|
112
|
+
Rails.logger.debug { "\n#{report}" }
|
64
113
|
end
|
65
114
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
115
|
+
def load_rails
|
116
|
+
require File.expand_path("config/environment", Dir.pwd)
|
117
|
+
rescue LoadError
|
118
|
+
Rails.logger.debug "Error: Rails application not found. Please run this command from your Rails application root."
|
119
|
+
exit 1
|
71
120
|
end
|
72
121
|
|
73
|
-
|
122
|
+
def configure_validator(models = nil, options = {})
|
123
|
+
config = DbValidator.configuration
|
124
|
+
config.only_models = models if models
|
125
|
+
config.limit = options[:limit] if options[:limit]
|
126
|
+
config.batch_size = options[:batch_size] if options[:batch_size]
|
127
|
+
config.report_format = options[:format] if options[:format]
|
128
|
+
config.show_records = options[:show_records] if options[:show_records]
|
129
|
+
end
|
130
|
+
|
131
|
+
def configure_options
|
132
|
+
options = {}
|
133
|
+
|
134
|
+
@prompt.say("\n")
|
135
|
+
limit_input = @prompt.ask("Enter record limit (leave blank for no limit):") do |q|
|
136
|
+
q.validate(/^\d*$/, "Please enter a valid number")
|
137
|
+
q.convert(:int, nil)
|
138
|
+
end
|
139
|
+
options[:limit] = limit_input if limit_input.present?
|
140
|
+
|
141
|
+
options[:format] = @prompt.select("Select report format:", %w[text json], default: "text")
|
142
|
+
|
143
|
+
options
|
144
|
+
end
|
74
145
|
|
75
146
|
def display_header
|
76
147
|
title = TTY::Box.frame(
|
@@ -85,7 +156,7 @@ module DbValidator
|
|
85
156
|
}
|
86
157
|
}
|
87
158
|
)
|
88
|
-
|
159
|
+
Rails.logger.debug title
|
89
160
|
end
|
90
161
|
end
|
91
|
-
end
|
162
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module DbValidator
|
4
4
|
class Configuration
|
5
|
-
attr_accessor :only_models, :ignored_models, :ignored_attributes, :batch_size, :report_format, :limit
|
5
|
+
attr_accessor :only_models, :ignored_models, :ignored_attributes, :batch_size, :report_format, :limit, :show_records
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
@only_models = []
|
@@ -11,6 +11,7 @@ module DbValidator
|
|
11
11
|
@batch_size = 1000
|
12
12
|
@report_format = :text
|
13
13
|
@limit = nil
|
14
|
+
@show_records = true
|
14
15
|
end
|
15
16
|
|
16
17
|
def only_models=(models)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module DbValidator
|
7
|
+
module Formatters
|
8
|
+
class JsonFormatter
|
9
|
+
def initialize(invalid_records)
|
10
|
+
@invalid_records = invalid_records
|
11
|
+
end
|
12
|
+
|
13
|
+
def format
|
14
|
+
formatted_data = @invalid_records.group_by { |r| r[:model] }.transform_values do |records|
|
15
|
+
{
|
16
|
+
error_count: records.length,
|
17
|
+
records: records.map { |r| format_record(r) }
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
save_to_file(formatted_data)
|
22
|
+
formatted_data.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def format_record(record)
|
28
|
+
{
|
29
|
+
id: record[:id],
|
30
|
+
errors: record[:errors]
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def save_to_file(data)
|
35
|
+
FileUtils.mkdir_p("db_validator_reports")
|
36
|
+
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
|
37
|
+
filename = "db_validator_reports/validation_report_#{timestamp}.json"
|
38
|
+
|
39
|
+
File.write(filename, JSON.pretty_generate(data))
|
40
|
+
Rails.logger.info "JSON report saved to #{filename}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "tty-box"
|
4
4
|
require "tty-spinner"
|
5
|
+
require "db_validator/formatters/json_formatter"
|
5
6
|
|
6
7
|
module DbValidator
|
7
8
|
class Reporter
|
@@ -31,7 +32,7 @@ module DbValidator
|
|
31
32
|
def generate_report
|
32
33
|
case DbValidator.configuration.report_format
|
33
34
|
when :json
|
34
|
-
|
35
|
+
Formatters::JsonFormatter.new(@invalid_records).format
|
35
36
|
else
|
36
37
|
generate_text_report
|
37
38
|
end
|
@@ -41,8 +42,7 @@ module DbValidator
|
|
41
42
|
|
42
43
|
def format_value(value)
|
43
44
|
case value
|
44
|
-
when true, false
|
45
|
-
when Symbol
|
45
|
+
when true, false, Symbol
|
46
46
|
value.to_s
|
47
47
|
when String
|
48
48
|
"\"#{value}\""
|
@@ -55,7 +55,7 @@ module DbValidator
|
|
55
55
|
|
56
56
|
def generate_text_report
|
57
57
|
report = StringIO.new
|
58
|
-
|
58
|
+
|
59
59
|
title_box = TTY::Box.frame(
|
60
60
|
width: 50,
|
61
61
|
align: :center,
|
@@ -70,46 +70,51 @@ module DbValidator
|
|
70
70
|
) do
|
71
71
|
"Database Validation Report"
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
report.puts title_box
|
75
75
|
report.puts
|
76
76
|
|
77
77
|
if @invalid_records.empty?
|
78
|
-
report.puts "No invalid records found."
|
78
|
+
report.puts "No invalid records found."
|
79
79
|
else
|
80
|
-
|
80
|
+
is_plural = @invalid_records.count > 1
|
81
|
+
report.puts "Found #{@invalid_records.count} invalid #{is_plural ? 'records' : 'record'} across #{@invalid_records.group_by do |r|
|
82
|
+
r[:model]
|
83
|
+
end.keys.count} #{is_plural ? 'models' : 'model'}"
|
81
84
|
report.puts
|
82
85
|
|
83
86
|
@invalid_records.group_by { |r| r[:model] }.each do |model, records|
|
84
|
-
report.puts "#{model}: #{records.count} invalid records
|
87
|
+
report.puts "#{model}: #{records.count} invalid #{records.count == 1 ? 'record' : 'records'}"
|
88
|
+
|
89
|
+
next if DbValidator.configuration.show_records == false
|
90
|
+
|
85
91
|
report.puts
|
86
92
|
|
87
93
|
records.each do |record|
|
88
94
|
record_obj = record[:model].constantize.find_by(id: record[:id])
|
89
|
-
next unless record_obj
|
90
95
|
|
91
96
|
info = ["ID: #{record[:id]}"]
|
92
|
-
|
93
|
-
|
97
|
+
if record_obj.respond_to?(:created_at)
|
98
|
+
info << "Created: #{record_obj.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
|
99
|
+
end
|
100
|
+
if record_obj.respond_to?(:updated_at)
|
101
|
+
info << "Updated: #{record_obj.updated_at.strftime('%Y-%m-%d %H:%M:%S')}"
|
102
|
+
end
|
94
103
|
info << "Name: #{record_obj.name}" if record_obj.respond_to?(:name)
|
95
104
|
info << "Title: #{record_obj.title}" if record_obj.respond_to?(:title)
|
96
|
-
|
97
|
-
report.puts " #{info.join(' | ')}"
|
105
|
+
|
106
|
+
report.puts " #{info.join(' | ')}"
|
98
107
|
record[:errors].each do |error|
|
99
|
-
report.puts " ⚠️ #{error}"
|
108
|
+
report.puts " ⚠️ #{error}"
|
100
109
|
end
|
101
110
|
report.puts
|
102
111
|
end
|
103
|
-
|
112
|
+
|
104
113
|
report.puts
|
105
114
|
end
|
106
115
|
end
|
107
116
|
|
108
117
|
report.string
|
109
118
|
end
|
110
|
-
|
111
|
-
def generate_json_report
|
112
|
-
@invalid_records.to_json
|
113
|
-
end
|
114
119
|
end
|
115
120
|
end
|
@@ -1,33 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "ruby-progressbar"
|
4
|
-
require "colorize"
|
5
4
|
|
6
5
|
module DbValidator
|
7
6
|
class Validator
|
8
7
|
attr_reader :reporter
|
9
8
|
|
10
9
|
def initialize(options = {})
|
11
|
-
@options = options
|
12
|
-
@reporter = Reporter.new
|
13
10
|
configure_from_options(options)
|
11
|
+
@reporter = Reporter.new
|
14
12
|
end
|
15
13
|
|
16
14
|
def validate_all
|
17
|
-
|
15
|
+
models = get_models_to_validate
|
16
|
+
invalid_count = 0
|
18
17
|
|
19
|
-
models
|
20
|
-
|
21
|
-
|
18
|
+
models.each do |model|
|
19
|
+
model_count = validate_model(model)
|
20
|
+
invalid_count += model_count if model_count
|
21
|
+
end
|
22
22
|
|
23
|
-
if
|
24
|
-
Rails.logger.debug "
|
25
|
-
|
23
|
+
if invalid_count.zero?
|
24
|
+
Rails.logger.debug "\nValidation passed! All records are valid."
|
25
|
+
else
|
26
|
+
total_records = models.sum(&:count)
|
27
|
+
is_plural = invalid_count > 1
|
28
|
+
Rails.logger.debug do
|
29
|
+
"\nFound #{invalid_count} invalid #{is_plural ? 'records' : 'record'} out of #{total_records} total #{is_plural ? 'records' : 'record'}."
|
30
|
+
end
|
26
31
|
end
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
33
|
+
@reporter.generate_report
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_test_model(model_name)
|
37
|
+
model = model_name.constantize
|
38
|
+
scope = model.all
|
39
|
+
scope = scope.limit(DbValidator.configuration.limit) if DbValidator.configuration.limit
|
40
|
+
|
41
|
+
total_count = scope.count
|
42
|
+
progress_bar = create_progress_bar("Testing #{model.name}", total_count)
|
43
|
+
invalid_count = 0
|
44
|
+
|
45
|
+
begin
|
46
|
+
scope.find_each(batch_size: DbValidator.configuration.batch_size) do |record|
|
47
|
+
invalid_count += 1 unless validate_record(record)
|
48
|
+
progress_bar.increment
|
49
|
+
end
|
50
|
+
rescue StandardError => e
|
51
|
+
Rails.logger.debug { "Error validating #{model.name}: #{e.message}" }
|
52
|
+
end
|
53
|
+
|
54
|
+
if invalid_count.zero?
|
55
|
+
Rails.logger.debug "\nValidation rule passed! All records would be valid."
|
56
|
+
else
|
57
|
+
Rails.logger.debug do
|
58
|
+
"\nFound #{invalid_count} records that would become invalid out of #{total_count} total records."
|
59
|
+
end
|
31
60
|
end
|
32
61
|
|
33
62
|
@reporter.generate_report
|
@@ -38,13 +67,11 @@ module DbValidator
|
|
38
67
|
def configure_from_options(options)
|
39
68
|
return unless options.is_a?(Hash)
|
40
69
|
|
41
|
-
if options[:only_models]
|
42
|
-
DbValidator.configuration.only_models = Array(options[:only_models])
|
43
|
-
end
|
44
|
-
|
70
|
+
DbValidator.configuration.only_models = Array(options[:only_models]) if options[:only_models]
|
45
71
|
DbValidator.configuration.limit = options[:limit] if options[:limit]
|
46
72
|
DbValidator.configuration.batch_size = options[:batch_size] if options[:batch_size]
|
47
73
|
DbValidator.configuration.report_format = options[:report_format] if options[:report_format]
|
74
|
+
DbValidator.configuration.show_records = options[:show_records] if options[:show_records]
|
48
75
|
end
|
49
76
|
|
50
77
|
def find_all_models
|
@@ -59,37 +86,42 @@ module DbValidator
|
|
59
86
|
|
60
87
|
config = DbValidator.configuration
|
61
88
|
model_name = model.name.downcase
|
62
|
-
|
89
|
+
|
63
90
|
if config.only_models.any?
|
64
|
-
return config.only_models.map(&:downcase).include?(model_name)
|
91
|
+
return config.only_models.map(&:downcase).include?(model_name) ||
|
92
|
+
config.only_models.map(&:downcase).include?(model_name.singularize) ||
|
93
|
+
config.only_models.map(&:downcase).include?(model_name.pluralize)
|
65
94
|
end
|
66
95
|
|
67
|
-
|
96
|
+
config.ignored_models.map(&:downcase).exclude?(model_name)
|
68
97
|
end
|
69
98
|
|
70
99
|
def validate_model(model)
|
71
100
|
config = DbValidator.configuration
|
72
|
-
batch_size = config.batch_size ||
|
101
|
+
batch_size = config.batch_size || 100
|
73
102
|
limit = config.limit
|
74
103
|
|
75
104
|
scope = model.all
|
76
105
|
scope = scope.limit(limit) if limit
|
77
106
|
|
78
107
|
total_count = scope.count
|
79
|
-
return if total_count.zero?
|
108
|
+
return 0 if total_count.zero?
|
80
109
|
|
81
110
|
progress_bar = create_progress_bar(model.name, total_count)
|
111
|
+
invalid_count = 0
|
82
112
|
|
83
113
|
begin
|
84
114
|
scope.find_in_batches(batch_size: batch_size) do |batch|
|
85
115
|
batch.each do |record|
|
86
|
-
validate_record(record)
|
116
|
+
invalid_count += 1 unless validate_record(record)
|
87
117
|
progress_bar.increment
|
88
118
|
end
|
89
119
|
end
|
90
120
|
rescue StandardError => e
|
91
|
-
Rails.logger.debug "Error validating #{model.name}: #{e.message}"
|
121
|
+
Rails.logger.debug { "Error validating #{model.name}: #{e.message}" }
|
92
122
|
end
|
123
|
+
|
124
|
+
invalid_count
|
93
125
|
end
|
94
126
|
|
95
127
|
def create_progress_bar(model_name, total)
|
@@ -102,8 +134,15 @@ module DbValidator
|
|
102
134
|
end
|
103
135
|
|
104
136
|
def validate_record(record)
|
105
|
-
return if record.valid?
|
137
|
+
return true if record.valid?
|
138
|
+
|
106
139
|
@reporter.add_invalid_record(record)
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_models_to_validate
|
144
|
+
models = find_all_models
|
145
|
+
models.select { |model| should_validate_model?(model) }
|
107
146
|
end
|
108
147
|
end
|
109
148
|
end
|
data/lib/db_validator/version.rb
CHANGED
@@ -13,9 +13,9 @@ DbValidator.configure do |config|
|
|
13
13
|
# "Post" => ["cached_votes"]
|
14
14
|
# }
|
15
15
|
|
16
|
-
# Set the batch size for processing records (default: 1000)
|
17
|
-
# config.batch_size = 100
|
18
|
-
|
19
16
|
# Set the report format (:text or :json)
|
20
17
|
# config.report_format = :text
|
18
|
+
|
19
|
+
# Show detailed record information in reports
|
20
|
+
# config.show_records = true
|
21
21
|
end
|
@@ -1,29 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
namespace :db_validator do
|
4
|
-
desc "Validate
|
4
|
+
desc "Validate records in the database"
|
5
5
|
task validate: :environment do
|
6
6
|
cli = DbValidator::CLI.new
|
7
7
|
|
8
|
-
has_any_args = ENV["models"] || ENV["limit"] || ENV["
|
8
|
+
has_any_args = ENV["models"].present? || ENV["limit"].present? || ENV["format"].present? || ENV["show_records"].present?
|
9
9
|
|
10
10
|
if has_any_args
|
11
|
-
|
11
|
+
if ENV["models"].present?
|
12
|
+
models = ENV["models"].split(",").map(&:strip).map(&:classify)
|
13
|
+
DbValidator.configuration.only_models = models
|
14
|
+
end
|
12
15
|
|
13
|
-
DbValidator.configuration.only_models = models
|
14
16
|
DbValidator.configuration.limit = ENV["limit"].to_i if ENV["limit"].present?
|
15
|
-
DbValidator.configuration.batch_size = ENV["batch_size"].to_i if ENV["batch_size"].present?
|
16
17
|
DbValidator.configuration.report_format = ENV["format"].to_sym if ENV["format"].present?
|
18
|
+
DbValidator.configuration.show_records = ENV["show_records"] != "false" if ENV["show_records"].present?
|
17
19
|
else
|
18
20
|
cli.display_progress("Loading models") do
|
19
21
|
Rails.application.eager_load!
|
20
22
|
end
|
21
23
|
|
22
24
|
available_models = ActiveRecord::Base.descendants
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
.reject(&:abstract_class?)
|
26
|
+
.select(&:table_exists?)
|
27
|
+
.map(&:name)
|
28
|
+
.sort
|
27
29
|
|
28
30
|
selected_models = cli.select_models(available_models)
|
29
31
|
options = cli.configure_options
|
@@ -32,10 +34,62 @@ namespace :db_validator do
|
|
32
34
|
DbValidator.configuration.limit = options[:limit] if options[:limit].present?
|
33
35
|
DbValidator.configuration.batch_size = options[:batch_size] if options[:batch_size].present?
|
34
36
|
DbValidator.configuration.report_format = options[:format].to_sym if options[:format].present?
|
37
|
+
DbValidator.configuration.show_records = options[:show_records] if options[:show_records].present?
|
35
38
|
end
|
36
39
|
|
37
40
|
validator = DbValidator::Validator.new
|
38
41
|
report = validator.validate_all
|
39
42
|
puts "\n#{report}"
|
40
43
|
end
|
44
|
+
|
45
|
+
desc "Test validation rules on existing records"
|
46
|
+
task test: :environment do
|
47
|
+
unless ENV["model"] && ENV["rule"]
|
48
|
+
puts "Usage: rake db_validator:test model=user rule='validates :field, presence: true' [show_records=false] [limit=1000] [format=json]"
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
model_name = ENV.fetch("model").classify
|
53
|
+
validation_rule = ENV.fetch("rule", nil)
|
54
|
+
|
55
|
+
# Configure options
|
56
|
+
DbValidator.configuration.show_records = ENV["show_records"] != "false" if ENV["show_records"].present?
|
57
|
+
DbValidator.configuration.limit = ENV["limit"].to_i if ENV["limit"].present?
|
58
|
+
DbValidator.configuration.report_format = ENV["format"].to_sym if ENV["format"].present?
|
59
|
+
|
60
|
+
begin
|
61
|
+
base_model = model_name.constantize
|
62
|
+
# Extract attribute name from validation rule
|
63
|
+
attribute_match = validation_rule.match(/validates\s+:(\w+)/)
|
64
|
+
if attribute_match
|
65
|
+
attribute_name = attribute_match[1]
|
66
|
+
unless base_model.column_names.include?(attribute_name) || base_model.method_defined?(attribute_name)
|
67
|
+
puts "\n❌ Error: Attribute '#{attribute_name}' does not exist for model '#{model_name}'"
|
68
|
+
puts "Available columns: #{base_model.column_names.join(', ')}"
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Create temporary subclass with new validation
|
74
|
+
temp_model = Class.new(base_model) do
|
75
|
+
self.table_name = base_model.table_name
|
76
|
+
class_eval(validation_rule)
|
77
|
+
end
|
78
|
+
|
79
|
+
Object.const_set("Temporary#{model_name}", temp_model)
|
80
|
+
|
81
|
+
validator = DbValidator::Validator.new
|
82
|
+
report = validator.validate_test_model("Temporary#{model_name}")
|
83
|
+
puts "\n#{report}"
|
84
|
+
rescue NameError
|
85
|
+
puts "\n❌ Error: Model '#{model_name}' not found"
|
86
|
+
exit 1
|
87
|
+
rescue SyntaxError => e
|
88
|
+
puts "\n❌ Error: Invalid validation rule syntax"
|
89
|
+
puts e.message
|
90
|
+
exit 1
|
91
|
+
ensure
|
92
|
+
Object.send(:remove_const, "Temporary#{model_name}") if Object.const_defined?("Temporary#{model_name}")
|
93
|
+
end
|
94
|
+
end
|
41
95
|
end
|
data/readme.md
CHANGED
@@ -26,6 +26,7 @@ $ bundle install
|
|
26
26
|
The simplest way to run validation is using the provided rake task:
|
27
27
|
|
28
28
|
#### Validate models in interactive mode
|
29
|
+
|
29
30
|
<img width="798" alt="Screenshot 2024-11-07 at 21 50 57" src="https://github.com/user-attachments/assets/33fbdb8b-b8ec-4284-9313-c1eeaf2eab2d">
|
30
31
|
|
31
32
|
```bash
|
@@ -52,12 +53,37 @@ $ rake db_validator:validate limit=1000
|
|
52
53
|
$ rake db_validator:validate format=json
|
53
54
|
```
|
54
55
|
|
55
|
-
###
|
56
|
+
### Test Mode
|
56
57
|
|
57
|
-
|
58
|
+
You can test new validation rules before applying them to your models:
|
58
59
|
|
59
60
|
```bash
|
60
|
-
$ rake db_validator:
|
61
|
+
$ rake db_validator:test model=User rule='validates :name, presence: true'
|
62
|
+
```
|
63
|
+
|
64
|
+
#### Testing Email Format Validation
|
65
|
+
|
66
|
+
Here's an example of testing email format validation rules:
|
67
|
+
|
68
|
+
```bash
|
69
|
+
# Testing invalid email format (without @)
|
70
|
+
$ rake db_validator:test model=User rule='validates :email, format: { without: /@/, message: "must contain @" }'
|
71
|
+
|
72
|
+
Found 100 records that would become invalid out of 100 total records.
|
73
|
+
|
74
|
+
# Testing valid email format (with @)
|
75
|
+
$ rake db_validator:test model=User rule='validates :email, format: { with: /@/, message: "must contain @" }'
|
76
|
+
|
77
|
+
No invalid records found.
|
78
|
+
```
|
79
|
+
|
80
|
+
#### Error Handling
|
81
|
+
|
82
|
+
Trying to test a validation rule for a non-existent attribute will return an error:
|
83
|
+
|
84
|
+
```
|
85
|
+
❌ Error: Attribute 'i_dont_exist' does not exist for model 'User'
|
86
|
+
Available columns: id, email, created_at, updated_at, name
|
61
87
|
```
|
62
88
|
|
63
89
|
### Ruby Code
|
@@ -103,17 +129,26 @@ ID: 5
|
|
103
129
|
|
104
130
|
### JSON Format
|
105
131
|
|
132
|
+
The JSON report is saved to a file in the `db_validator_reports` directory.
|
133
|
+
|
106
134
|
```json
|
107
|
-
|
108
|
-
{
|
109
|
-
"
|
110
|
-
"
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
135
|
+
{
|
136
|
+
"User": {
|
137
|
+
"error_count": 2,
|
138
|
+
"records": [
|
139
|
+
{
|
140
|
+
"id": 1,
|
141
|
+
"errors": [
|
142
|
+
"email is invalid (actual value: \"invalid-email\")"
|
143
|
+
]
|
144
|
+
},
|
145
|
+
{
|
146
|
+
"id": 2,
|
147
|
+
"errors": [
|
148
|
+
"name can't be blank (actual value: \"\")"
|
149
|
+
]
|
150
|
+
}
|
151
|
+
]
|
117
152
|
}
|
118
|
-
|
153
|
+
}
|
119
154
|
```
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: db_validator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Krzysztof Duda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
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
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: ruby-progressbar
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,33 +81,33 @@ dependencies:
|
|
95
81
|
- !ruby/object:Gem::Version
|
96
82
|
version: '1.11'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
|
-
name: tty-
|
84
|
+
name: tty-box
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
100
86
|
requirements:
|
101
87
|
- - "~>"
|
102
88
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.
|
89
|
+
version: 0.7.0
|
104
90
|
type: :runtime
|
105
91
|
prerelease: false
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
107
93
|
requirements:
|
108
94
|
- - "~>"
|
109
95
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.
|
96
|
+
version: 0.7.0
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
|
-
name: tty-
|
98
|
+
name: tty-prompt
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
114
100
|
requirements:
|
115
101
|
- - "~>"
|
116
102
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
103
|
+
version: 0.23.1
|
118
104
|
type: :runtime
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
107
|
requirements:
|
122
108
|
- - "~>"
|
123
109
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
110
|
+
version: 0.23.1
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
112
|
name: tty-spinner
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -151,6 +137,7 @@ files:
|
|
151
137
|
- lib/db_validator.rb
|
152
138
|
- lib/db_validator/cli.rb
|
153
139
|
- lib/db_validator/configuration.rb
|
140
|
+
- lib/db_validator/formatters/json_formatter.rb
|
154
141
|
- lib/db_validator/railtie.rb
|
155
142
|
- lib/db_validator/reporter.rb
|
156
143
|
- lib/db_validator/validator.rb
|
@@ -163,6 +150,9 @@ homepage: https://github.com/krzysztoff1/db-validator
|
|
163
150
|
licenses:
|
164
151
|
- MIT
|
165
152
|
metadata:
|
153
|
+
source_code_uri: https://github.com/krzysztoff1/db-validator/
|
154
|
+
documentation_uri: https://github.com/krzysztoff1/db-validator/blob/main/README.md
|
155
|
+
changelog_uri: https://github.com/krzysztoff1/db-validator/blob/main/changelog.md
|
166
156
|
rubygems_mfa_required: 'true'
|
167
157
|
post_install_message:
|
168
158
|
rdoc_options: []
|
@@ -179,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
179
169
|
- !ruby/object:Gem::Version
|
180
170
|
version: '0'
|
181
171
|
requirements: []
|
182
|
-
rubygems_version: 3.5.
|
172
|
+
rubygems_version: 3.5.9
|
183
173
|
signing_key:
|
184
174
|
specification_version: 4
|
185
175
|
summary: DbValidator helps identify invalid records in your Rails application that
|