Last active
November 3, 2020 11:00
-
-
Save gamecreature/a5f17ad8da409e06f7dcdea2e8f6306e to your computer and use it in GitHub Desktop.
Schema_validations concern for rails, replacement for the schema_validations gem (Compatible with Rails 6.1)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Schema Validations Concern - simple basic replacement for https://github.com/SchemaPlus/schema_validations | |
# | |
# Warning, uniqueness validators are NOT Added | |
# | |
# Constraints: | |
# - null: false => validates ... presence: true | |
# - limit: 100 => validates ... length: { maximum: 100 } | |
# | |
# Data types: | |
# - :boolean => :validates ... inclusion: { in: [true, false] } | |
# - :float => :validates ... numericality: true | |
# - :integer => :validates ... numericality: { only_integer: true, greater_than_or_equal_to: ..., less_than: ... } | |
# - :decimal, precision: ... => :validates ... numericality: { greater_than: ..., less_than: ... } | |
module SchemaValidations | |
extend ActiveSupport::Concern | |
class Config < OpenStruct | |
def initialize | |
super( | |
auto_create: true, | |
log_generated_validations: true, | |
whitelist: %i[created_at updated_at created_on updated_on], | |
except: [] | |
) | |
end | |
def set(options) | |
options.each { |k, v| self[k] = v } | |
end | |
end | |
def self.config | |
@config ||= SchemaValidations::Config.new | |
end | |
def self.setup | |
yield config | |
end | |
class AutoValidatorBuilder | |
attr_reader :model, :config | |
def initialize(model, config) | |
@config = config | |
@model = model | |
end | |
## Column validators | |
def converted_data_type_for_column(column) | |
return :enum if model.respond_to?(:defined_enums) && model.defined_enums.key?(column.name) | |
{ integer: :integer, | |
decimal: :decimal, | |
money: :decimal, | |
float: :numeric, | |
text: :text, | |
string: :text, | |
boolean: :boolean }[column.type] || column.type | |
end | |
def create_validator_for_column(column) | |
datatype = converted_data_type_for_column(column) | |
# create datatype validator | |
case datatype | |
when :integer then create_integer_validation(column) | |
when :decimal then create_decimal_validation(column) | |
when :numeric then create_validator_logged(column.name, numericality: { allow_blank: true }) | |
when :text | |
create_validator_logged(column.name, length: { maximum: column.limit, allow_blank: true }) if column.limit | |
when :datetime, :boolean | |
else | |
Rails.logger.warn "** [schema validations] unkown data type: #{datatype}" | |
end | |
# create not null validator | |
create_not_null_validators_for_column(column) | |
end | |
def create_not_null_validators_for_column(column) | |
return if column.null || column.auto_increment? | |
if column.type == :boolean | |
create_validator_logged(column.name, inclusion: { in: [true, false], message: :blank }) | |
elsif column.has_default? | |
create_validator_logged(column.name, presence: true) | |
else | |
create_validator_logged(column.name, not_nil: true) | |
end | |
end | |
def create_integer_validation(column) | |
# returns a ActiveModel::Type::Integer instantce | |
type = model.connection.lookup_cast_type_from_column(column) | |
args = { | |
allow_blank: true, | |
only_integer: true, | |
greater_than_or_equal_to: type.send(:min_value), | |
less_than: type.send(:max_value) | |
} | |
create_validator_logged(column.name, numericality: args) | |
end | |
def create_decimal_validation(column) | |
return unless column.precision | |
limit = 10**(column.precision - (column.scale || 0)) | |
create_validator_logged(column.name, numericality: { allow_blank: true, greater_than: -limit, less_than: limit }) | |
end | |
## Create Association Validators | |
def create_association_validations | |
model.reflect_on_all_associations(:belongs_to).each do |association| | |
column = model.columns_hash[association.foreign_key] | |
next unless column | |
# NOT NULL constraints | |
create_validator_logged(column.name, presence: true) unless column.null | |
# UNIQUE constraints | |
# add_uniqueness_validation(column) if column.unique? | |
end | |
end | |
## Create all validators | |
def create_validator_logged(name, args) | |
msg = "[schema_validations] #{model.name}.validate #{name.to_sym}, #{args.inspect}" | |
Rails.logger&.debug msg if config.log_generated_validations | |
model.validates name.to_sym, args | |
end | |
def create_validators | |
model.columns.each do |column| | |
create_validator_for_column(column) if should_create_validator?(column) | |
end | |
create_association_validations | |
end | |
def should_create_validator?(column) | |
name = column.name.to_sym | |
return false if (config.except || []).include?(name) | |
return false if (config.whitelist || []).include?(name) | |
true | |
end | |
end | |
included do | |
class_attribute :schema_validations_loaded | |
class_attribute :schema_validations_config | |
before_validation :load_schema_validations unless schema_validations_loaded? | |
def self.schema_validations_config | |
@schema_validations_config ||= SchemaValidations.config.dup | |
end | |
def self.create_schema_validations? | |
schema_validations_config.auto_create && | |
!(schema_validations_loaded || abstract_class? || name.blank?) && table_exists? | |
end | |
def self.load_schema_validations | |
return unless create_schema_validations? | |
AutoValidatorBuilder.new(self, schema_validations_config).create_validators | |
self.schema_validations_loaded = true | |
end | |
def self.schema_validations(options) | |
schema_validations_config.set(options) | |
end | |
def self.validators | |
load_schema_validations unless schema_validations_loaded? | |
super | |
end | |
def self.validators_on(*args) | |
load_schema_validations unless schema_validations_loaded? | |
super | |
end | |
end | |
def load_schema_validations | |
self.class.load_schema_validations | |
end | |
end | |
unless defined?(NotNilValidator) | |
class NotNilValidator < ActiveModel::EachValidator | |
def validate_each(record, attr_name, value) | |
record.errors.add(attr_name, :blank, options) if value.nil? | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment