Der Großteil aller Modelvalidierungen wird in Ruby On Rails mit ActiveModel::Validations abgedeckt.
Allerdings gibt es mitunter die Notwendigkeit von Custom Validierung.
Dann kann es Sinn machen, sie in eine eigene Validation Klasse zu überführen.
Validierung Beispiel
Als Ausgangsbeispiel dient ein vereinfachtes User Modell:
rails g model User email:string password:string
Das Passwort soll vorhanden sein und eine Mindestlänge von 6 Zeichen haben. Zusätzlich soll die Passwortkomplexität validiert werden. In diesem Beispiel soll es entweder aus Groß-/ Kleinbuchstaben, Großbuchstaben/ Zahlen oder Kleinbuchstaben/ Zahlen bestehen:
class User < ApplicationRecord
validates :password, presence: true
validates :password, length: { minimum: 6 }, allow_nil: true
validate :password_complexity
private
REQUIRED_PASSWORD_COMPLEXITY = 2
PASSWORD_COMPLEXITIES = [
/[A-Z]/,
/[a-z]/,
/[0-9]/
]
def password_complexity
return if password.nil?
return if implied_complexity(password) >= REQUIRED_PASSWORD_COMPLEXITY
errors.add(:password, I18n.t('errors.messages.complexity'))
end
def implied_complexity(value)
COMPLEXITIES.select { |complexity| complexity =~ value }
.size
end
end
Es gibt verschiedene Gründe, warum die Validierung in eine eigene Validator Klasse überführt werden sollte:
- bessere Testbarkeit der Validierungslogik
- Wiederverwendbarkeit des Validators in anderen Models oder Form Objects
Custom Validatoren einführen
Zunächst muss noch der Pfad zu den Custom Validatoren für den Autoloader konfiguriert werden:
config.autoload_paths += Dir["#{config.root}/lib/custom_validations/**/"]
Custom Validator Klasse
Es gilt die Konvention für den Klassennamen: Name des Validators + Validator:
# lib/custom_validations/complexity_validator.rb
class ComplexityValidator < ActiveModel::EachValidator
MAX_LEVEL = 3
COMPLEXITIES = [
/[A-Z]/,
/[a-z]/,
/[0-9]/
]
def validate_each(subject, attribute, value)
return if options[:allow_nil] && value.nil?
return if implied_complexity(value.to_s) >= required_complexity
subject.errors.add(attribute, error_message)
end
private
def required_complexity
options[:with] || MAX_LEVEL
end
def implied_complexity(value)
COMPLEXITIES.select { |complexity| complexity =~ value }
.size
end
def error_message
options[:message] || I18n.t('errors.messages.complexity')
end
end
Die Validierungslogik muss sich in der Methode validate_each befinden. Die Übergabeparameter sind das Objekt (user), das Attribut (password) und dem Wert (das Passwort). Außerdem besteht noch die Möglichkeit auf options zuzugreifen. Das sind die bekannten Standardoptionen (message, allow_nil, allow_blank, on, if, unless, strict). Mit der with Option können weitere Parameter an den Validator übergeben werden, wie man es zum Beispiel von dem LengthValidator kennt.
Custom Validator verwenden
Durch die Auslagerung der Validierungslogik in den Custom Validator, gewinnt die Lesbarkeit des Modells:
class User < ApplicationRecord
validates :password, presence: true
validates :password, length: { minimum: 6 },
complexity: 2,
allow_nil: true
Der ComplexityValidator wird mit der Komplexität 2 gesetzt.
Der Validator kann auch mit anderen Validatoren kombiniert werden (in diesem Fall mit dem LengthValidator).
Angenommen für Administratoren soll eine höhere Passwortkomplexität gelten, ist das sehr einfach umsetzbar. Zum Beispiel in einem Form Object für das Erstellen von Administratoren:
class AdminForm < ApplicationForm
validates :password, presence: true
validates :password, length: { minimum: 8 },
complexity: 3,
allow_nil: true