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:
one: id: 1 username: sandal email: firstname.lastname@example.org
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.
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 => "email@example.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 => "firstname.lastname@example.org" ) 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 => "email@example.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
I promised the code would be crazy easy, and it is. Here are the updated definitions for
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
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 => "firstname.lastname@example.org") >> 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