Neu: Das englische Ruby on Rails 4.0 Buch.

4.15. Validierung (Validation)

Einer der häufigsten Fehlerquellen in Programmen sind nicht valide Datensätze. ActiveRecord stellt mit validates eine schnelle und einfache Möglichkeit zur Validierung von Datensätzen zur Verfügung. So können Sie sicher sein, dass auch wirklich nur sinnvolle Datensätze den Weg in Ihre Datenbank finden.

Vorbereitung

Erstellen wir eine neue Applikation für dieses Kapitel:
MacBook:~ xyz$ rails new shop
[...]
MacBook:~ xyz$ cd shop
MacBook:shop xyz$ rails generate model product name 'price:decimal{7,2}' weight:integer in_stock:boolean expiration_date:date
[...]
MacBook:shop xyz$ rake db:migrate
[...]
MacBook:shop xyz$

Die Grundidee

Zu jedem Model gibt es im Verzeichnis app/models/ eine passende Datei. In dieser Datei können wir nicht nur Datenbankabhängigkeiten definieren, sondern auch sämtliche Validations realisieren. Der Vorteil ist: alles an einer Stelle. Convention over Configuration!
Ohne jegliche Validierung können wir problemlos einen leeren Datensatz in beiden Modellen anlegen:
SW:shop stefan$ rails console
Loading development environment (Rails 3.2.8)
1.9.3p194 :001 > Product.create
   (0.1ms)  begin transaction
  SQL (8.9ms)  INSERT INTO "products" ("created_at", "expiration_date", "in_stock", "name", "price", "updated_at", "weight") VALUES (?, ?, ?, ?, ?, ?, ?)  [["created_at", Mon, 10 Sep 2012 14:45:33 UTC +00:00], ["expiration_date", nil], ["in_stock", nil], ["name", nil], ["price", nil], ["updated_at", Mon, 10 Sep 2012 14:45:33 UTC +00:00], ["weight", nil]]
   (0.9ms)  commit transaction
 => #<Product id: 1, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: "2012-09-10 14:45:33", updated_at: "2012-09-10 14:45:33"> 
1.9.3p194 :002 > exit
SW:shop stefan$
Dieser Datensatz macht aber in der Realität keinen Sinn. Ein Product braucht einen name und einen price. Deshalb kann man in ActiveRecord Validierungen definieren. Damit können Sie als Programmierer sicherstellen, dass nur für Sie valide Datensätze in Ihrer Datenbank abgespeichert werden.
Ich greife zur Veranschaulichung des Mechanismus den presence Helper vorweg. Bitte füllen Sie Ihre app/model/product.rb mit folgendem Inhalt:
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            :presence => true

  validates :price,
            :presence => true
end
Jetzt versuchen wir noch mal in der Console einen leeren Datensatz anzulegen:
SW:shop stefan$ rails console
Loading development environment (Rails 3.2.8)
1.9.3p194 :001 > product = Product.create
   (0.0ms)  begin transaction
   (0.1ms)  rollback transaction
 => #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :002 > product.valid?
 => false 
1.9.3p194 :003 > 
Obwohl wir mit der Methode create (siehe „create“) einen neuen Datensatz anlegen wollten, ist dies nicht geschehen. Der Validierungs-Mechanismus hat vor dem Abspeichern des Datensatzes eingegriffen. Es wird also erst validiert und dann gespeichert.
Haben wir Zugriff auf die Fehler? Ja, mit der Methode errors bzw. mit errors.messages können wir uns die aufgetretenen Fehler anschauen:
1.9.3p194 :003 > product.errors
 => #<ActiveModel::Errors:0x007ff88537fae8 @base=#<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>, @messages={:name=>["can't be blank"], :price=>["can't be blank"]}> 
1.9.3p194 :004 > product.errors.messages
 => {:name=>["can't be blank"], :price=>["can't be blank"]} 
1.9.3p194 :005 >
Diese Fehlermeldung ist für einen menschlichen und englischsprachigen Benutzer definiert (mehr dazu und wie die Fehler ins Deutsche übersetzt werden können in Kapitel 10, Internationalisierung).
Erst wenn wir den Attributen name und price einen Wert zuweisen, können wir das Objekt abspeichern:
1.9.3p194 :005 > product.name = 'Milch (1 Liter)'
 => "Milch (1 Liter)" 
1.9.3p194 :006 > product.price = 0.45
 => 0.45 
1.9.3p194 :007 > product.save
   (0.1ms)  begin transaction
  SQL (6.4ms)  INSERT INTO "products" ("created_at", "expiration_date", "in_stock", "name", "price", "updated_at", "weight") VALUES (?, ?, ?, ?, ?, ?, ?)  [["created_at", Mon, 10 Sep 2012 14:52:44 UTC +00:00], ["expiration_date", nil], ["in_stock", nil], ["name", "Milch (1 Liter)"], ["price", #<BigDecimal:7ff8852ea5d8,'0.45E0',9(45)>], ["updated_at", Mon, 10 Sep 2012 14:52:44 UTC +00:00], ["weight", nil]]
   (0.8ms)  commit transaction
 => true 
1.9.3p194 :008 >

valid?

Die Methode valid? gibt als Boolean aus, ob ein Objekt valide ist. So kann man schon vor dem eigentlichen Abspeichern eine entsprechende Kontrolle machen:
1.9.3p194 :008 > product = Product.new
 => #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :009 > product.valid?
 => false 
1.9.3p194 :010 >

save( :validate => false )

Wie so oft im Leben kann man auch hier alles umgehen. Wenn man der Methode save als Parameter :validate => false mitgibt, dann wird der Datensatz von Validation abgespeichert:
1.9.3p194 :010 > product = Product.new
 => #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :011 > product.save
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
 => false 
1.9.3p194 :012 > product.valid?
 => false 
1.9.3p194 :013 > product.save(:validate => false)
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "products" ("created_at", "expiration_date", "in_stock", "name", "price", "updated_at", "weight") VALUES (?, ?, ?, ?, ?, ?, ?)  [["created_at", Tue, 08 May 2012 15:20:44 UTC +00:00], ["expiration_date", nil], ["in_stock", nil], ["name", nil], ["price", nil], ["updated_at", Tue, 08 May 2012 15:20:44 UTC +00:00], ["weight", nil]]
   (3.5ms)  commit transaction
 => true 
1.9.3p194 :014 > exit
MacBook:shop xyz$

Warnung

Ich gehe davon aus, dass Sie die hier auftauchende Problematik verstehen. Diese Möglichkeit bitte nur einsetzen, wenn es einen guten Grund gibt. Sonst könnte man sich die ganze Validierung ja auch direkt sparen.

presence

In unserem Model product gibt es ein paar Felder, die auf jeden Fall ausgefüllt werden müssen. Das erreichen wir mit presence.
app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            :presence => true

  validates :price,
            :presence => true
end
Wenn wir damit einen leeren User-Datensatz anlegen wollen, bekommen wir sehr viele Validierungsfehler:
MacBook:shop xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > product = Product.create
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
 => #<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :002 > product.errors
 => #<ActiveModel::Errors:0x007ffe9189e0d0 @base=#<Product id: nil, name: nil, price: nil, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>, @messages={:name=>["can't be blank"], :price=>["can't be blank"]}> 
1.9.3p194 :003 >
Erst wenn wir alle Angaben gemacht haben, kann der Datensatz gespeichert werden:
1.9.3p194 :003 > product.name = 'Milch (1 Liter)'
 => "Milch (1 Liter)" 
1.9.3p194 :004 > product.price = 0.45
 => 0.45 
1.9.3p194 :005 > product.save
   (0.1ms)  begin transaction
  SQL (5.2ms)  INSERT INTO "products" ("created_at", "expiration_date", "in_stock", "name", "price", "updated_at", "weight") VALUES (?, ?, ?, ?, ?, ?, ?)  [["created_at", Tue, 08 May 2012 15:25:22 UTC +00:00], ["expiration_date", nil], ["in_stock", nil], ["name", "Milch (1 Liter)"], ["price", #<BigDecimal:7ffe91a2d270,'0.45E0',9(45)>], ["updated_at", Tue, 08 May 2012 15:25:22 UTC +00:00], ["weight", nil]]
   (3.9ms)  commit transaction
 => true 
1.9.3p194 :006 > exit
MacBook:shop xyz$

length

Mit length können Sie die Länge eines bestimmten Attributes eingrenzen. Erklärt sich am Beispiel am einfachsten.
app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            :presence => true,
            :length => { :in => 2..20 }

  validates :price,
            :presence => true
end
Wenn wir jetzt einen Product mit einem name aus einem Buchstaben abspeichern wollen, dann bekommen wir einen Fehler:
MacBook:shop xyz$ rails console
Loading development environment (Rails 3.2.8)
1.9.3p194 :001 > product = Product.create(:name => 'M', :price => 0.45)
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
 => #<Product id: nil, name: "M", price: #<BigDecimal:7fd6ad8cec28,'0.45E0',9(45)>, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :002 > product.errors
 => #<ActiveModel::Errors:0x007fd6ad88fe38 @base=#<Product id: nil, name: "M", price: #<BigDecimal:7fd6ad8d19c8,'0.45E0',9(45)>, weight: nil, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>, @messages={:name=>["is too short (minimum is 2 characters)"]}> 
1.9.3p194 :003 > exit
MacBook:shop xyz$

Optionen

length kann mit folgenden Optionen aufgerufen werden:
  • :minimum
    Die minimale Länge eines Attributes. Beispiel:
    validates :name,
              :presence => true,
              :length => { :minimum => 2 }
  • :maximum
    Die maximale Länge eines Attributes. Beispiel:
    validates :name,
              :presence => true,
              :length => { :maximum => 20 }
  • :is
    Ist genau so viele Zeichen lang. Beispiel:
    validates :name,
              :presence => true,
              :length => { :is => 8 }
  • :in oder :within
    Definiert einen Längenraum. Als Erstes kommt das Minimum und als Zweites das Maximum. Beispiel:
    validates :name,
              :presence => true,
              :length => { :in => 2..20 }
  • :tokenizer
    Damit kann definiert werden, wie das Attribut zum Zählen gesplittet werden soll. Default: Default: lambda{ |value| value.split(//) } (damit werden einzelne Zeichen gezählt). Beispiel (zum Zählen von Wörtern):
    validates :content,
              :presence => true,
              :length => { :in => 2..20 },
              :tokenizer => lambda {|str| str.scan(/\w+/)}
    
  • :too_long
    Definiert die Fehlermeldung von :maximum. Default: "is too long (maximum is %d characters)". Beispiel:
    validates :name,
              :presence => true,
              :length => { :maximum => 20 },
              :too_long => "must have at most %{count} characters"
    

    Anmerkung

    Bitte beachten Sie bei allen Fehlermeldungen Kapitel 10, Internationalisierung.
  • :too_short
    Definiert die Fehlermeldung von :minimum. Default: "is too short (min is %d characters)". Beispiel:
    validates :name,
              :presence => true,
              :length => { :minimum => 5 },
              :too_short => "must have at least %{count} characters"
    

    Anmerkung

    Bitte beachten Sie bei allen Fehlermeldungen Kapitel 10, Internationalisierung.

numericality

Mit numericality können Sie überprüfen, ob ein Attribut eine Zahl ist. Erklärt sich am Beispiel am einfachsten.
app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            :presence => true,
            :length => { :in => 2..20 }

  validates :price,
            :presence => true

  validates :weight,
            :numericality => true
end
Wenn wir das Gewicht (weight) fehlerhaft mit oder ganz aus Buchstaben definieren, dann bekommen wir einen Validierungsfehler:
MacBook:shop xyz$ rails console
Loading development environment (Rails 3.2.8)
1.9.3p194 :001 > product = Product.create(:name => 'Milch (1 Liter)', :price => 0.45, :weight => 'abc')
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
 => #<Product id: nil, name: "Milch (1 Liter)", price: #<BigDecimal:7f8594def848,'0.45E0',9(45)>, weight: 0, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :002 > product.errors
 => #<ActiveModel::Errors:0x007f8594ba3878 @base=#<Product id: nil, name: "Milch (1 Liter)", price: #<BigDecimal:7f8594df3588,'0.45E0',9(45)>, weight: 0, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>, @messages={:weight=>["is not a number"]}> 
1.9.3p194 :003 > exit
MacBook:shop xyz$

Tipp

Auch wenn ein Attribute in der Datenbank als String abgelegt wird, kann man mit numericality den Inhalt auf eine Zahl festlegen.

Optionen

numericality kann mit folgenden Optionen aufgerufen werden:
  • :only_integer
    Das Attribut darf nur eine Integer Zahl enthalten. Default: false. Beispiel:
    validates :weight, 
              :numericality => { :only_integer => true }
    
  • :greater_than
    Die im Attribut gespeicherte Zahl muss größer sein, als der hier angegebene Wert. Beispiel:
    validates :weight, 
              :numericality => { :greater_than => 100 }
    
  • :greater_than_or_equal_to
    Die im Attribut gespeicherte Zahl muss größer oder gleich dem hier angegebene Wert sein. Beispiel:
    validates :weight, 
              :numericality => { :greater_than_or_equal_to => 100 }
    
  • :equal_to
    Definiert einen bestimmten Wert, den das Attribut haben muss. Beispiel:
    validates :weight, 
              :numericality => { :equal_to => 100 }
    
  • :less_than
    Die im Attribut gespeicherte Zahl muss kleiner sein, als der hier angegebene Wert. Beispiel:
    validates :weight, 
              :numericality => { :less_than => 100 }
    
  • :less_than_or_equal_to
    Die im Attribut gespeicherte Zahl muss kleiner oder gleich dem hier angegebenen Wert sein. Beispiel:
    validates :weight, 
              :numericality => { :less_than_or_equal_to => 100 }
    
  • :odd
    Die im Attribut gespeicherte Zahl muss ungerade sein. Beispiel:
    validates :weight, 
              :numericality => { :odd => true }
    
  • :even
    Die im Attribut gespeicherte Zahl muss gerade sein. Beispiel:
    validates :weight, 
              :numericality => { :even => true }
    

Anmerkung

Die folgenden Validationen können als Wert auch ein Proc oder ein Symbol, das mit einer Methode korrespondiert, enthalten: :greater_than, :greater_than_or_equal_to, :equal_to, :less_than, :less_than_or_equal_to.
Beispiel dazu aus ri validates_numericality_of:
validates_numericality_of :width, :less_than => Proc.new { |person| person.height }

uniqueness

Mit uniqueness können Sie definieren, dass der Wert dieses Attributes einzigartig in der Datenbank ist. Wenn ein Produkt in der Datenbank einen eindeutigen und nicht doppelt vorhandenen Namen haben soll, dann geht das mit dieser Validierung:
app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            :presence => true,
            :uniqueness => true
end
Wenn wir jetzt ein neues Product mit einem schon existierenden name einrichten wollen, bekommen wir einen Fehler ausgegeben:
MacBook:shop xyz$ rails console
Loading development environment (Rails 3.2.8)
1.9.3p194 :001 > Product.last
  Product Load (0.2ms)  SELECT "products".* FROM "products" ORDER BY "products"."id" DESC LIMIT 1
 => #<Product id: 4, name: "Milch (1 Liter)", price: #<BigDecimal:7fc6e9695160,'0.45E0',9(45)>, weight: nil, in_stock: nil, expiration_date: nil, created_at: "2012-05-08 15:25:22", updated_at: "2012-05-08 15:25:22"> 
1.9.3p194 :002 > product = Product.create(:name => 'Milch (1 Liter)', :price => 0.45, :weight => 1000)
   (0.1ms)  begin transaction
  Product Exists (0.2ms)  SELECT 1 FROM "products" WHERE "products"."name" = 'Milch (1 Liter)' LIMIT 1
   (0.1ms)  rollback transaction
 => #<Product id: nil, name: "Milch (1 Liter)", price: #<BigDecimal:7fc6e9f7cb70,'0.45E0',9(45)>, weight: 1000, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :003 > product.errors
 => #<ActiveModel::Errors:0x007fc6e9432300 @base=#<Product id: nil, name: "Milch (1 Liter)", price: #<BigDecimal:7fc6e9b09250,'0.45E0',9(45)>, weight: 1000, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>, @messages={:name=>["has already been taken"]}> 
1.9.3p194 :004 > exit
MacBook:shop xyz$

Warnung

Die Validierung mit uniqueness ist kein absoluter Garant für eine echte Einzigartigkeit des Attributes in der Datenbank. Es kann dabei zu einer Race-Condition kommen (siehe http://de.wikipedia.org/wiki/Race_Condition). Die detaillierte Diskussion dieses Effektes übersteigt allerdings die Tiefe eines Einsteiger-Buches (es handelt sich um ein extrem seltenes Phänomen).

Optionen

uniqueness kann mit folgenden Optionen aufgerufen werden:
  • :scope
    Definiert einen Geltungsbereich für die Einzigartigkeit. Wenn wir eine anders strukturierte Telefonnummerndatenbank hätten (mit nur einem Feld für die Telefonnummer), dann könnten wir damit definieren, dass eine Telefonnummer pro User nur einmal gespeichert werden darf. Das sähe dann so aus:
    validates :name,
              :presence => true,
              :uniqueness => { :scope => :user_id }
  • :case_sensitive
    Überprüft die Einzigartigkeit auch auf Groß- und Kleinschreibung. Default: False. Beispiel:
    validates :name,
              :presence => true,
              :uniqueness => { :case_sensitive => true }
    

inclusion

Mit inclusion können Sie definieren, aus welchen Werten der Inhalt dieses Attributes erstellt werden kann. Bei unserem Beispiel können wir das am Attribute in_stock veranschaulishen.
app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            :presence => true,
            :length => { :in => 2..20 }

  validates :price,
            :presence => true

  validates :in_stock,
            :inclusion => { :in => [true, false] }
end
In unserem Datenmodel muss ein Product als in_stock entweder true oder false sein (es darf also kein nil geben). Wenn wir einen anderen Wert als true oder false eingeben, wird ein Validation-Fehler gemeldet:
MacBook:shop xyz$ rails console
Loading development environment (Rails 3.2.8)
1.9.3p194 :001 > product = Product.create(:name => 'Milch fettarm (1 Liter)', :price => 0.45, :weight => 1000)
   (0.1ms)  begin transaction
  Product Exists (0.2ms)  SELECT 1 FROM "products" WHERE "products"."name" = 'Milch fettarm (1 Liter)' LIMIT 1
   (0.1ms)  rollback transaction
 => #<Product id: nil, name: "Milch fettarm (1 Liter)", price: #<BigDecimal:7fd125470f38,'0.45E0',9(45)>, weight: 1000, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :002 > product.errors
 => #<ActiveModel::Errors:0x007fd12513a648 @base=#<Product id: nil, name: "Milch fettarm (1 Liter)", price: #<BigDecimal:7fd125475bf0,'0.45E0',9(45)>, weight: 1000, in_stock: nil, expiration_date: nil, created_at: nil, updated_at: nil>, @messages={:in_stock=>["is not included in the list"]}> 
1.9.3p194 :003 > exit
MacBook:shop xyz$

Tipp

Denken Sie immer an die Macht von Ruby! Sie können so z. B. das Enumerable-Objekt immer live aus einer anderen Datenbank generieren. Das heißt, die Validation ist nicht statisch definiert.

Optionen

inclusion kann mit folgenden Optionen aufgerufen werden:
  • :message
    Um eine eigene Fehlermeldung auszugeben. Default: "is not included in the list". Beispiel:
    validates :in_stock,
              :inclusion => { :in => [true, false], 
                              :message => 'this one is not allowed' }
    

    Anmerkung

    Bitte beachten Sie bei allen Fehlermeldungen Kapitel 10, Internationalisierung.

exclusion

exclusion ist die Umkehrung von „inclusion“. Sie können definieren, aus welchen Werten der Inhalt dieses Attributes nicht erstellt werden darf.
app/models/product.rb
class Product < ActiveRecord::Base
  attr_accessible :expiration_date, :in_stock, :name, :price, :weight

  validates :name,
            :presence => true,
            :length => { :in => 2..20 }

  validates :price,
            :presence => true

  validates :in_stock,
            :exclusion => { :in => [nil] }
end

Tipp

Denken Sie immer an die Macht von Ruby! Sie können so z. B. das Enumerable-Objekt immer live aus einer anderen Datenbank generieren. Das heißt, die Validation muss nicht statisch definiert werden.

Optionen

exclusion kann mit folgenden Optionen aufgerufen werden:
  • :message
    Um eine eigene Fehlermeldung auszugeben. Default: "is not included in the list". Beispiel:
    validates :in_stock,
              :exclusion => { :in => [nil], 
                              :message => 'xyz' }
    

    Anmerkung

    Bitte beachten Sie bei allen Fehlermeldungen Kapitel 10, Internationalisierung.

format

Mit format können Sie mit einer Regular Expression (siehe http://de.wikipedia.org/wiki/Regulärer_Ausdruck) definieren, wie sich der Inhalt eines Attributes aufbauen darf.
Mit format kann man z. B. eine einfache Validierung der Syntax einer E-Mail-Adresse machen:
validates :email, 
          :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }

Warnung

Es sollte klar sein, dass die hier gezeigte E-Mail-Adressen-Validierung nicht vollständig ist. Es geht hier nur um ein Beispiel. Damit kann man nur die syntaktische Korrektheit einer E-Mail-Adresse überprüfen.

Optionen

validates_format_of kann mit folgenden Optionen aufgerufen werden:
  • :message
    Um eine eigene Fehlermeldung auszugeben. Default: "is invalid". Beispiel:
    validates :email, 
              :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
                           :message => 'is not a valid email address' }
    

    Anmerkung

    Bitte beachten Sie bei allen Fehlermeldungen Kapitel 10, Internationalisierung.

Allgemeine Validierungs-Optionen

Es gibt einige Optionen, die bei allen Validierungen verwendet werden können:
  • :allow_nil
    Erlaubt den Wert nil. Beispiel:
    validates :email, 
              :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
              :allow_nil => true
  • :allow_blank
    Wie allow_nil, aber zusätzlich noch mit einem leeren String. Beispiel:
    validates :email, 
              :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
              :allow_blank => true
  • :on
    Eine Validierung kann mit on auf die Events create, update oder safe beschänkt werden. Im folgenden Beispiel greift die Validierung nur beim initialen Erstellen des Datensatzes (beim create):
    validates :email, 
              :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
              :on => :create
  • :if und :unless
    :if oder :unless rufen die angegebene Methode auf und führen die Validierung nur aus, wenn das Ergebniss der Methode true ist:
    validates :name,
              :presence => true,
              :if => :today_is_monday?
    
    def today_is_monday?
      Date.today.monday?
    end
    
  • :proc
    :proc ruft ein Proc Objekt auf.
    validates :name,
              :presence => true,
              :if => Proc.new { |a| a.email == 'test@test.com' }
    

Eigene Validations schreiben

Hin und wieder möchte man eine Validierung durchführen, bei der man eigene Programmlogik benötigt. Für diesen Fall kann man eigene Validierungen definieren.

Validierungen mit eigenen Methoden definieren

Nehmen wir einmal an, Sie haben eine Klasse HotelReservation und dort darf das Ende der Reservierung logischerweise nicht vor dem Start der Reservierung liegen. Legen wir erst mal das Model an:
MacBook:shop xyz$ rails generate model HotelReservation start_date:date end_date:date room_type
[...]
MacBook:shop xyz$ rake db:migrate
[...]
MacBook:shop xyz$
Dann legen wir in der app/model/hotel_reservation.rb fest, dass die Attribute start_date und end_date auf jeden Fall ausgefühlt werden müssen und zusätzlich überprüfen wir mit der Methode start_has_to_be_before_end, dass das start_date vor dem end_date liegt:
class HotelReservation < ActiveRecord::Base
  attr_accessible :end_date, :room_type, :start_date

  validates :start_date,
            :presence => true

  validates :end_date,
            :presence => true

  validate :start_has_to_be_before_end

  def start_has_to_be_before_end
    if end_date < start_date
      errors.add(:start_date, 'has to be before the end date')
    end
  end

end
Mit errors.add können wir Fehlermeldungen zu einzelnen Attributen hinzufügen. Mit errors.add_to_base können Sie Fehlermeldungen zum gesamten Objekt hinzufügen.
Probieren wir die Validierung in der Console aus:
MacBook:shop stefan$ rails console
Loading development environment (Rails 3.2.8)
1.9.3p194 :001 > new_reservation = HotelReservation.new(:start_date => Date.today, :end_date => Date.today - 1.day)
 => #<HotelReservation id: nil, start_date: "2012-09-16", end_date: "2012-09-15", room_type: nil, created_at: nil, updated_at: nil> 
1.9.3p194 :002 > new_reservation.valid?
 => false 
1.9.3p194 :003 > new_reservation.errors.messages
 => {:start_date=>["has to be before the end date"]} 
1.9.3p194 :004 > new_reservation.end_date = Date.today + 10.days
 => Wed, 26 Sep 2012 
1.9.3p194 :005 > new_reservation.valid?
 => true 
1.9.3p194 :006 > exit
MacBook:shop stefan$ 

Weitere Doku

Das Thema Validations wird in der offiziellen englischen Rails-Doku unter http://guides.rubyonrails.org/active_record_validations_callbacks.html sehr gut beschrieben. Im System können Sie mit ri validates auf der Kommandozeile die System-Doku abrufen.