Article

Home » Server-side Coding » Ruby & Rails » Rapid RESTful Rails Apps -- No, Really!

About the Author

Myles Eftos

author_myles Myles Eftos is a Perth-based web developer that jumped on the Rails express and never looked back. He is the event co-ordinator for the Australian Web Industry Assocation which explains why most of their events are at the pub near his house.

View all articles by Myles Eftos...

Rapid RESTful Rails Apps -- No, Really!

By Myles Eftos

February 29th, 2008

Reader Rating: 9

Page: 1 2 Next

As you may have read in my blog post, RESTful Rails Part I, RESTful Rails is a way of mapping HTTP verbs (GET, POST, PUT, and DELETE) to CRUD actions (Create, Read, Update, and Delete) in your Rails apps. If that sentence was complete gibberish to you, take some time to read the blog post and it may make a bit more sense.

Alright, I assume you've had enough to time to absorb my post, and you're full of CRUD and REST. Now, it's time to build an actual example.

REST Tumblelog

For this example, we'll build a (very) basic tumblelog-type app. What's a Tumblelog? Well it's like a blog but without the essays. A Tumblelog is a jumble of frequent but short posts that are typically just a link, a quote, a quick comment, and so on.

To start, you'll need to create a new rails project called rest_tumble, with one model and one controller. So fire up you favourite console and run:

rails rest_tumble  
cd rest_tumble  
script/generate model post  
script/generate controller posts


Open up db/migrate/001_create_posts.rb and edit it to look like this:

class CreatePosts
 def up      
   create_table do |t|
     t.string :message, :null => false, :limit => 140        
     t.timestamps      
   end    
 end    
 def self.down      
   drop_table :posts    
 end  
end

Then, run:

rake db:migrate

Finally, add some validation to the post model (app/models/post.rb):

class Post < ActiveRecord::Base
 validates_presence_of :message    
 validates_length_of :message, :maximum => 140  
end


Okay, so that now we've created the Rails app, let's configure the controller and the routes. If you cast your mind back to the last article, you would remember that there are four different RESTful verbs. Let's add those four methods to our controller (app/controllers/posts_controller.rb):

class PostsController < ApplicationController    
 # GET - displays all posts    
 def index    
 end    
 
 # GET - shows one post (based on the supplied id)    
 def show    
 end    
 
 # POST - creates a new post    
 def create    
 end    
 
 # PUT - updates an existing post    
 def update    
 end    
 
 # DELETE - deletes a post    
 def destroy    
 end  
end


You can see from the comments above how each REST verb maps to the controller methods. You'll also notice that even though there are four verbs, we need five methods. The GET verb has two corresponding methods: one shows all the posts, while the other shows a specific post. We actually need two more methods -- new and edit -- to complete the controller.

We add these extra methods for two reasons. First of all, Rails uses the same endpoint (plus maybe an id) for all the verbs, so if we link to /posts (a GET method), Rails will return a list of posts. By adding these methods, we have a way to display the forms.

Let's add the extra methods and some code:

class PostsController < ApplicationController    
 # GET - displays all posts    
 def index      
   @posts = Post.find :all, :order => 'created_at ASC'    
 end    
 
 # GET - shows one post (based on the supplied id)    
 def show      
   @post = Post.find(params[:id])    
 end    
 
 # GET - displays a form which can be used to create a post
 def new  
   @post = Post.new    
 end    
 
 # POST - create a new post    
 def create      
   @post = Post.new(params[:post])      
   @post.save!      
   redirect_to post_path(@post)    
   rescue ActiveRecord::RecordInvalid      
   render :action => 'new'    
 end    
 
 # GET - displays a form allowing us to edit an existing post
 def edit
   @post = Post.find(params[:id])
 end    
 
 # PUT - update an existing post    
 def update      
   @post = Post.find(params[:id])      
   @post.attributes = params[:post]      
   @post.save!      
   redirect_to post_path(@post)    
   rescue ActiveRecord::RecordInvalid      
   render :action => 'edit'    
 end    
 
 # DELETE - delete a post    
 def destroy      
   @post  = Post.find(params[:id])      
   @post.destroy      
   redirect_to posts_path    
 end  
end


If you've ever done any Rails coding before, there won't be any surprises here, with the possible exception of the posts_path, post_path, edit_post_path, and delete_post_path methods. These special methods will generate a path to a resource based on entries in routes.rb. If you can't remember the syntax (and it can get a little tricky with things like nested routes), you can run the ever-helpful routes rake task, like so:

rake routes

But for those special methods to work, we need to tell Rails that we have a REST resource. Open up config/routes.rb and add the following line (about halfway through the code, you should see a similar example line that has been commented out; put this line under it):

  map.resources :posts

All this does is tell Rails that we have a posts_controller that wants to be RESTful, so it needs to respond accordingly. Try out the routes rake task to see the results of your handiwork.

To complete the site, we need to add the views -- each GET action needs a corresponding view: index.html.erb, show.html.erb, new.html.erb, and edit.html.erb. I've also created a _form.html.erb partial, because the form code for the new and edit methods is basically the same.

Here's the index view. There's nothing too special about this file -- again, we use post_path, this time as a parameter of the link_to tag:

# app/views/posts/index.html.erb  
<%= link_to 'New message', new_post_path %>  
<hr />  
<% if @posts.empty? -%>    
 <p>There are no posts</p>  
<% else -%>    
 <% @posts.each do |post| -%>      
   <p>        
     <%= link_to h(post.message), post_path(post) -%>      
   </p>    
 <% end -%>  
<% end -%>


Here's is the show view. For this page, we show a delete button and edit link:

# show.html.erb  
<p><%= h(@post.message) -%></p>  
 
<% form_tag post_path(@post), :method => :delete do -%>    
 <%= link_to 'Edit', edit_post_path(@post) -%>    
 <button type="submit">Delete</button>  
<% end -%>


You'll notice in the above code that we have a call to a form_tag incorporating a post_path and a delete method. This instructs the form to perform a DELETE action when the button's pressed. However, when the edit link is clicked, we get taken to the special edit view.

Both the new and edit views are basically the same -- so much so that we use a partial for the common code:

# app/views/posts/new.html.erb  
<%= error_messages_for('post') %>  
 
<% form_tag posts_path do -%>    
 <%= render :partial => 'posts/form' %>    
 
 <fieldset>      
   <button type="submit">Save</button>      
   <%= link_to 'Cancel', posts_path %>    
 </fieldset>  
<% end -%>  
 
# app/views/posts/edit.html.erb  
<%= error_messages_for('post') %>  
 
<% form_tag post_path(@post), :method => :put do -%>    
 <%= render :partial => 'posts/form' %>    
 
 <fieldset>      
   <button type="submit">Update</button>      
   <%= link_to 'Cancel', posts_path %>    
 </fieldset>  
<% end -%>


You'll notice that, once again, we set a method attribute, this time with PUT. We don't need to do this for the new view, as the default action for a form, POST, is right for our new action.

Finally, let's look at the form partial, which is all very run-of-the-mill as far as Rails forms go:

# app/views/posts/_form.html.erb  
<fieldset>    
 <legend>Enter your message</legend>    
 
 <label for="post_message">Message</label>    
 <%= text_area 'post', 'message' %>  
</fieldset>

Let's run it and see what happens! Run:

ruby script/server

And pointing your browser to http://localhost:3000/posts. With any luck, you should see something similar to this:

The app in action

Congratulations! You just created a RESTful Rails app!

If you liked this article, share the love:
Print-Friendly Version Suggest an Article