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 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