Skip to content

Instantly share code, notes, and snippets.

@grodowski
Last active January 27, 2021 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grodowski/5f828c2ae77f401f817d2c0c3f85be9b to your computer and use it in GitHub Desktop.
Save grodowski/5f828c2ae77f401f817d2c0c3f85be9b to your computer and use it in GitHub Desktop.
Codegen script to extract explicit ActiveModel validators from SchemaValidations::ActiveRecord
# frozen_string_literal: true
namespace :validations do
desc 'List all model validations defined by schema_validations'
task list: :environment do
LIST_OUTPUT = STDOUT
VALIDATIONS = Hash.new
# monkey patch SchemaValidations to extract validation metadata instead of applying them
# gem source: lib/schema_validations/active_record/validations.rb
SchemaValidations::ActiveRecord::Base::ClassMethods.module_eval do
# disables real validation patching
alias_method :_real_validate_logged, :validate_logged
def validate_logged(method, arg, opts={})
opts = opts.dup
# respect SchemaValidations filtering
if _filter_validation(method, arg)
if opts[:if]
opts[:if] = "#{arg}_changed?".to_sym
end
# print out validation type, column and options
validation = "#{method} #{arg.inspect}"
validation += ", #{opts.inspect[1...-1]}" if opts.any?
validation += "\n"
LIST_OUTPUT.puts(validation)
VALIDATIONS[self] << validation
end
end
end
Rails.application.eager_load!
ActiveRecord::Base.descendants.each_with_index do |cls, idx|
LIST_OUTPUT.puts "#{idx}: #{cls}"
VALIDATIONS[cls] = []
cls.send(:load_schema_validations)
end
end
desc 'Update all models with exported validations'
task load: :list do
puts 'Applying listed validators within app/models...'
dir = Dir.glob('app/models/**/*.rb').reject { |path| path =~ %r{/entities/} }
VALIDATIONS.each do |cls, validators|
if validators.empty?
puts "Skipping #{cls}. No validators"
next
end
model_paths = dir.select { |path| path =~ %r{/#{cls.name.underscore}.rb$} }
model_path = case cls.name
when 'User'
'app/models/user.rb'
when 'Admin::User'
'app/models/admin/user.rb'
else
raise "Ambiguous path #{cls}: #{model_paths}" if model_paths.size > 1
model_paths[0]
end
unless model_path
puts "Could not find #{cls.name} in app/models/"
next
end
source_lines = File.read(model_path).lines
if source_lines.find { |ln| ln =~ /# codegen schema validations begin/ }
puts "Skipping #{cls.name}. Already modified"
next
end
# try first validation line
start_line_idx = Enumerator.new(source_lines).with_index.find { |ln, _idx| ln =~ /validate/ }&.last
start_line_idx -= 1 if start_line_idx # prepend validations if any were found
# try class def line
unless start_line_idx
start_line_idx = Enumerator.new(source_lines).with_index.find { |ln, _idx| ln =~ /class[\s:\w]+/ }.last
end
validators.unshift("# codegen schema validations begin\n")
validators.append("# codegen schema validations end\n")
File.open(model_path, 'w') do |f|
f.write(source_lines[0..start_line_idx].join)
f.write(validators.map { |ln| " #{ln}" }.join)
f.write(source_lines[start_line_idx+1..-1].join)
end
puts "#{cls.name} #{model_path}: applied #{validators.size} validations"
end
puts 'Done!'
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment