Adding validations to an existing Rails application

Suppose you have an existing Rails application with a Person model that has name and age attributes.  Let’s make two people and save them in the database.

>> frank = Person.create(name: "frank", age: 22)
   (0.0ms)  begin transaction
  SQL (23.5ms)  INSERT INTO "people" ("age", "created_at", "name", "updated_at") VALUES (?, ?, ?, ?)  [["age", 22], ["created_at", Fri, 19 Jul 2013 02:42:16 UTC +00:00], ["name", "frank"], ["updated_at", Fri, 19 Jul 2013 02:42:16 UTC +00:00]]
   (1.2ms)  commit transaction
=> #<Person id: 1, name: "frank", created_at: "2013-07-19 02:42:16", updated_at: "2013-07-19 02:42:16", age: 22>
>> bob = Person.create(name: "bob")
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "people" ("age", "created_at", "name", "updated_at") VALUES (?, ?, ?, ?)  [["age", nil], ["created_at", Fri, 19 Jul 2013 02:42:36 UTC +00:00], ["name", "bob"], ["updated_at", Fri, 19 Jul 2013 02:42:36 UTC +00:00]]
   (2.6ms)  commit transaction
=> #<Person id: 2, name: "bob", created_at: "2013-07-19 02:42:36", updated_at: "2013-07-19 02:42:36", age: nil>

Let’s add a validation, so age is a required field for a Person.

# models/person.rb
class Person < ActiveRecord::Base
  attr_accessible :name, :age
  validates_presence_of :age
end

With the age validation, no new records can be added to the database, unless the age is present. The following record is not saved to the database:

>> Person.create! name: "phil"
   (0.0ms)  begin transaction
   (0.1ms)  rollback transaction
ActiveRecord::RecordInvalid: Validation failed: Age can't be blank

Adding the validation makes all of the existing records in the database that do not have an age invalid. In this case, Frank is valid, but Bob is not:

>> Person.all.map {|p| [p.name, p.valid?]}
  Person Load (0.5ms)  SELECT "people".* FROM "people" 
=> [["frank", true], ["bob", false]]

Having invalid records in the database is terrible because it causes unexpected bugs. For example, if we try to update bob’s name to Robert with the update_attributes method, it will fail:

>> bob = Person.find_by_name("bob")
  Person Load (0.3ms)  SELECT "people".* FROM "people" WHERE "people"."name" = 'bob' LIMIT 1
=> #<Person id: 2, name: "bob", created_at: "2013-07-19 02:42:36", updated_at: "2013-07-19 02:53:36", age: nil>
>> bob.update_attributes(name: "Robert")
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> false

When adding validations to an existing database, make sure to check all the existing records are valid before moving on to another task. Invalid records cause unexpected bugs and all records in a database should pass the database validations.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s