In the end, everything is a gag. - Charlie Chaplin

A real life RESTful Rails application

“Soon you will know, soon you will be one of us” — an ancient interwebs proverb.

Introduction

If you have spent some time learning RESTful way of developing applications
with Rails, you’ve probably been through some tutorials on developing basic CRUD
applications in the RESTful way.

However, a lot of people struggle with applying the RESTful concepts to
anything beyond simple toy applications. So, here I am presenting an example
application which shows slightly more involved use of RESTful
architecture.

At this point I should point out that it is a moderately complex example and
so if you are new to Rails or RESTful architecture in Rails, you should
brush up on these first. Here are a few good starting points for REST:

and for how it applies to Rails:

OK, let’s start.

While using RESTful architecture in Rails, only controller and routes are
different. What you already know about models and helpers and all of the other plugins
and libraries you are used to, work exactly the same.

Controllers become more streamlined and there will be lot more controller
classes than earlier, as you will see soon. However the code becomes much more
simpler.

Requirements

Let’s start by building an example Rails application. Here are the
requirements:

Our application should allow users to register and then login using their
login and password. Fairly standard stuff. After authentication, they should
see an inbox with messages – very much like your Gmail inbox, and a big button
called “send a message”. On clicking it, any user should be able to compose
and send another user a message. A message consists of subject and body. Again, quite
similar to email but these messages are within the closed system i.e. only
users in the system can send a message to another user in the system.
Yes,
quite like what social networking sites do. Additionally, a user should be
able to archive a message or delete it; view archived messages and sent
messages.

Is requirements part clear? Good.

Preparation

Let’s start by building user accounts and authentication:

$ rails -d mysql demo
$ cd demo
$ ruby script/plugin install git://github.com/technoweenie/restful-authentication.git
$ ruby script/generate authenticated user sessions

  1. add ‘include AuthenticatedSystem’ to application_controller.rb as recommended by restful-authentication plugin
  2. edit file: config/database.yml and add password for the database if you have one
    $ rake db:create
    $ rake db:migrate
    $ ruby script/server

Point your browser to: http://localhost:3000/users/new and create a new account. Then go to: http://localhost:3000/sessions/new and sign in with the new user account which you just created and you should see the “Welcome aboard” page.

So far you have built a basic Rails app, with restful auth. You might want to
register 2 users and keep their login and passwords handy – let’s say with
login names venkat and suresh. We would next be building the messaging
part so that these two users can send messages to each other.

Models and the Database design

You can reflect for a moment about what kind of DB design would suit this
problem. But, just to speed us up, here’s something I thought of. Let’s have
these following 3 models (and hence 3 tables in the DB) and their attributes:

MessageDetail: subject, body, sent_message_id

ReceivedMessage: message_detail_id, recipient_id, state

SentMessage: sender_id, state

The idea being that MessageDetail object represents the actual message – the
subject and body; ReceivedMessage represents the view of the message as
visible to the recipient; similarly SentMessage is sender’s
view. ReceivedMessage and SentMessage are separate objects from MessageDetail
so that senders and each of the multiple recipients can independently archive or
delete their messages.

This means, even if sender decides to delete a message, we won’t delete
MessageDetail record from our table because otherwise message from recipient
view would be lost. We would delete the message when both sender and
receiver delete their messages.

Let’s create each of these models:


$ ruby script/generate model MessageDetail subject:string body:text sent_message_id:integer
$ ruby script/generate model ReceivedMessage message_detail_id:integer recipient_id:integer state:string
$ ruby script/generate model SentMessage sender_id:integer state:string

Then run the migrations to create the DB tables:


$ rake db:migrate

Now, fill in the model code:



# model classes omitting validations

class MessageDetail < ActiveRecord::Base
  attr_accessible :subject, :body, :sender, :recipient

  belongs_to :sent_message
  has_one :sender, :through => :sent_message
  has_many :received_messages
  has_many :recipients, :through => :received_messages
  
  def sender=(user)
    self.build_sent_message
    self.sent_message.sender = user
  end

  def recipient=(user)
    self.received_messages << ReceivedMessage.new(:recipient => User.find_by_login(user))
  end

  def recipient
    self.received_messages.map { |r| r.recipient.try(:login) }.join(", ")
  end
end

class SentMessage < ActiveRecord::Base
  has_one :message_detail
  belongs_to :sender, :class_name => 'User'
end

class ReceivedMessage < ActiveRecord::Base
  belongs_to :message_detail
  belongs_to :recipient, :class_name => 'User'
end


You will also have to modify the User model a little bit to add this association:



class User < ActiveRecord::Base
  has_many :received_messages, :foreign_key => 'recipient_id'
  has_many :sent_messages, :foreign_key => 'sender_id'
  ...
end


Now, that is a lot of code. But, hopefully you can make some sense out of it. Even if you are not able to, don’t worry. We will mostly treat this as black box. Our main aim here is to learn about the controllers which is coming later.

To test that our models work as we expect, do this in your Rails console:



message = MessageDetail.new(:subject => 'test subject',
                            :body => 'test body',
                            :sender => User.find_by_login('suresh'),
                            :recipient => 'venkat')

message.save # this should return true

suresh = User.find_by_login('suresh')
msg = suresh.sent_messages.first # suresh's first sent message
msg.message_detail.recipients # list of all recipients of this message


Restful architecture

Now, our aim is to expose the messages system through our UI. REST is
methodology of specifying an external interface (or a network API in other
words) of a system. Constructing that API is also quite systematic.

In review, REST is about 3 things:

  • resources, which are identified by URLs
  • fixed vocabulary of verbs (HTTP verbs: GET, POST, PUT and DELETE)
  • representations of resources e.g. the type of representation is specified by HTTP header: content-type

Our first task is constructing a URL scheme by identifying the resources in
the system. Think of resources as the nouns. Then each resource can be acted
upon by one of the standard HTTP verbs: GET, POST, PUT or DELETE.

Let’s say we decide on following URL scheme:

/messages
where GET /messages returns all messages in inbox, POST /messages sends a new message

/messages/archived
similar to /messages but shows all messages in archive folder

/messages/sent
similar to /messages but shows all messages in sent folder.

/messages/1
GET /messages/1 shows the message id 1

/messages/1/archived
GET /messages/1/archived tells us whether this message is archived and PUT
/messages/1/archived with a true in the body changes the status of the message id 1 to “archived”.

Rails actually constraints the REST even further. It generates the following set of URLs:

/messages

/messages/1

/messages/1/edit

/messages/new

Each of these URLs can be generated by a named route helper (if you don’t know named routes, see here: http://guides.rubyonrails.org/routing.html). The helper names for the above URLs are:

messages_path

message_path(:id => 1)

edit_message_path(:id => 1)

new_message_path

Then Rails maps certain combinations of HTTP verbs with these URLs to controller actions:

GET /messages index

GET /messages/1 get

POST /messages create

PUT /messages/1 update

DELETE /messages/1 delete

GET /messages/new new

GET /messages/1/edit edit

Now, creating the four named routes, and mapping the above 7 combinations of
HTTP verb and URL to controller actions, can be accomplished by adding just this one
line in the config/routes.rb file:


map.resources :messages


Go ahead and add this to your config/routes.rb. Then, create a controller called MessagesController with the above 7 actions, and the actions will be called whenever the user fetches the corresponding URL.


$ ruby script/generate controller messages


Filling the controller with the 7 actions is very much boiler plate code. But,
unfortunately Rails does not provide a generator for that (yet). If you use
Textmate or Emacs, you can use a snippet
(available here).

You can also generate the controllers using scaffold or resource generator.

However, let’s do it by hand:


$ ruby script/generate controller messages index show new edit create update destroy 


As an aside, if you are confused about difference between new and create: when a user
wants to create a new message, she GETs the /messages/new URL which is
rendered by new action, fills in the form, and then POSTs to /messages to
create the object in the database. The POST /messages request is handled by
create action. edit and update actions work similarly.

Now, all that is remaining is to code up the 7 actions in the controller.

There are rules about what can the web application do for each of these
actions. Most importantly, REST specifies that GET, PUT, DELETE are
idempotent. Which means they can be repeated, say by user hitting reload on
their browser; where as POST is not. So, all actions except create must be
idempotent, that is even if they are repeated, there should be no harm.

Now, there are a few things to take care of while coding the
controller. Firstly, put a filter so that the actions can be executed only
after authentication:



class MessagesController < ApplicationController
  before_filter :login_required
  ...
  ...
end


Now, we have to skillfully map our restful controller’s actions to calls to
the models we already have built. So, for example, code of index action would
be:



# GET /messages
def index
  @messages = current_user.received_messages
end

Now, create a view messages/index.html.erb and display the messages from @messages



<table>
<thead>
  <th>Sent by</th>
  <th>Subject</th>
  <th>Date</th>
</thead>
<% @messages.each do |message| %>
  <tr>
    <td><%= message.message_detail.sender.login %></td>
    <td><%= message.message_detail.subject %></td>
    <td><%= message.created_at %></td>
  </tr>
<% end %>
</table>

Go to http://localhost:3000/messages to look at the inbox for the logged in
user.

Now, it is quite straight forward to code up all the controller actions and corresponding views. Let’s try create next:



  # file: app/views/messages/new.html.erb
  <h1>Enter your message</h1>

  <% form_for(:message, :url => messages_path) do |f| %>

  <p>From <strong><%= current_user.login %></strong></p>
  <p>
      <%= f.label :recipient, "login-id of the recipient:" %><br />
      <%= f.text_field :recipient %>
  </p>
  <p>
      <%= f.label :subject, "Subject:" %><br />
      <%= f.text_field :subject %>
  </p>

  <p><%= f.text_area :body, :cols => 60, :rows => 8 %></p>

  <p><%= f.submit "Post Message" %></p>

  <% end %>

  # file: app/controllers/messages_controller.rb
  # GET /messages/new
  def new
    @message = MessageDetail.new
  end

  # POST /messages
  def create
    @message = MessageDetail.new(params[:message].merge(:sender => current_user))

    if @message.save
      flash[:success] = 'Message was successfully sent.'
      redirect_to messages_path
    else
      render :action => "new"
    end
  end
  

There, you have a message system whose external interface is completely RESTful.

Recap:

  • We built a skeleton Rails app with restful_authentication
  • Then we added messages models and tested from console that we can send a message from one user to other
  • Lastly we exposed the messages through a RESTful interface

Conclusion:

Rails supports RESTful architecture out of the box. There is no need for any plugin to make it work.

REST learning resources:

Rails:

1 Comment Added

Join Discussion
  1. Andrew Wang
    Andrew Wang Sep 29, 2010 at 12:00 PM
    Great tutorial. Thanks!

Post a comment