REST and Rails – no reason to be scared

I’ve been meaning to try simply_restul for a while now, and today’s announcement on rails-core pushed me over the edge. It’s no longer a plugin, it lost it’s name and changed a few things, but it’s pretty much the same. I’m going to do a quick tutorial on setting up a RESTful controller. It’s not hard — I was just scared of it!

rails simple

This creates a new rails project named “simple”. Without a -d flag, it will use mysql by default

cd rails

mysqladmin -u root create simple_development

mysqladmin -u root create simple_test

create our databases – if this were a longer tutorial, we’d test it as we went.

rake rails:freeze:edge

Make your project use the latest version of rails “Edge”. You have to do this if you want to follow along. In the future, this step will be unnecessary

script/generate model Person

Our example object – a Person
[source:ruby]
db/migrate/001_create_people.rb:
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column "name", :string
t.column "bio", :text
end
end

def self.down
drop_table :people
end
end
[/source]

rake db:migrate

We’re creating our table now using a migration. If everything went well, you’ll now have a table in your database – “people”, with two columns, name and bio.

Add the following line to your config/routes.rb

map.resources :people

(this is a change from simply_restful, plural now) Here is where the magic happens. This one line of code will give you a bunch of stuff. All the mappings between REST and your controller will now just work.

script/generate controller people index new create show edit update destroy

Create our controller with all the REST / rails actions.

app/controllers/people_controller.rb:

Here’s a lot of code. This is the person controller and all the actions that you want to do on a person. They map to HTTP verbs very nicely. You should type the code (the generator made a lot if it for you, you just need to fill in the methods)

You’ll notice the funny “respond_to do |format|” stuff. This is one of the other slick features: you can send the client the format they want. So if someone were to request /person/1.xml – they clearly want xml output – this handles that (and so much more).

That was all fine and good. We could write a very nice test suite for all the code to now and still be lost on the views. Let’s go through them quickly.

app/views/people/index.rhtml

app/views/people/new.rhtml

app/views/people/show.rhtml

app/views/people/edit.rhtml

app/views/people/_form.rhtml

This concludes the whirlwind tour of the new RESTful Rails controllers.
I’ll be changing this post a lot based on feedback, so check back.

15 thoughts on “REST and Rails – no reason to be scared”

  1. Hi,

    thanks for the insights. Great stuff. One thing though: Maybe you are able to extend your tutorial with a relationship. I would be very happy to use such elegant urls as /people/2/projects;new /people/2/projects/1 and so on. But I’m stuck on the routes for this.

    Cheers,
    Jan

  2. Jan, thanks for the comment. I’m not that far in REST yet, but someone was talking about nested resource mapping yesterday in a chat, I’ll have to go ask them.

  3. Jan: From the README, it appears that you can add a path_prefix to the map.resources call:

    map.resource :message, :controller => “categories”,
    :path_prefix => “/category/:category_id”,
    :name_prefix => “category_”

    # –> GET /categories/7/messages/1
    # has named route “category_message”

    Question for Kastner or anyone else: Is there a snappy way to handle errors, for example, if an xml request asks for a Person that doesn’t exist? Currently, my app is spitting out the default HTML rescue page when I request /people/2 (a non-existent record).

    Duane

  4. Jan,

    You can also nest your calls to map.resources:

    map.resources :people do |people|
    people.resources :projects
    end

    which will generate routes like:

    /people/1/projects/2

    CAUTION: I’ve never actually used the new routing yet but I’m pretty sure this syntax is correct.

    Bob

  5. This Restful stuff is nice and shiny, but where does all the validation and error handling go?

    To cite some of your code:

    # gets called when for POSTs to /people
    def create
    @person = Person.new(params[:person])
    @person.save!
    respond_to do |format|
    format.html do
    flash[:notice] = “Person was successfuly created”
    redirect_to person_url(@person)
    end
    format.js
    end
    end

    In here I see that you’re not dealing with the exception at all, are we just giving up the entire nice error handling functionality of Rails for extra restfullness, or am I missing something obvious?

  6. Don, I don’t have the answer for that, I’m still working on that myself. But Technoweenie’s Restful resource generator handles the errors in a very slick, very rubyish way – it rescues AR errors.
    Nice stuff.

  7. Don,

    I just looked at the resource generatory Kastner mentioned and ripped two lines of code out of it & dumped it into my controller (which is basically identical to the example you posted above).

    I used to handle my ‘new’ and ‘edit’ actions in the same function (called ‘edit’). If an ID was passed in as a param, it would edit that ID, If no ID was passed in, it would create a new record.

    The ‘save!’ function throws an exception instead of returning ‘false’ like ‘save’ does…so we need to ‘rescue’ that exception. The new RESTful stuff breaks that logic, as you have obviously found out…

    Here is my original code (generated from ‘crud2_generator’ with SLIGHT modifications):

    def create
    @person = Person.new(params[:person])

    @person.save!

    respond_to do |format|
    format.html do
    flash[:notice] = “#{@person.first_name} has been added to the database…”
    redirect_to :action => “index”
    end
    format.js # Renders create.rjs
    format.xml do
    headers["Location"] = person_url(@person)
    render(:nothing => true, :status => “201 Created”)
    end
    end
    end

    In order to get the error messages ‘we have all grown to love’ back, all you need to do is take this code & throw it at the end of the function above:

    rescue ActiveRecord::RecordInvalid
    render :action => ‘new’
    (*taken from: “tech’s resource generator”:http://svn.techno-weenie.net/projects/plugins/resource_generator/generators/resource/templates/controller.rb)

    As long as you have the ‘error_messages_for’ call in you view…they should show up again now.

    I have not used this generator yet. I am currently using ‘crud2_generator’ to create my models/controllers, but adding these two lines of code took care of it for me.

    I hope that helps…

  8. I’m interested in seeing some associations in action.

    For one, I don’t see the significance of: GET /users/1/messages/3
    How is this not the same as: GET /messages/3

    The simple cases look absolutely alluring. Do associated REST URLs have the same beauty?

  9. Derek – when you are nesting resources, GET /users/1/messages/3 will go to the messages controller, “show” action with params[:id] = 3 AND (here’s the magic) params[:user_id] = 1

    I’m still putzing around with map.resources :messages AND map.resources :users { |user| user.resources :messages }
    which lets you do BOTH ways.

  10. All right… So you use a GET request, passing the user_id in the URL as opposed to using POST.

    Do you get a cleverly named URL for free in this case? Something like message_url(@message, @user)

  11. Pingback: REST and Rails
  12. @dhaval

    Did you render the action or redirect? If you redirect, you effectively refresh your instance variables, and your error object disappears.

Comments are closed.