Defining the language of a Ruby on Rails application

Previously, I created some wireframes and setup the initial migrations to build out the tables for my application. Now it’s on to creating the models so I can actually use Rails to save data.

Generating a model

I already generated all of my tables, but I really could have done it one table at a time. With Rails v1.1 the script/generate model command defaults to generating a migration script for the model. Luckily, you can tell it to skip this with the appropriately named option of —skip-migration.

script/generate model User --skip-migration

Since I skipped creating a migration file in db/migrate, generating the User model created the following files:

app/model/User.rb
test/fixture/users.yml
test/unit/user_test.rb

With the empty template files created, I decided to write my tests first. They should mostly fail, but the point is to add the approprate code to the model to get them to pass.

My problem creating unit tests

Starting out, I’m relying heavily on reference. I’m sure a lot of people are using the Agile Development with Rails book. The basic tests I started out with ran okay. However, when I started working with fixture data as described in the book, I started getting errors like this:

RuntimeError: Called id for nil, which would mistakenly be 4
 -- if you really wanted the id of nil, use object_id

After a quick search for the error on the web I found Mike Clark’s description of what the problem was. At some point, the defaults for the way Rails runs test changed in order to improve performance. Therefore, the methods described in the book no longer work (at least the first printing I have). If you had a fixture named fred in your test/fixtures/users.yml file, you used to be able to reference it as @fred in your test functions. You can’t do this anymore by default. I therefore changed references like @fred to the now preferred format of users(:fred).

On with the unit testing

With my problem figured out, it was on to test scripts. Unit tests are used to test models. I started out with some very simple models to get my feet wet before tackling ones with more complex behavior.

Since testing is a large topic, I’ll leave the samples and details to other sources like A Guide to Testing the Rails and just give you an example of a test function used to make sure that a user can’t be created with a username that already exists.

 # use function names that would make sense in a story
def test_that_we_cannot_create_a_username_that_already_exists
   good_user = User.new
   good_user.username = "testuser"
   good_user.password = "password"
   #
   # this user should save so assert that it does
   #
   assert good_user.save, good_user.errors.full_messages.join("; ")
   #
   # now try to add a duplicate username
   #
   dupe_user = User.new
   dupe_user.username = "testuser"
   dupe_user.password = "password"
   dupe_user.save
   #
   # This user should not have been saved
   # so only cause a test failure if it DOES exist
   #
   assert_raise(ActiveRecord::RecordNotFound) { User.find(dupe_user.id) }
end

I could have used some fixtures to simplify things, too.

The problem with tests are that unless the model has the rules in place to enforce them the tests fail. Therefore, we need to add the appropriate code to our models so our tests pass.

Making the unit tests pass by creating the model

Since I was able to quickly write up some tests that said my models weren’t doing what I wanted them to do, I had a small bit of work to do. Luckily to prevent duplicate usernames, there was only two lines I need to add the logic.

validates_presence_of :username
validates_uniqueness_of :username

The first line just makes sure we have a username and the next informs Rails to make sure the value is unique. This was all that was necessary to have my test pass.

Based on what I’ve been through so far, most of your validation should be about as easy as what you’ve seen. You can definitely get more complex. The application I’m working on has voting periods which are not supposed to overlap each other. Unit tests helped me out a great deal by allowing me to quickly put the code into place to make sure when I add a voting period, it doesn’t conflict with the date range from another one.

Next steps

I have a lot of unit tests and rules and relationships in my models now. There’s still some missing stuff here and there, but I’ll be able to add it later when I need it. All my current tests will be useful later on to make sure I don’t break anything.

My next stop is to start setting up controllers, create functional tests and get some web pages working. However, one thing I need to think though first is authentication and security. Even if I’m not initially checking security, it’s probably better in the long run to have the function calls in place from the start. At the very least, I should have an idea for how I plan to implement it.

Commenting has expired for this post.