Rails 3 Baby Steps – Part 3

In the last entry in my Rails 3 series we looked at hooking up a Controller and View so that we could render a page in the browser. By doing this we technically had a working Rails app, but we were missing one piece of the MVC puzzle, the model! In order to really connect the dots, we need all three pieces in place.

But before we go there, I want to hook up one more piece in our Rails 3 application…RSpec. You have probably heard in the past just how crazy Rails developers are about testing. Well, everything you have heard is true. Not only is testing a very good thing, but testing in languages that are as dynamic and flexible as Ruby are absolutely critical. While I am certainly not a TDD wizard by any measure, I do enjoy a nicely written, tested codebase.

Because in Rails we sometimes like to generate code, I would like to go ahead and get RSpec pulled in and wired up so that when we start to create our model and controllers, we get some example test code along with it.


Setting Up RSpec

The first step is to add the RSpec-Rails gem to our test and development groups in our Gemfile. We don’t want to just add "rspec-rails" to our Gemfile like this:

gem "rspec-rails"

Because then we might get our version of RSpec switched out on us when we aren’t expecting. We want to specify a version so that we can update our dependencies in a predictable manner. So first we have to figure out what the latest version of the ‘rspec-rails’ gem is. This can be accomplished by running:

gem list "rspec-rails" --remote

This will list a few gems, but you will see the rspec-rails gem in the list, along with a version number (at time of writing, 2.5.0). So now we can add this to our Gemfile (don’t forget to put it in your test and development groups!):

gem 'rspec-rails', '~> 2.5.0'

So what is the little tilde arrow thingy for? Well, it means to use any version in the 2.5.0 minor release, but not 2.6. So it’ll use 2.5.1, 2.5.2, 2.5.3, etc… but it won’t upgrade to a new minor release. This is because breaking changes are much more likely to occur when switching between minor releases and these updates should occur consciously.

Now we just need to run "bundle" and watch as RSpec is installed:

bundle

Okay, RSpec should have installed itself into your gemset. Now if you run:

rails g

You’ll see that RSpec has created a generator for you called "rspec:install". We are going to run this generator:

rails g rspec:install

This will create a few files in your app, but what you really care about is the "spec" folder. This is where you will put all of your RSpec tests, also called "specs" or "specifications". Now what we need to do is to tell Rails to use RSpec when generating tests, and not generate tests using its built in framework Test::Unit. (you can stick with Test::Unit if you want, no harm done, but you’re on your own in this post)

Now that RSpec is installed, we need to tell rails to use RSpec as the default test framework when generating tests. This will happen inside of the application.rb file. To do this we just need to add the following code inside of our RailsBaby module (in application.rb):

config.generators do |g|
    g.test_framework :rspec
end

You can just put it in at the end of the module. Okay, so now we have RSpec ready to go, let’s go ahead run it with no tests just so see how it is going to work.

To run RSpec we will execute this from the console:

rake spec

At this point you haven’t seen rake yet, and we won’t delve into it yet, but I’ll just say that it is "make" for Ruby. It is a DSL for creating build scripts that are written entirely in Ruby. It can be used to automate just about any task.

After you ran that command, you probably just got an error complaining about a file called "schema.rb" and something about a database. In order for us to get past this error we are going to need to run this command:

rake db:migrate

I’ll get back to that command in a bit, but for now I’ll just say that it initializes your database. What database? Just go check out your Gemfile. Rails includes support for SQLite by default, so you already have a database without even knowing it! How is that for sweet?

Go ahead now and run "rake spec" again. This time you should see a message that RSpec didn’t find any specs to run. Which is good, because that means you have it running!

Creating Some Content

If you’ve looked into Rails before, you have probably heard of this thing they call "scaffolding". Scaffolding is simply generated code that creates a simple set of views, controller and model which have the basic CRUD operations.

Many people will tell you to avoid scaffolding, and that is probably good advice, but when learning Rails I found it to be a good starting point for creating our first end to end request. This way we aren’t Googling and hunting for every little piece of code to put in. Since our application is called Rails Baby, let’s go ahead and generate a controller, model, and views for our babies. Just run this command:

rails g scaffold Baby name:string age:integer

Here we are running the scaffold generator with our model name "Baby" and two attributes, one called "name" and one called "age". We have also specified the types for each of our attributes. Once we run this command you’ll see a huge amount of stuff generated, and this is usually why people say that using scaffolding is bad, because it generates a ton of so-so code that you don’t want or don’t understand. We’ll get back to that in a second.

But first let’s look at some of the files which were generated when we ran the previous command.

  • A file in the /db/migrate folder: This is called a migration, and it is what creates your database schema. We will look at this next.
  • A baby_spec.rb file: This is a test file for our baby model.
  • Some code into the route file: This route doesn’t look like anything we have seen before, again we will look at this in a bit.
  • A babies_controller.rb file. Notice that Rails properly pluralized the file name. Cool stuff. This can be customized in the /config/initializers/inflections.rb file.
  • A bunch of views for the babies.
  • Specs for the views.
  • A request spec: This is a spec that operates at the request level and allows you to do some integration testing with RSpec.

Migrations

The first thing that we are going to look at is the migration file. Migrations are a way to version your database. Go ahead and open the file and look at it. In this file you’ll find this:

class CreateBabies < ActiveRecord::Migration
  def self.up
    create_table :babies do |t|
      t.string :name
      t.integer :age
      t.timestamps
    end
  end

  def self.down
    drop_table :babies
  end
end

As you can see, there are two methods here, an up and a down. "up" is the method that creates the database, in this case the babies table. "down" is the method that undoes the migration. In this case it drops the babies table. The code you are seeing here is an api supplied by Rails to allow the creation of databases to be abstracted from the underlying database.

The general concept of migrations is that I can create these migration files and execute them locally to modify my database. Then when I go to commit my code, I will check in my migration files along with my source. When someone checks code out, they will run this command:

rake db:migrate

It will execute the "up" method on all of the migrations that haven’t been run yet (it stores the executed migrations ids in the database). It runs the files in order based on the filename. You probably noticed the timestamp that is generated in the migration filename when it was created. When you executed the above command you should see output in the console verifying that the babies table has been created. Now that we have created the needed tables, instead of pulling up the app just yet let’s first take a look at the model that we just generated.

The Model

The model that we generated is fairly simple, just look in /app/models/baby.rb:

class Baby < ActiveRecord::Base
end

Wow, nothing there! But didn’t we just add attributes to this class when we generated it? Yes, we did, but those were used for the migration. The model class itself doesn’t care what attributes it has, these are create dynamically from the database table.

Security Warning

We need to add one thing to our model which some might see as a pain, but it is absolutely essential. The problem is "over posting" and without some additional code, Rails is vulnerable to this flaw. I wrote about this before with ASP.NET MVC. In Rails there is a fairly simple solution that will work for you most of the time, it is to change your model to look like this:

class Baby < ActiveRecord::Base
  attr_accessible :name, :age
end

Now, whenever we create a model from form post parameters, only certain attributes can be assigned. All other parameters will have to be explicitly assigned. This will keep a hacker from "over posting" on your model and overwriting attributes that you don’t want them to be able to overwrite.

There is even a way to specify different sets of accessible attributes based on some state in the application, but that is a bit out of scope for what we are doing here. Go check out the docs for the MassAssignmentSecurity module.

Testing The Model

We know that we created a name and an age, so let’s just start from there. I want to add some simple validations onto my model to make sure that we aren’t allowing silly values to be entered. For instance, we want to require a name and we don’t want age to be less than zero. Sounds reasonable. First, let’s go ahead and find the spec that was generated for the Baby model and add a few pending specs to it. Go look in /spec/models/baby_spec.rb. At this point this file should be empty, but let’s go ahead and make it look like this:

require 'spec_helper'

describe Baby do
  it "is valid with proper values"
  it "is not valid without a name"
  it "is not valid without an age"
  it "is not valid with an age less than zero"
end

Here we have now defined four pending specs. If we ran RSpec now (with the "rake spec" command) it would tell us that we have four pending specs:

image

So let’s go ahead and fill out the first spec, we just want to create a baby model with some valid attributes, and then check to see if it is valid. RSpec has a "should" method that we can call on the model and then we can pass it a matcher:

it "is valid with proper values" do
  baby = Baby.new name:"Billy", age:12
  baby.should be_valid
end

In this test we are just creating a baby and giving it a valid name and age. In case you don’t recognize the synax, I am just calling "new" and then passing a Ruby 1.9 hash (the syntax has been updated a bit so that it looks more like JSON) which assigns values to each attribute. Next we are just checking that the model is valid using the "be_valid" matcher provided by RSpec. RSpec has a ton of these matchers, you can check out the docs if you want to see more, but we will probably talk about more of them in the future.

In the meantime, let’s go ahead and fill out the rest of those specs:

it "is not valid without a name" do
  baby = Baby.new age:12
  baby.should_not be_valid
end

it "is not valid without an age" do
  baby = Baby.new name:"Billy"
  baby.should_not be_valid
end

it "is not valid with an age less than zero" do
  baby = Baby.new name:"Billy", age:-12
  baby.should_not be_valid
end

You might be thinking "argh! These tests are brittle and repetitive!" And you would be correct! Let’s first get these tests passing, and then we are going to look at a gem called "Factory Girl" in order to clean up this mess.

Validations

If you ran the tests now, you’d see that all of them fail, except for the first one. We need to get some validation into this model, which you can guess is a built in piece of Active Record. In order to accomplish this, we only need to annotate the Rails model in this way:

class Baby < ActiveRecord::Base
    validates_presence_of :name
    validates_numericality_of :age, :only_integer => true, :greater_than => 0
end

Very easy. Rails has a good number of validations, and they are also extensible. If you want to dig a lot deeper into Rails validations, you should check out the Rails Guide on validations.

The first validation "validates_presence_of" just makes sure that the value is there and isn’t whitespace. The second validation makes sure that the value is a number, only an integer, and is greater than zero. This should satisfy all of our specs, so we are good to go. Just go ahead and run RSpec again (remember "rake spec") and now all of your tests should be passing.

Cleaning Up The Tests With Factory Girl

I mentioned earlier that our tests sucked. They suck because we are building a new baby in every test, and if anything changes, like an attribute being added or removed, these tests will start failing. And you’ll have to go and touch each and every one. This might not suck too much right now, but in the future when you have 1000 tests it will really suck.

Factory Girl lets us easily build instances of our classes to use for testing. Rails has a built in feature called "fixtures" for this same purpose, but Factory Girl is much more flexible. The first step is to get the "factory_girl_rails" gem installed by putting it in our Gemfile:

gem 'factory_girl_rails', '~> 1.0.1'

Now run the "bundle" command to install it. Once it is installed you can go to your "spec" folder and create a "factories" folder. In this folder create a file called "baby_factories.rb". In this file we are going to put this code:

Factory.define :baby do |f|
  f.name "Billy"
  f.age 12
end

This just creates a factory for us called "baby" and it automatically uses the naming to guess the model class that it will use. In one of our tests, if we wanted to just replace the code inline, we could do this:

it "is valid with proper values" do
  baby = Factory.build(:baby)
  baby.should be_valid
end

Notice the use of "Factory.build(:baby)", most Factory Girl tutorials you will see will just have "Factory(:baby)". The key difference here is that calling "Factory.build" does not save the model to the database, whereas calling "Factory" does. Depending on what kind of testing you are doing, use one or the other.

And you see that we have replaced where we were originally specifying our class explicitly. But we could arguably do a little bit better than this by using the RSpec’s "before(:each)" method and creating the baby object from the factory before each spec is run:

before(:each) do
  @baby = Factory.build(:baby)
end

it "is valid with proper values" do
  @baby.should be_valid
end

In later tests, you could just override the needed attribute values for your test:

it "is not valid without a name" do
  @baby.name = nil
  @baby.should_not be_valid
end

Although some people would prefer to specify the factory in every test, because you can just override attributes on creation:

it "is not valid without a name" do
  baby = Factory.build(:baby, name:nil)
  baby.should_not be_valid
end

I guess it depends on what kinds of tests you were writing as to which form is more useful. I would recommend considering it on a case by case basis.

Wrapping It Up

In this installment of my Rails 3 baby steps series we learned a good bit about testing. After creating the model in the last entry we setup RSpec and started using it to run some tests against it. Then we added a few small validations to our model and made sure those were working. Finally we cleaned up our tests a bit by including Factory Girl. I really hope you enjoyed this entry in the series, and I hope you’ll come back and check out the next entry where we will dive into a full CRUD controller and how it works.

Be Sociable, Share!

13 comments

  1. Good overview, Justin. A few quick corrections, if you don’t mind me interjecting too much:

    1.) Factories and default strategies

    First, what `Factory(:foo)` does is actually dependent on the `default_strategy` setting for that factory — it’s not a separate thing unto itself. When you define a factory, you can set the default strategy to a symbol, which is the method that will be invoked when you call `Factory(:foo)`.

    In this case, you didn’t set a strategy, so the default is used, which is `create`. Thus, `Factory(:foo)` is the same as calling `Factory.create(:foo)`.

    The reason why you might want to change the default strategy is that sometimes you have a very expensive object that isn’t worth creating and then immediately discarding. In those sorts of cases, it’s better to `:default_strategy => :build` and make your tests go a little faster if you don’t need to actually save it to your backing store.

    2.) RSpec and let(…)

    I noticed you used instance variables in the tests. The generally recommended alternative is to use a `let` block, so your `baby` example would look more like:

    let(:baby) { Factory(:baby) }; …

    and your `before(:each)` block would disappear.

    The use of instance variables is typically frowned upon for a couple of reasons.

    – First, instance variables come into being once they’re referenced for the first time. That means that if you misspell an instance variable, you get a new one that is created and then initialized to nil, which leads to errors that can be obnoxious to detect.

    – Using `let` creates a a method, so if you mess up, you’ll get a NameError when you misspell. This also makes it marginally easier to refactor.

    – If you have a `before(:each)` hook, it’s going to run before each example, even if that example doesn’t use any of the instance variables. By contrast, `let` only runs if the spec calls it.

    3.) Small typo

    Also, there’s a small error in your validates_numericality_of line (it looks like your blog decided to replace the `:o` of `:only…` with a smiley).

    4.) 1.9 hash syntax

    This is a bit subjective, but I don’t like the 1.9 hash syntax `{a: ‘foo’, b: ‘bar’}` instead of `{ :a => ‘foo’, :b => ‘bar’ }`, since it has a number of limitations. It’s great for web development, where you might want to read in a literal JS hash and have it directly map to Ruby. But I’d use it for that or similar reasons, not in regular code.

  2. Justin Etheredge

    @John Thanks for the feedback! This is why I post stuff like this :-)

    1) Awesome, I have not dug too deeply into Factory Girl. I had only used it at its most basic level, in order to replace fixtures.
    2) I will look into this. I was actually following the model recommended in the RSpec Book by PragProg. If “let” is used, won’t it memoize the result and then we would be sharing state across specs? I would think that this would be a bad idea in general.
    3) Thanks!
    4) Come on! :-) I love the new hash syntax.

  3. @Justin: `let` memoizes the result, but when you’re not modifying the result (as you’re doing with the validation tests), that’s a lot better than creating an object and throwing it away every time.

    Also, all the “should not be valid without a name” stuff is really testing the framework, not the behavior of your app. In that particular case, you’re now checking that `validates_presence_of` does the right thing, which isn’t quite what you want.

    The tests can get a lot simpler. If you observe that what you probably mean to ask is, “did I put a validates_presence_of on this block”, then you can do this instead:

    describe Baby { it { should validate_presence_of :name } }

    How’s that for a one-liner? :)

    ~ jf

  4. Great intro to RSpec and unit testing in rails. This area seems to always be left for last in the tutorials. Glad to see you got to it pretty early.

    I liked the tests when they were, “brittle and repetitive”. Validation tests will always be brittle, but I thought they were easier to read when each test block described the setup required for the test.

    I look forward to the next tutorial.

  5. I’d also recommend using the new “sexy validation” in Rails 3 where you would replace “validates_presence_of :name” with “validates :name, :presence => true”.

    This lets you apply multiple validators to a given attribute on a single line.

    A good blog post on it is at http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/

  6. Justin Etheredge

    @James Awesome, I’ll check it out!

  7. Hey,
    Quick point about your tests.
    From how I learned to write tests you should test the first case to pass or fail.
    So for age instead of testing fail case as -12 it should be -1. And for passing age you should be testing for 0 (A test that would fail, I think, because you are making sure that age is greater than 0).

    The reason for this is say when you were typing up your validations your finger slipped and you hit ’9′ instead of ’0′. Both your tests would still pass. You want to make sure that the line between pass and fail is properly tested.

    Any one agree or disagree?

  8. Denny Ferrassoli

    Really appreciate the work you’re putting into these tutorials! Keep up the good work. Anxious for the next part.

  9. Congratulations for the best simple explanation of Factory Girl that I’ve come across on the web, the documentation is sparse for a newbie like me.
    I’ve now been able to use Factory Girl in unit tests successfully but am struggling in functional tests because I can’t get my head around how to replicate standard fixtures, such as Users, which are required to be logged in: eg

    def login_as(user)
    @request.session[:user_id] = users(user).id
    end

    The controller code is: login_as(:eugene)
    and the fixtures are:

    eugene:
    email: ‘eugene@example.com’
    hashed_password: ‘e5e9fa1ba33a663f05f4′ # => secret
    lauren:
    email: ‘lauren@example.com’
    hashed_password: ‘e5e9fa1ba31ec63f05f4′ # => secret

    Your advice would be most welcome
    Best regards
    Alan

  10. Justin, all sorted.
    I had two factories, an article that belonged to a user.
    I was missing the fact that Factory(:article) generated a user record, allocated a user id and then created an article record.
    Looking forward to your next post

  11. Thanks Justin for a good tutorial!

  12. thanks for this tutorial. It clearly illustrates how Rspec is being used in rails 3 in terms of the model specs.
    Can you please write more tutes to show rspec works with controllers and views in rails 3?
    I’ve been using the standard rspec generators and my specs for controllers & views do not work properly and there’s not too many good references around. I even have the rspec book.

    Thanks again :)

  13. Hey mate. I wanted to thank you for these tutorials. I’m new to rails and have been struggling with the official Rails Guides… even though they’re easy, I have bad comprehension sometimes when it comes to code. Your guides are easy to follow and I love that you explain things in more than just a black-and-white manor. Thanks! -Nick

Leave a comment