Validations

Validations offer a simple and powerful pattern to ensure the integrity of your data. By declaring validations on your models, you can be certain that only valid data will be saved to your database. No longer will you need to recall where you put that function which verifies the legitimacy of an e-mail and whether or not it will stop the record fom being saved. With validations, if your data is invalid, ActiveRecord will take care of marking the record as invalid instead of writing it to the database.

Validations will run for the following methods normally:

1 $book->save();
2 Book::create();
3 $book->update_attributes(array('title' => 'new title'));

The following will skip validations and save the record:

1 $book->update_attribute();
2 $book->save(false); # anytime you pass false to save it will skip validations

Is my model valid or not?

You can determine whether or not your model is valid and can be saved to the database by issuing one of these methods: Model::is_valid or Model::is_invalid. Both of these methods will run the validations for your model when invoked.

 1 class Book extends ActiveRecord\Model
 2 {
 3   static $validates_presence_of = array(
 4     array('title')
 5   );
 6 }
 7 
 8 # our book won't pass validates_presence_of
 9 $book = new Book(array('title' => ''));
10 echo $book->is_valid(); # false
11 echo $book->is_invalid(); # true

If validation(s) fails for your model, then you can access the error message(s) like so. Let's assume that our validation was validates_presence_of.

 1 class Book extends ActiveRecord\Model
 2 {
 3   static $validates_presence_of = array(
 4     array('title')
 5   );
 6 }
 7 
 8 $book = new Book(array('title' => ''));
 9 $book->save();
10 $book->errors->is_invalid('title'); # => true
11 
12 # if the attribute fails more than 1 validation,
13 # you would get an array of errors below
14 
15 echo $book->errors->on('title'); # => can't be blank

Now let's assume our model failed two validations: validates_presence_of and validates_size_of.

 1 class Book extends ActiveRecord\Model
 2 {
 3   static $validates_presence_of = array(
 4     array('title')
 5   );
 6 
 7   static $validates_size_of = array(
 8     array('title', 'within' => array(1,20))
 9   );
10 }
11 
12 $book = new Book(array('title' => ''));
13 $book->save();
14 $book->errors->is_invalid('title'); # true
15 
16 print_r($book->errors->on('title'));
17 
18 # which would give us:
19 
20 # Array
21 # (
22 #   [0] => can't be blank
23 #   [1] => is too short (minimum is 1 characters)
24 # )

Commonalities

Validations are defined with a common set of options and some of them have specific options. As you've seen above, creating a validation is as simple as declaring a static validation variable in your model class as a multi-dimensional array (to validate multiple attributes). Each validation will require you to put the attribute name in the 0 index of the array. You can configure the error message by creating a message key with the message string as the value. You can also add an option which will only run the validation on either creation or update. By default, your validation will run everytime Model#save() is called.

 1 class Book extends ActiveRecord\Model
 2 {
 3   # 0 index is title, the attribute to test against
 4   # message is our custom error msg
 5   # only run this validation on creation - not when updating
 6   static $validates_presence_of = array(
 7     array('title', 'message' => 'cannot be blank on a book!', 'on' => 'create')
 8   );
 9 }

In some validations you may use: in, is within. In/within designate a range whereby you use an array with the first and second elements representing the beginning and end of the range respectively. Is represents equality.

Common options available to all validators:

Available validations

There are a number of pre-defined validation routines that you can declare on your model for specific attributes.

validates_presence_of

This is probably the simplest of all the validations. It will make sure that the value of the attribute is not null or a blank string. Available options:

 1 class Book extends ActiveRecord\Model
 2 {
 3   static $validates_presence_of = array(
 4     array('title'),
 5     array('cover_blurb', 'message' => 'must be present and witty')
 6   );
 7 }
 8 
 9 $book = new Book(array('title' => ''));
10 $book->save();
11 
12 echo $book->errors->on('cover_blurb'); # => must be present and witty
13 echo $book->errors->on('title'); # => can't be blank

validates_size_of / validates_length_of

These two validations are one and the same. The purpose is to validate the length in characters of a given attribute. Available options:

is: attribute should be exactly n characters long
in/within: attribute should be within an range array(n, m)
maximum/minimum: attribute should not be above/below respectively

Each of the options has a particular message and can be changed.

 1 class Book extends ActiveRecord\Model
 2 {
 3   static $validates_size_of = array(
 4     array('title', 'within' => array(1,5), 'too_short' => 'too short!'),
 5     array('cover_blurb', 'is' => 20),
 6     array('description', 'maximum' => 10, 'too_long' => 'should be short and sweet')
 7   );
 8 }
 9 
10 $book = new Book;
11 $book->title = 'War and Peace';
12 $book->cover_blurb = 'not 20 chars';
13 $book->description = 'this description is longer than 10 chars';
14 $ret = $book->save();
15 
16 # validations failed so we get a false return
17 if ($ret == false)
18 {
19   # too short!
20   echo $book->errors->on('title');
21 
22   # is the wrong length (should be 20 chars)
23   echo $book->errors->on('cover_blurb');
24 
25   # should be short and sweet
26   echo $book->errors->on('description');
27 }

validates_(in|ex)clusion_of

As you can see from the names, these two are similar. In fact, this is just a white/black list approach to your validations. Inclusion is a whitelist that will require a value to be within a given set. Exclusion is the opposite: a blacklist that requires a value to not be within a given set. Available options:

 1 class Car extends ActiveRecord\Model
 2 {
 3   static $validates_inclusion_of = array(
 4     array('fuel_type', 'in' => array('petroleum', 'hydrogen', 'electric')),
 5   );
 6 }
 7 
 8 # this will pass since it's in the above list
 9 $car = new Car(array('fuel_type' => 'electric'));
10 $ret = $car->save();
11 echo $ret # => true
12 
13 class User extends ActiveRecord\Model
14 {
15   static $validates_exclusion_of = array(
16     array('password', 'in' => array('god', 'sex', 'password', 'love', 'secret'),
17       'message' => 'should not be one of the four most used passwords')
18   );
19 }
20 
21 $user = new User;
22 $user->password = 'god';
23 $user->save();
24 
25 # => should not be one of the four most used passwords
26 echo $user->errors->on('password');

validates_format_of

This validation uses preg_match to verify the format of an attribute. You can create a regular expression to test against. Available options:

 1 class User extends ActiveRecord\Model
 2 {
 3   static $validates_format_of = array(
 4     array('email', 'with' =>
 5       '/^[^0-9][A-z0-9_]+([.][A-z0-9_]+)*[@][A-z0-9_]+([.][A-z0-9_]+)*[.][A-z]{2,4}$/')
 6     array('password', 'with' =>
 7       '/^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/', 'message' => 'is too weak')
 8   );
 9 }
10 
11 $user = new User;
12 $user->email = 'not_a_real_email.com';
13 $user->password = 'notstrong';
14 $user->save();
15 
16 echo $user->errors->on('email'); # => is invalid
17 echo $user->errors->on('password'); # => is too weak

validates_numericality_of

As the name suggests, this gem tests whether or not a given attribute is a number, and whether or not it is of a certain value. Available options:

 1 class Order extends ActiveRecord\Model
 2 {
 3   static $validates_numericality_of = array(
 4     array('price', 'greater_than' => 0.01),
 5     array('quantity', 'only_integer' => true),
 6     array('shipping', 'greater_than_or_equal_to' => 0),
 7     array('discount', 'less_than_or_equal_to' => 5, 'greater_than_or_equal_to' => 0)
 8   );
 9 }
10 
11 $order = new Order;
12 $order->price = 0;
13 $order->quantity = 1.25;
14 $order->shipping = 5;
15 $order->discount = 2;
16 $order->save();
17 
18 echo $order->errors->on('price'); # => must be greater than 0.01
19 echo $order->errors->on('quantity'); # => is not a number
20 echo $order->errors->on('shipping'); # => null
21 echo $order->errors->on('discount'); # => null

validates_uniqueness_of

Tests whether or not a given attribute already exists in the table or not.

 1 class User extends ActiveRecord\Model
 2 {
 3   static $validates_uniqueness_of = array(
 4     array('name'),
 5     array(array('blah','bleh'), 'message' => 'blah and bleh!')
 6   );
 7 }
 8 
 9 User::create(array('name' => 'Tito'));
10 $user = User::create(array('name' => 'Tito'));
11 $user->is_valid(); # => false

validate (custom)

Generic method allows for custom business logic or advanced validation. You can add your own errors to errors object. This does not take any parameters. You place this logic in a public method named validate.

 1 class User extends ActiveRecord\Model
 2 {
 3   public function validate() 
 4   {
 5     if ($this->first_name == $this->last_name)
 6     {
 7       $this->errors->add('first_name', 'can't be the same as Last Name');
 8       $this->errors->add('last_name', 'can't be the same as First Name');
 9     }
10   }
11 }
12 
13 $user = User::create(array('first_name' => 'Tito', 'last_name' => 'Tito'));
14 $user->is_valid(); # => false
15 echo $user->errors->on('first_name'); # => can't be the same as Last Name