Die Validation von Benutzereingaben betrifft nicht nur das Aufdecken von Tippfehlern, sondern verhindert auch, dass unpassende Werte das System instabil machen. Eine durchdachte Datenvalidation kann zudem auch Spambots einen Strich durch die Rechnung machen.
Ruby on Rails bringt schon viele Hilfsmittel mit, um Modeldaten beim persistieren zu validieren. Es fehlt jedoch eine Möglichkeit, um beispielsweise zu überprüfen, ob der eingegebene Vor- und Nachname unterschiedlich ist. Auch nach längerer Suche habe ich niemanden in den Telefonbüchern entdecken können, welcher den selben Vor- und Nachname hat. Wenn ihr jemanden findet, meldet dich doch bei mir.
Ganz nach dem Prinzip, der testgetriebenen Entwicklung (TDD), wird erst einmal ein Test erstellt, welcher die Anforderungen definiert und später das Resultat überprüft. Aber Moment mal, hat nicht der dänische Autor von Rails, David Heinemeier Hansson, testgetriebene Entwicklung in seinem Blog für tot erklärt? Der Test beinhaltet ein Modell mit den beiden Feldern, Vor- und Nachname, welches als Mock-Objekt dient. So wie ich das verstehe, ist genau dieses Mock-Objekt einer der Gründe, weshalb der Rails-Erfinder TDD nicht mehr mag.
Ist dieser Validator in ein Rails-Projekt eingebettet, so wäre es sinnvoller einen Test für die jeweiligen Modelle zu schreiben, welche diese Validation voraussetzen. Diese Tests zu den Modellen sind kontextbezogen und überprüfen somit genauer die spezifische Anforderung an die Software. Extrahiert man jedoch diesen Validator, beispielsweise in ein separates Gem, so würde sich dieser Test mit dem Mock-Objekt wieder rechtfertigen. Ein abgeschlossenes Modul, welches vielleicht in mehreren Projekten verwendet wird, soll ja getestet sein.
require 'test_helper'
class ValuesNotEqualValidatorTestModel
include ActiveModel::Validations
validates :forename, :surname, values_not_equal: true
attr_accessor :forename
attr_accessor :surname
end
class ValuesNotEqualValidatorTest < ActiveSupport::TestCase
test "if two equal values aren't valid" do
model = ValuesNotEqualValidatorTestModel.new
model.forename = 'friedrich'
model.surname = 'friedrich'
assert_not model.valid?
end
test "if two different values are valid" do
model = ValuesNotEqualValidatorTestModel.new
model.forename = 'fritz'
model.surname = 'friedrich'
assert model.valid?
end
endNun aber zum Validator selbst. Dadurch, dass wir mehrere Felder überprüfen möchten, erweitern wir den EachValidator. Dieser ruft die Methode validate_each für jedes Feld auf. Ist ein Feld einem zuvor kontrollierten Feld gleich, so wird eine Fehlermeldung generiert, welche sich Rails i18n-Funktionalität bedient.
class ValuesNotEqualValidator < ActiveModel::EachValidator
def validate(record)
@past = Hash.new
super
end
def validate_each(record, attribute, value)
@past.each do |k, v|
if v == value
record.errors.add(attribute, I18n.t('errors.messages.should_not_be_equal_to', other: record.class.human_attribute_name(k)))
end
end
@past[attribute] = value
end
end