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.
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.
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.
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:
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.
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.
I’ve
settled into a new project and I’m excited that I’ll be using Ruby on
Rails to develop it. With the recent release of Rails v1.1, it seems to
be a great time to start. I’m going to try to stay on a weekly schedule
during development. Hopefully I’ll hit upon most of the issues someone
has when they’re initially getting into Rails and I’ll be able to
provide some helpful information.
What am I building?
First
off, while I would have loved to just dive into the code and start
building things, this project turned out to not be the best to do that
with. I started by building out wireframes. The main reason was that I
was building off of someone else’s vision and the wireframes offered a
way to learn what that was very quickly and also provided our designer
the same information and something he could build a design from. It
took roughly three days and I now feel I know what I need to do.
I created the wireframes using Visio with the templates and stencils available at Garrett Dimon’s site.
Using these will save you a whole lot of time if you need to build some
wireframes. I would use Omnigraffle on Mac OSX, but I’m just more
familiar with Visio.
Sketching the database
Once
I had a better understanding of the site, I went to work on the tables
I’d need in the database. I generally use pen and paper initially to
draw out the tables I think I’ll need along with the relationships.
When this is ready to become more finalized, I then go back to Visio to
create an ERD diagram and start adding fields. Again, my familiarity
with Visio is why I chose it. If anyone has recommendations on a good
ERD tool for OSX, I’d be interested in checking it out.
My
intent with the ERD is not to create a living, breathing document
connected to the database. I’d love that, but it’s more about quickly
coming up with a starting point to get into creating Rails migrations
and creating the models. You’ll notice some handwritten notes, too.
These were pointing out things I missed which then get updated in the
document.
Migrations
If you’re not familiar with Migrations, here’s a good rundown on it. You can get further information on the Rails wiki UsingMigrations page.
Basically, they allow you a nice method for updating and rolling back
changes to the database. You can also add and alter data, as well.
This
was straightforward for the most part, the only part I had to research
was in creating some join tables in a has_and_belongs_to_many
relationship. These tables don’t need an id field and the create_table function adds it by default. There’s an :id option you can set to false to suppress it. Here’s an example:
create_table :messages_images, :id => false do |t|
t.column :message_id, :integer, :null => false
t.column :image_id, :integer, :null =>false
end
Next steps
Building out the language of the application with unit tests and models. From the first Dallas Ruby Brigade
meeting, Adam Keyes mentioned that he starts his Rails apps by creating
his models, which defines the language he’ll use in the rest of his
application. This made a lot of sense to me so that’s the approach I’ll
be taking.