db_validator 0.2.0 → 0.3.0

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: 60cba1d36771e30b0bafb5ac354f3d741b3bde0e5f37b267bcff4ebb9dd11bad
4
- data.tar.gz: 1baa177ce745a107d5c064844b74cd8731b5cddd8624f2944c464812c9e136b0
3
+ metadata.gz: 527983d42b2851e593a40202407ff01d8b82cb13c97741239307d21a12416567
4
+ data.tar.gz: 389a234732560ec7f0fcf1ba9c0bb995d9552a842719d75f20569b9d7f86818e
5
5
  SHA512:
6
- metadata.gz: d3e414c08cf3496438f5a61941c579b072dd9a41330133df64bfe48c3e8d205cf3de933f52368622d3cd7cbcea8cea5844982f7fb95bf1eafca59bcb1b6f781a
7
- data.tar.gz: 9a2d9de68775b836aec350ded2b88184c534e3dac4649d6b531e5f5575363f30ee60b1a10a9439cfbd2beb083ea2036a095a17d37c5593d9fe4e730bdb7349fe
6
+ metadata.gz: e4cec830738a0de711477620603757c2fade101075d64a9cbf824ecef806a994c7e0da655add3f495e2ac828a15a3e2b7db0143e32718a6aebbfa3fa3911fc70
7
+ data.tar.gz: e3a6d02dd6ce60e17e0ecdf5a255d0fe9886fc7533e0ad8c7334c0f3d0c03970c0aa4b01bdd90fa8342c3cb4225816b3a3658908e2e23e2ccfae2aa8fd1fc286
@@ -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 configure_options
44
- options = {}
45
-
46
- @prompt.say("\n")
47
- if @prompt.yes?("Would you like to configure additional options?", default: false)
48
- limit_input = @prompt.ask("Enter record limit (leave blank for no limit):") do |q|
49
- q.validate(/^\d*$/, "Please enter a valid number")
50
- q.convert(:int, nil)
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
- options[:limit] = limit_input if limit_input.present?
76
+ end
77
+ end
53
78
 
54
- batch_size = @prompt.ask("Enter batch size:", default: 1000, convert: :int) do |q|
55
- q.validate(/^\d+$/, "Please enter a positive number")
56
- q.messages[:valid?] = "Please enter a positive number"
57
- end
58
- options[:batch_size] = batch_size if batch_size.present?
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
- options[:format] = @prompt.select("Select report format:", %w[text json], default: "text")
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
- options
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 display_progress(message)
67
- spinner = TTY::Spinner.new("[:spinner] #{message}", format: :dots)
68
- spinner.auto_spin
69
- yield if block_given?
70
- spinner.success
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
- private
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
- puts title
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
- generate_json_report
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.".colorize(:green)
78
+ report.puts "No invalid records found."
79
79
  else
80
- report.puts "Found #{@invalid_records.count} invalid records across #{@invalid_records.group_by { |r| r[:model] }.keys.count} models".colorize(:yellow)
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".colorize(:red)
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
- info << "Created: #{record_obj.created_at.strftime('%Y-%m-%d %H:%M:%S')}" if record_obj.respond_to?(:created_at)
93
- info << "Updated: #{record_obj.updated_at.strftime('%Y-%m-%d %H:%M:%S')}" if record_obj.respond_to?(:updated_at)
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(' | ')}".colorize(:white)
105
+
106
+ report.puts " #{info.join(' | ')}"
98
107
  record[:errors].each do |error|
99
- report.puts " ⚠️ #{error}".colorize(:white)
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
- Rails.application.eager_load! if defined?(Rails)
15
+ models = get_models_to_validate
16
+ invalid_count = 0
18
17
 
19
- models = find_all_models
20
- models_to_validate = models.select { |model| should_validate_model?(model) }
21
- total_models = models_to_validate.size
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 models_to_validate.empty?
24
- Rails.logger.debug "No models selected for validation.".colorize(:yellow)
25
- return @reporter.generate_report
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
- models_to_validate.each_with_index do |model, index|
29
- Rails.logger.debug "Validating model #{index + 1}/#{total_models}: #{model.name}".colorize(:cyan)
30
- validate_model(model)
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
- !config.ignored_models.map(&:downcase).include?(model_name)
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 || 1000
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}".colorize(:red)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DbValidator
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -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 all records in the database"
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["batch_size"] || ENV["format"]
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
- models = ENV["models"].split(",").map(&:strip).map(&:downcase).map(&:singularize)
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
- .reject(&:abstract_class?)
24
- .select(&:table_exists?)
25
- .map { |m| m.name }
26
- .sort
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
- ### Interactive Mode
56
+ ### Test Mode
56
57
 
57
- Running the validation task without specifying models will start an interactive mode:
58
+ You can test new validation rules before applying them to your models:
58
59
 
59
60
  ```bash
60
- $ rake db_validator:validate
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
- "model": "User",
110
- "id": 1,
111
- "errors": ["email is invalid (actual value: \"invalid-email\")"]
112
- },
113
- {
114
- "model": "User",
115
- "id": 2,
116
- "errors": ["name can't be blank (actual value: \"\")"]
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.2.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-07 00:00:00.000000000 Z
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-prompt
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.23.1
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.23.1
96
+ version: 0.7.0
111
97
  - !ruby/object:Gem::Dependency
112
- name: tty-box
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.7.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.7.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.16
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