recheck-rails 0.0.1 → 0.5.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: 4c998b503885abb627269eed14197f92cbf88d92ff7b5596c4a4da887f955044
4
- data.tar.gz: 4a1e58a4342c91564a700af6e003c25be44ae5151f23a005186dd49bbdbf3b8a
3
+ metadata.gz: 4e1a5a41923f7278f00931872e42c235c2cf761e0cbed4a8852b60eaa115765f
4
+ data.tar.gz: 7333424af6b70b3f8b77a948047a3f96c00e97a69894f5322ffc0373fd4182f3
5
5
  SHA512:
6
- metadata.gz: 0c02fbddaa6e0ea3549fe35d4ab0d8acff2bf46366045f2dd3c007efbeb905e6c000b6dde3524e8e94bfd06f9e358471a7df07699843e7d4e40bdff359069b16
7
- data.tar.gz: cee30efbb2c65713669e8e7a01ff37efec5f7e930ce3580bc9ba91a0bbaafa39368fd17949b77ee46e8f92eaad9697de95cb823aa0f892e45afedb88118a7df1
6
+ metadata.gz: f0829c52c8643d24a2c5991ffc14c4bea2441c37993cbc771ef066000e373295dc4d41a325988a6668c550fd210a78259a0a4b2e4eda491433d08452d67974d1
7
+ data.tar.gz: 0d9625607265e83dcf4c2ff5a0f8b58a42c6642adc1d5ecabc6273c0bd6356bd365ecdd330f94793946b73c524a2522b4f0c83035628630665f6aea433bfb96c
@@ -0,0 +1,10 @@
1
+ require "rails"
2
+ require "recheck"
3
+
4
+ module Recheck
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ load File.expand_path("../tasks/recheck_tasks.rake", __FILE__)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,143 @@
1
+ require "pathname"
2
+
3
+ require "erb"
4
+
5
+ require_relative "validation"
6
+
7
+ module Recheck
8
+ module Command
9
+ class Setup
10
+ # override the base gem's method; with the Rails env booted we can
11
+ # introspect the models to create much better initial checks
12
+ def setup_model_checks
13
+ puts "Introspecting ApplicationRecord..."
14
+
15
+ # surely there's a better way to find the gem's root
16
+ template_dir = File.join(File.expand_path("../..", __dir__), "template")
17
+ model_template = File.read("#{template_dir}/application_record_check.rb.erb")
18
+ validation_template = File.read("#{template_dir}/validation_checker.rb.erb")
19
+
20
+ # Get all non-abstract ActiveRecord model classes
21
+ models = ApplicationRecord.descendants.each do |model|
22
+ puts model
23
+ puts " skipping abstract class" or next if model.abstract_class?
24
+ puts " skipping readonly model (probably a view)" or next if model.new.send(:readonly?)
25
+
26
+ source_location = Object.const_source_location(model.to_s)
27
+ model_root_filename = source_location[0].sub(%r{#{Rails.root}/}, "")
28
+ # kind of a bad variable name here, but support the default model path + others
29
+ model_filename = model_root_filename.sub(%r{^app/models/}, "")
30
+
31
+ if %r{/zeitwerk/}.match?(source_location.first)
32
+ puts " Warning: model class wasn't loaded; apparently saw a zeitwerk placeholder for its source_location: #{souce_location}"
33
+ puts "Your app boots/preloads in an unexpected way, if you can help debug please open an issue on recheck."
34
+ exit 1
35
+ end
36
+
37
+ model_check_filename = "recheck/model/#{model_filename}"
38
+ class_name = model.name.sub("::", "_") # to differentiate AccountName and Account::Name
39
+ depth = 1 + model_filename.count("/")
40
+
41
+ puts " #{model_check_filename}"
42
+ FileUtils.mkdir_p(File.dirname(model_check_filename))
43
+ rendered = ERB.new(model_template).result_with_hash({class_name:, depth:, model:, model_root_filename:})
44
+ File.write(model_check_filename, rendered)
45
+
46
+ validation_check_filename = "recheck/validation/#{model_filename}"
47
+ puts " #{validation_check_filename}"
48
+ FileUtils.mkdir_p(File.dirname(validation_check_filename))
49
+ binding = Validation.new(class_name:, depth:, model:, model_root_filename:, queries: queries(model:)).get_binding
50
+ rendered = ERB.new(validation_template, trim_mode: "-").result(binding)
51
+ File.write(validation_check_filename, rendered)
52
+ end
53
+ end
54
+
55
+ def queries model:
56
+ model.validators.map do |validator|
57
+ # validators take a list of attributes but operate on them individually
58
+ validator.attributes.map do |attr|
59
+ name = "query_#{validator.kind}_#{attr}"
60
+ # remove memory addresses; just noise
61
+ inspect = validator.inspect.gsub(/(#<[\w:]+):0x[0-9a-f]+ /, '\1 ')
62
+
63
+ column = model.columns_hash[attr.to_s]
64
+ next Placeholder.new inspect:, comment: "Can't query attribute #{attr}, it's not a database column" if column.nil?
65
+ type = column.sql_type_metadata.type
66
+
67
+ if validator.options[:if] || validator.options[:unless]
68
+ next Placeholder.new inspect:, comment: "Can't automatically translate this validation's :if or :unless into a query"
69
+ end
70
+
71
+ # normalizing - if not specified or [], on: means [:create, :udpate]
72
+ on = validator.options[:on] || []
73
+ on = [:create, :update] if on.empty?
74
+ if validator.options[:on] == [:create]
75
+ next Placeholder.new inspect:, comment: "Only validates on: :create, so there's nothing to validate for persisted records"
76
+ end
77
+
78
+ warning = if validator.options[:allow_nil] && !column.null
79
+ "Warning: model #{model.name} validates #{attr} with :allow_nil but column #{column.name} is NOT NULL, so a 'valid' record can't be saved.\nRemove :allow_nil or make the column nullable."
80
+ end
81
+
82
+ case validator
83
+ when ActiveModel::BlockValidator
84
+ Placeholder.new inspect:, comment: "Can't automatically translate a Ruby block into a query."
85
+ when ActiveModel::Validations::ConfirmationValidator
86
+ FunctionPlaceholder.new inspect:, name:, comment: "Coming soon to Recheck beta"
87
+ when ActiveModel::Validations::FormatValidator
88
+ FunctionPlaceholder.new inspect:, name:, comment: "Coming soon to Recheck beta"
89
+ when ActiveModel::Validations::InclusionValidator
90
+ FunctionPlaceholder.new inspect:, name:, comment: "Coming soon to Recheck beta"
91
+ when ActiveRecord::Validations::AbsenceValidator
92
+ FunctionPlaceholder.new inspect:, name:, comment: "Coming soon to Recheck beta"
93
+ when ActiveRecord::Validations::AssociatedValidator
94
+ FunctionPlaceholder.new inspect:, name:, comment: "Coming soon to Recheck beta"
95
+ when ActiveRecord::Validations::LengthValidator
96
+ or_clauses = []
97
+ if type == :stirng || type == :integer
98
+ if validator.options[:is]
99
+ or_clauses << %{"LENGTH(`#{column.name}`) = '')"}
100
+ end
101
+ if validator.options[:minimum] && validator.options[:maximum]
102
+ or_clauses << %{"LENGTH(`#{column.name}`) >= #{validator.options[:minimum]} and "LENGTH(`#{column.name}`) <= #{validator.options[:maximum]}}
103
+ elsif validator.options[:minimum]
104
+ or_clauses << %{"LENGTH(`#{column.name}`) >= #{validator.options[:minimum]}}
105
+ elsif validator.options[:maximum]
106
+ or_clauses << %{"LENGTH(`#{column.name}`) <= #{validator.options[:maximum]}}
107
+ end
108
+ elsif type == :boolean
109
+ comment = "Validating length of a boolean is backend-dependednt and a strange idea."
110
+ else
111
+ comment = "Recheck doesn't know how to handle presence on a #{type}, please report."
112
+ end
113
+ if !validator.options[:allow_nil] && !validator.options[:allow_blank]
114
+ or_clauses << "#{column.name}: nil"
115
+ end
116
+ when ActiveRecord::Validations::NumericalityValidator
117
+ FunctionPlaceholder.new inspect:, name:, comment: "Coming soon to Recheck beta"
118
+ when ActiveRecord::Validations::PresenceValidator
119
+ if validator.options[:allow_blank]
120
+ next Placeholder.new inspect:, comment: "Validates presence of #{attr} with :allow_blank, which can never fail."
121
+ end
122
+ or_clauses = []
123
+ if !validator.options[:allow_nil]
124
+ or_clauses << "#{column.name}: nil"
125
+ end
126
+ case type
127
+ when :string
128
+ or_clauses << %{"TRIM(`#{column.name}`) = ''"}
129
+ when :boolean
130
+ or_clauses << "#{column.name}: false"
131
+ else
132
+ comment = "Recheck doesn't know how to handle presence on a #{type}, please report."
133
+ end
134
+ Query.new inspect:, name:, warning:, comment:, or_clauses:
135
+ when ActiveRecord::Validations::UniquenessValidator
136
+ FunctionPlaceholder.new inspect:, name:, comment: "Coming soon to Recheck beta"
137
+ end
138
+ end
139
+ end.flatten
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,11 @@
1
+ module Recheck
2
+ Placeholder = Data.define(:inspect, :comment)
3
+ FunctionPlaceholder = Data.define(:inspect, :name, :comment)
4
+ Query = Data.define(:inspect, :warning, :name, :comment, :or_clauses)
5
+
6
+ Validation = Data.define(:class_name, :depth, :model, :model_root_filename, :queries) do
7
+ def get_binding
8
+ binding
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ require_relative "recheck/railtie" if defined?(Rails)
2
+
3
+ module Recheck
4
+ end
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recheck-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Bhat Harkins
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-13 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: recheck
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  description: Check on validations, background jobs, third-party integrations, state
13
27
  machines, and business rules
14
28
  email:
@@ -16,14 +30,18 @@ email:
16
30
  executables: []
17
31
  extensions: []
18
32
  extra_rdoc_files: []
19
- files: []
33
+ files:
34
+ - lib/recheck-rails.rb
35
+ - lib/recheck-rails/setup.rb
36
+ - lib/recheck-rails/validation.rb
37
+ - lib/recheck/railtie.rb
20
38
  homepage: https://recheck.dev
21
39
  licenses:
22
40
  - LGPL-3.0
23
41
  metadata:
24
42
  homepage_uri: https://recheck.dev
25
- source_code_uri: https://github.com/recheckdev/recheck/ruby/recheck/
26
- changelog_uri: https://github.com/recheckdev/recheck/ruby/ruby/blob/main/CHANGELOG.md
43
+ source_code_uri: https://github.com/recheckdev/recheck/ruby/recheck-rails
44
+ changelog_uri: https://github.com/recheckdev/recheck/ruby/recheck-rails/blob/main/CHANGELOG.md
27
45
  rdoc_options: []
28
46
  require_paths:
29
47
  - lib
@@ -38,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
38
56
  - !ruby/object:Gem::Version
39
57
  version: '0'
40
58
  requirements: []
41
- rubygems_version: 3.6.2
59
+ rubygems_version: 3.6.7
42
60
  specification_version: 4
43
- summary: Recheck your production data integrity
61
+ summary: Recheck your production data integrity (in Rails)
44
62
  test_files: []