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:
# maps to /people - named route of person_url
# HTTP verb: GET
def index
@people = Person.find(:all)
respond_to do |format|
format.html
format.xml { render :xml => @people.to_xml }
end
end
# maps to /people/new - named route of new_person_url
def new
@person = Person.new
end
# 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
# maps to /people/:id - named route of person_url(person_object)
# HTTP verb: GET
def show
respond_to do |format|
format.html
format.xml { render :xml => @person.to_xml }
end
end
# maps to /people/:id;edit - named route of edit_person_url(person_object)
def edit
end
# gets called for PUT to /people/:id
def update
@person.attributes = params[:person]
@person.save!
respond_to do |format|
format.html do
flash[:notice] = “Person updated”
redirect_to person_url(@person)
end
format.js
end
end
# maps to /person/:id;destroy
# HTTP verb: DELETE
def destroy
@person.destroy
respond_to do |format|
format.html do
flash[:notice] = “Person destroyed”
redirect_to people_url
end
format.xml { render :nothing => true }
end
end
protected
def find_person
@person = Person.find(params[:id])
end
end
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
New person
< % form_for :person, @person, :url => person_url, :method => :post do |f| %>
< %= render :partial => “form”, :object => f %>
< % end %>
app/views/people/show.rhtml
< %= h @person.name %>
< %= h @person.bio %>
< %= link_to "Edit", edit_person_url(@person) %>
app/views/people/edit.rhtml
Edit person: < %= h @person.name >
< % form_for :person, @person, :url => person_url(@person), :html => { :method => ‘put’ } do |f| >
< %= render :partial => ‘form’, :object => f >
< % end >
app/views/people/_form.rhtml
< %= form.text_field :name >
< %= form.text_area :bio >
< %= submit_tag %>
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.
August 1st, 2006 at 9:18 am
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
August 1st, 2006 at 9:21 am
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.
August 3rd, 2006 at 5:15 pm
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
August 10th, 2006 at 7:19 pm
[...] REST and Rails - no reason to be scared A quick tutorial on setting up a RESTful controller. It’s not hard. (tags: programming ruby rails rest web2.0) [...]
August 14th, 2006 at 7:41 pm
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
August 16th, 2006 at 1:32 pm
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?
August 17th, 2006 at 6:25 pm
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.
August 20th, 2006 at 1:44 am
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…
September 14th, 2006 at 3:08 pm
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?
September 14th, 2006 at 3:29 pm
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.
September 15th, 2006 at 12:58 pm
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)
September 23rd, 2006 at 1:09 am
[...] REST LINKS REST and Rails - no reason to be scared at Meta | ateM [...]
January 26th, 2007 at 6:59 am
[...] REST and Rails no reason to be scared [...]
June 5th, 2007 at 3:40 am
i still didnt get a error message after including error_message_for
August 24th, 2007 at 11:42 am
@dhaval
Did you render the action or redirect? If you redirect, you effectively refresh your instance variables, and your error object disappears.