Keep up with your Rails tests

I haven’t updated my progress on the project I’ve been working on in a while. This is somewhat due to simply getting caught up in the development of it. I’ve also been working the past few weeks on creating the CSS/HTML based on the delivered design. Here’s two things I’ve learned about my experience since last time…

Don’t forget about running your tests

While I think I did a good job creating unit tests, I definitely let my functional tests slide. I have also not been religiously running my existing tests, so it wasn’t much of a surprise that a number of them were failing when I ran them again recently. As I fix them up, I’ll be keeping track of where they would have helped me out.

One thing I do know is where I need to have functional tests. I’m not nearly as concerned with pages which do nothing but display information, mostly ones where it would change, or redirect you based on security.

IE7 is much easier to style than IE6, but still not quite as good

Okay, This isn’t really Ruby on Rails specific. This assumes you create styles like I do. Create your CSS to work in Firefox and Safari and then get it to work in IE. I did run into some IE7 weirdness, but overall, there will be far less tweaks with it than you’d have with IE6. I created separate stylesheets with IE6 and IE7 fixes and the IE7 one is roughly 80% smaller than the one for IE6.

Agile Web Development 2nd Edition

If you’ve got the first edition of this book or about to buy it to get yourself started with Ruby on Rails, don’t buy it. The 2nd edition is now available in a beta form and it’s well worth it. The most recent additions to Rails really the original print largely obsolete. Yes, that much has changed.

Getting a little off track with my project

I got to a point where I think I’m getting a little off track with my Rails project. In trying to make sure everything’s perfect along the way, I think I’ve lost sight of the rapid development part of it all. When building my models, I wrote out a lot of tests to make sure things were working. While I am glad I did that, it isn’t the way to get the application in a usable state right away.

I’m very glad I did write the unit tests, since I’ve already had to make a change where the tests were correct but my models stopped working. Running the unit tests helped me fix the problems quickly.

As I proceed to build the controllers, I’m doing what I can to get the pages to work navigationally so people can login and click around the site and see at least static content. I’ll go back in and add actual logic, including funtional tests, as I code the details.

It just dawned on me that I’ve been doing a lot of work and my co-workers haven’t been able to actually see the site. This is Ruby on Rails, so I figured that was backwards. I guess I’m still getting used to it all.

I have decided that authentication plugins for Ruby on Rails suck

My first thought when it came to implementing the authentication for my current application was to check out plugins and generators others are using to make adding this a simple five minute process. I had read about some issues with one of the first such plugins like this from when Rails was first made available, but I figured by this time they had matured a bit. I checked out a few, the primary contenders being the acts_as_authenticated plugin and the LoginEngine. LoginEngine had a nice video demonstration showing how easy it was to integrate with your site. I was going to need role-based security and part of what won me over was that LoginEngine had a sister engine for dealing with just that.

After an entire day of playing around with LoginEngine and not getting anywhere close to the results I wanted, I decided to scrap using it and any other ready-made authentication system and just write my own. I ended up wasting more time failing to adjust LoginEngine to my needs than just writing it from scratch based on information in the Agile Development with Rails book and the upcoming Rails Recipies.

But hold on, these things are great. Lots of people are using them.

Lots of people may be using them, but they are not great in all situations. I can only see them saving time in applcations where they would work out-of-the-box without modification. I do have some custom requirements which are not built into any of these plug-and-play systems.

Here’s what’s wrong

  • If you’re learning Rails, using an authentication system will not teach you anything.
  • In a lot of cases, you’re not saving much time.
  • If you need to change much with what’s there, it will take you longer than if you had written it yourself in the first place. I’m sure if you had a lot of knowledge about them, this would not be as big an issue, though.
  • David Heinemeir Hanson is vocal about his cautious view on Engines and other high-level components.
  • Just the other day, the release of Rails v1.1.1 broke LoginEngine. Look through the comments. The Engine people fixed it quickly, but it broke.

This sums it up

David Heinemeir Hanson summed up how I feel about these things from my short-lived experience with them.

The short summary is that high-level components are a mirage: By the time they become interesting, their fitting will require more work than creating something from scratch.

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.

More Articles

Page 7 of 10 » ‹ First  < 5 6 7 8 9 >  Last ›