app/models/alchemy/ingredient_validator.rb
# frozen_string_literal: true
module Alchemy
# Ingredient Validations:
#
# Ingredient validations can be set inside the +config/elements.yml+ file.
#
# Supported validations are:
#
# * presence
# * uniqueness
# * format
#
# *) format needs to come with a regex or a predefined matcher string as its value.
#
# There are already predefined format matchers listed in the +config/alchemy/config.yml+ file.
# It is also possible to add own format matchers there.
#
# Example of format matchers in +config/alchemy/config.yml+:
#
# format_matchers:
# email: !ruby/regexp '/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/'
# url: !ruby/regexp '/\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix'
# ssl: !ruby/regexp '/https:\/\/[\S]+/'
#
# Example of an element definition with ingredient validations:
#
# - name: person
# ingredients:
# - role: name
# type: Text
# validate: [presence]
# - role: email
# type: Text
# validate: [format: 'email']
# - role: homepage
# type: Text
# validate: [format: !ruby/regexp '^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$']
#
# Example of an element definition with chained validations.
#
# - name: person
# ingredients:
# - role: name
# type: Text
# validate: [presence, uniqueness, format: 'name']
#
class IngredientValidator < ActiveModel::Validator
def validate(ingredient)
@ingredient = ingredient
validations.each do |validation|
if validation.respond_to?(:keys)
validation.map do |key, value|
send(:"validate_#{key}", value)
end
else
send(:"validate_#{validation}")
end
end
end
private
attr_reader :ingredient
def validations
ingredient.definition.fetch(:validate, [])
end
def validate_presence(*)
if ingredient.value.blank?
ingredient.errors.add(:value, :blank)
end
end
def validate_uniqueness(*)
if duplicates.any?
ingredient.errors.add(:value, :taken)
end
end
def validate_format(format)
matcher = Alchemy::Config.get("format_matchers")[format] || format
if !ingredient.value.to_s.match?(Regexp.new(matcher))
ingredient.errors.add(:value, :invalid)
end
end
def validate_length(opts = {})
value_length = ingredient.value.to_s.length
if value_length < opts[:minimum]
ingredient.errors.add(:value, :too_short, count: opts[:minimum])
end
if value_length > opts[:maximum]
ingredient.errors.add(:value, :too_long, count: opts[:maximum])
end
end
def duplicates
ingredient.class
.joins(:element).merge(Alchemy::Element.published)
.where(Alchemy::Element.table_name => {name: ingredient.element.name})
.where(value: ingredient.value)
.where.not(id: ingredient.id)
end
end
end