Rails 3 Baby Steps – Part 4

Go here if you want to see an index for the entire series.

In this post we are going to take a look at a typical CRUD controller in Rails. We will use a modified version of the controller generated by the command that we can in Part 3:

rails g scaffold Baby name:string age:integer

If we remember, this generated a ton of stuff for us (which can be good and bad), one of which was a controller called BabiesController. If you look in the controller you’ll see that a bunch of actions are in there. These actions were generated with important names, and one way to see that is by looking in our routes.rb file. In this file we will see that our entire controller is being wired up by one line:

resources :babies

So here we see what Rails calls a "resource route". It is a route which supports a common set of actions to support RESTful controllers. The standard actions supported are these:

  • index – Shows a list of all saved models.
  • show – Display a single read-only view of a model.
  • new – Renders a form to create a new model.
  • edit – Finds a model, then renders a form to edit it.
  • create – Where a new model is posted to be saved.
  • update – Where the edit form posts to be saved.
  • destroy – Deletes a model.

If you can, it is a good idea to use the resource routes and stick with the default naming. It’ll save a ton of typing and make thing more predictable. And don’t think that it’ll lock you down, the resource routes are completely configurable. You can add in custom routes and opt out of specific routes.

Now just because you have these standard 7 methods in your controller doesn’t mean that they will always be implemented exactly the same. Do you remember I said in a previous entry that people will often recommend against using scaffolding because it can create a bunch of so-so code which you don’t want and might not understand? Well, the generated controller is a perfect example of this.

The controller generates a ton of code so that you can GET and POST through a browser and using xml. Why did they decide to put code in there to return xml? Not sure, probably because they wanted to give you can example of how it works. But it doesn’t even really follow good Rails 3 convention and use respond_with at the controller level. We can strip a lot of code out of this controller to simplify it (you’ll find examples online that strip out even more code).

Here is the simplified version of the controller:

class BabiesController < ApplicationController
  # GET /babies
  def index
    @babies = Baby.all
  end

  # GET /babies/1
  def show
    @baby = Baby.find(params[:id])
  end

  # GET /babies/new
  def new
    @baby = Baby.new
  end

  # GET /babies/1/edit
  def edit
    @baby = Baby.find(params[:id])
  end

  # POST /babies
  def create
    @baby = Baby.new(params[:baby])
    if @baby.save
      redirect_to(@baby, :notice => 'Baby was successfully created.')
    else
      render :action => "new"
    end
  end

  # PUT /babies/1
  def update
    @baby = Baby.find(params[:id])
    if @baby.update_attributes(params[:baby])
      redirect_to(@baby, :notice => 'Baby was successfully updated.')
    else
      render :action => "edit"
    end
  end

  # DELETE /babies/1
  def destroy
    @baby = Baby.find(params[:id])
    @baby.destroy
    redirect_to(babies_url)
  end
end

Now if we point our browser to http://localhost:3000/babies we will see a listing, and then we can proceed to add, edit, delete, etc… All of the routes for our Baby model are wired up and working!

Testing The Controller

Now that we have taken a look at the controller, let’s go ahead and take a look at how we might test this controller with RSpec. Yes, we aren’t writing our tests first…shhhhhhh. I won’t tell if you don’t.

The first thing I want you to do is find the controller spec file. It will be located at "/spec/controllers/babies_controller_spec.rb". If you look at it all at once it can be a bit overwhelming. So for now, just ignore most of the file and we will look at it piece by piece.

First we start off this spec the same way we started off our model spec, by telling it what controller we are describing:

describe BabiesController do

Next we have a method that is going to be used in order to mock out our model:

def mock_baby(stubs={})
  @mock_baby ||= mock_model(Baby, stubs).as_null_object
end

This method has quite a bit going on. First, we are declaring an instance variable named "mock_baby". (In case you haven’t done much Ruby, that is what the @ means) Next we are using the ||= operator to create a mock only if the mock doesn’t already exist. This is a very common idiom in Ruby and you’ll see it all over the place.

Next we have the "mock_model" method which is a RSpec helper. It takes in the model class and a hash of values and returns a mocked instance of the class. This method is built specifically for Rails models, and does things like set the id to the next value in a sequence, sets values for specific methods like "destroyed?", "persisted?", etc… If you’re interested you can find the source here.

Finally, calling "as_null_object" just tells the mock to record and ignore all calls to it except those that are explicitly stubbed out.

Now that we know how this method works, lets go ahead and break down the first test in the file:

describe "GET index" do
  it "assigns all babies as @babies" do
    Baby.stub(:all) { [mock_baby] }
    get :index
    assigns(:babies).should eq([mock_baby])
  end
end

First we have the call to "describe" which just tells us that we are testing a GET against the index action. The call to "it" then tells us that we are specifically testing that the retrieved babies from the index action gets assigned to the "@babies" instance variable.

The next line we are stubbing out the "all" method on the Baby object. This is because our controller action looks like this:

def index
  @babies = Baby.all
end

You see, we are calling "Baby.all" in order to retrieve the babies. So we simply stub the "all" method and return the mock_baby. Then we call the "get" method provided to us by Rails (these aren’t part of RSpec) and we pass the action that we want to invoke.

After that we can make a call to "assigns" (which again is part of Rails) to get a value which was assigned as an instance variable inside of the controller. The call "assigns(:babies)" will return the value of the "@babies" instance variable that we assigned.

Then we just use the "should" method provided by RSpec and check that "@babies" is equal to the array of "mock_baby", which should be true! Pretty easy to setup and test. At least I think so! You’ll see a bunch more tests like this in the file, and a bit further down you’ll see some examples of writing tests with posts:

it "assigns a newly created baby as @baby" do
  Baby.stub(:new).with({'these' => 'params'}) { mock_baby(:save => true) }
  post :create, :baby => {'these' => 'params'}
  assigns(:baby).should be(mock_baby)
end

Here we are stubbing the "new" method on the baby, setting some parameters that we expect to see, and then return the mock baby with "save" set to true. Then we invoke the "create" action using the "post" method and pass the same parameters as a piece of post data.

Then we can check the "@baby" instance variable to see if is the mock_baby variable we created earlier. Pretty straightforward, but you have to decide for yourself how useful tests like these are for your code.

Linking To Actions

At this point I am going to switch gears and look at how we can generate links in Rails. The reason for this is that if we want to get to the babies controller, we have to know to type "/babies" after the site url, but instead we want to have a link from the root of the website.

If we are using resource routes then we get appropriate names applies automatically to the generated routes. Rails has a convention of taking a route name and then allowing you to append "_path" or "_url" to the name and exposing those as helpers. For example, if my route is called "babies" then I can use the variable "babies_path" and it will give me the path to that route.

You can also name your own custom routes so that you can get these same helpers. Unfortunately it is really hard to remember what all the routes are named, especially the ones that are generated for you. Thankfully the guys who made Rails thought of this as well, and created this task:

rake routes

This will write out all of your routes into the console, and you can see the names to the left. As an example, one of your routes will look like this:

babies GET  /babies(.:format)  {:action=>"index", :controller=>"babies"}

This is the "babies" route using the "GET" HTTP verb, with the path "/babies" and an action named "index" on the "babies" controller. If we wanted to link to this action we could simply make an anchor tag like this in our view:

<a href="<%= babies_path %>">Check out the babies</a>

There is also a url helper called "link_to" that we can exploit:

<%= link_to "Check out the babies", babies_path %>

Both of the two above code snippets do the same thing. Notice above I also said that instead of "_path" I said you could also use "_url". Well, the difference is that "_path" gives you a relative path to the action whereas "_url" gives you the full url to the action. If we look at the HTML generated from the links above, we will see that the link points to "/babies" and not http://localhost:3000/babies.

Now that you saw the other named routes for our resources, you may be wondering how you would use them. For instance, you see that the edit route for a baby is called "edit_baby", so you can safely assume that we can use "edit_baby_path" to link to that baby…and you would be right. But we can’t just say "edit_baby_path" and magically expect Rails to know which baby we want to edit. We have to specify this as a parameter to the helper.

If you go look at "/app/views/babies/index.html.erb" you’ll see where it is generating the edit links. Here it is passing the instance of the model to the link, and by default it knows to grab the id of the model to use for the link:

<%= link_to 'Edit', edit_baby_path(baby) %>

You could also change it to pass the id explicitly, and the same links would render just fine:

<%= link_to 'Edit', edit_baby_path(baby.id) %>

If you wanted to link to a single baby you see that the route is just named "baby", so you’d assume that you could just do this:

<%= link_to 'View my baby!', baby_path(baby) %>

And that works, but the link_to method also allows you to just pass the model itself:

<%= link_to 'View my baby!', baby %>

Very cool. As you are probably starting to realize, Rails goes out of its way to keep you from having to type unneeded code.

Wrap Up

I hope you enjoyed this entry in my Rails 3 series. In the next entry I am going to look at how we can implement delete links, and form posts to use our update actions. Hope to see you next time!

Be Sociable, Share!

One comment

  1. Another great article, Justin, thank you.

    Slightly off-topic, but I wonder if anybody has implemented url / routing helpers in ASP.NET MVC using dynamic types? That would be pretty cool.

Leave a comment