advertisement

Print

Understanding ActiveRecord: A Gentle Introduction to the Heart of Rails (Part 2)
Pages: 1, 2, 3, 4

Introducing test/unit

Before we go to playing around with the model, let's make sure that it checks for unique user names and email addresses. We could just add validations to our model, then toy around in script/console to make sure that the proper errors are getting set up, but that gets old quickly, and it also doesn't do much for making sure that the behavior is maintained over time.



Instead, this time around we're going to write some unit tests. This is an automated way of making sure our expectations are being met, and once you get used to it, you'll never want to code without them. Since Rails apps dedicate a whole database to testing, we might as well make use of it.

Rails is designed to be test-driven, so it includes plenty of facilities to help you along. The first one we will need is fixtures, which allow you to set up some sample data to work against.

We will start with just one user, only filling in the essentials:

test/fixtures/users.yml
  one:
    id: 1
    username: sandal
    email: gregory.t.brown@gnospammail.com

We are going to want to check to see that a user needs to have a unique username and email address to be considered a valid record. To check this, we need three different cases.

test/unit/user_test.rb
require File.dirname(__FILE__) + '/../test_helper'

class UserTest < Test::Unit::TestCase

  fixtures :users
  def test_user_is_unique

    # -- user must have a unique username
    user = User.new( :username => "sandal",
                     :email => "greg7224@gnospammail.com" )

    assert(!user.valid?, "Should not save user unless username is unique")
    assert(user.errors.invalid?(:username), "Expected an error for duplicate username")

    # -- user must also have a unique email address
    user = User.new( :username => "shoe",
                     :email => "gregory.t.brown@gnospammail.com" )

    assert(!user.valid?, "Should not save user unless email is unique")
    assert(user.errors.invalid?(:email), "Expected an error for duplicate email")

    # -- If both username and email are unique, record should be considered valid.
    user = User.new( :username => "shoe",
                     :email => "greg@gnospammail.com" )

    assert(user.valid?, "Expected to save record but did not")
  end
end

That might look a little imposing at first, but if you look at it, we're really just codifying our expectations. We haven't added the validations yet, so we should expect these tests to fail, and they do, giving us a helpful message (truncated for clarity):

  $ rake test

    1) Failure:
    test_user_is_unique(UserTest) [./test/unit/user_test.rb:10]: 
    Should not save user unless username is unique.
    <false> is not true.

Let's add the username validation to our model(app/models/user.rb) and run the tests again.

  class User < ActiveRecord::Base  
     validates_uniqueness_of :username
  end

Sure enough, adding the declaration above makes some progress, we get a new failure.

  1) Failure:
  test_user_is_unique(UserTest) [./test/unit/user_test.rb:21]:
  Should not save user unless email is unique.
  <false> is not true.

By editing the validation to include the email address, all of our tests pass:

  
  class User < ActiveRecord::Base  
     validates_uniqueness_of :username, :email
  end

This is a bit of an oversimplified example, but hopefully you're already feeling the confidence boost of having tests covering your expectations. Most good Rails programmers practice Test Driven Development, so it is a good habit to get into. Also, this is only scratching the surface of the kind of testing you can do, but since that's an entire topic on it's own, this will have to suffice for now.

Adding Entries to Users

Now that we have a User model, we can hook up Entry to it. The type of association we're dealing with for this particular relationship is one-to-many. Any given user will have many entries, but any given entry will belong to exactly one user. The relevant keywords for this in Rails are has_many and belongs_to.

I promised the code would be crazy easy, and it is. Here are the updated definitions for Entry and User after adding the relationship, note that it's a total change of two lines of code:

class User < ActiveRecord::Base
  validates_uniqueness_of :username,:email

  # -- tell User it has a collection of Entry objects associated with it
  has_many :entries
end

class Entry < ActiveRecord::Base

  validates_uniqueness_of :url

  # -- Tell Entry that it has a unique User associated with it.
  belongs_to :user

  def created_date
    created_at.strftime("%Y.%m.%d")
  end

  def updated_date
    updated_at.strftime("%Y.%m.%d")
  end

  protected

  def validate
    if short_description =~ /rube goldberg/i
      errors.add("short_description", "can't include references to Rube")
    end
  end

end

There is one small change we need to make to our database schema before this will work. Most of the grunt work is handled by Rails conventions, but our Entry records need to have a place to store the user's database ID for this all to work. We can add a column for this with a simple migration.

$ script/generate migration add_user_id_to_entry
class AddUserIdToEntry < ActiveRecord::Migration
  def self.up
    add_column(:entries,:user_id,:integer)
  end

  def self.down
    remove_column(:entries,:user_id)
  end
end

When you run rake db:migrate it should add the user_id column, and we've now successfully hooked up User to Entry. Here is a quick script/console session to show our progress. I've narrowed the output down just to the interesting bits.

>> user = User.create(:username => "sandal", :email => "gregory@test.com")
>> user.entries
=> []
>> user.entries.create(:url => "google.com", :short_description => "Search Engine")
>> user.entries.find_by_url("google.com").short_description
=> "Search Engine" 
>> user.entries.length                                     
=> 1

Pages: 1, 2, 3, 4

Next Pagearrow