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 +4 -4
- data/lib/recheck/railtie.rb +10 -0
- data/lib/recheck-rails/setup.rb +143 -0
- data/lib/recheck-rails/validation.rb +11 -0
- data/lib/recheck-rails.rb +4 -0
- metadata +26 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e1a5a41923f7278f00931872e42c235c2cf761e0cbed4a8852b60eaa115765f
|
4
|
+
data.tar.gz: 7333424af6b70b3f8b77a948047a3f96c00e97a69894f5322ffc0373fd4182f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0829c52c8643d24a2c5991ffc14c4bea2441c37993cbc771ef066000e373295dc4d41a325988a6668c550fd210a78259a0a4b2e4eda491433d08452d67974d1
|
7
|
+
data.tar.gz: 0d9625607265e83dcf4c2ff5a0f8b58a42c6642adc1d5ecabc6273c0bd6356bd365ecdd330f94793946b73c524a2522b4f0c83035628630665f6aea433bfb96c
|
@@ -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
|
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
|
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:
|
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/
|
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.
|
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: []
|