Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
258 lines (204 sloc) 6.36 KB

REST in Rails

In Rails, a RESTful resource is expressed in routing via config/routes.rb.

RailsMvc::Application.routes.draw do
  resources :things
end

One Resource, Many Representations

  • An HTML representation: http://localhost:3000/things/1
  • A TEXT representation: http://localhost:3000/things/1.txt
  • A JSON representation: http://localhost:3000/things/1.json
{
  "thing": {
    "created_at": "2012-06-06T11:45:38Z",
      "description": "The beginning...",
      "id": 1,
      "name": "First Thing",
      "updated_at": "2012-06-06T11:45:38Z"
  }
}

The Things controller is already exposing the RESTful resource, but it needs to return different formats.

def show
  @thing = Thing.find(params[:id])
  respond_to do |format|
    format.html
    format.json { render :json => @thing }
    format.text { render :text => @thing }
  end
end

Creating Resources

Respond to different formats and with different status codes in controllers/things_controller.rb.

def create
  @thing = Thing.new(params[:thing])
  if @thing.save
    respond_to do |format|
      format.html { redirect_to @thing, notice: "Thing was successfully created." }
      format.json { render :json => @thing, :status => 201 }
    end
  else
    respond_to do |format|
      format.html { render action: "new" }
      format.json { render :json => { "errors" => @thing.errors }, :status => 400 }
    end
  end
end

Use curl from the command line.

$ curl -vd "thing[name]=new%20thing&thing[description]=description" http://localhost:3000/things.json

Responds with 201 Created.

HTTP/1.1 201 Created
Content-Type: application/json

{ "thing":{"created_at":"2012-02-13T14:10:07Z","description":null,"id":5,"name":"new thing","updated_at":"2012-02-13T14:10:07Z"}}

Updating Resources

$ curl -X PUT -vd "thing[name]=updated%20thing" -H "Accept: application/json" http://localhost:3000/things/1

Updating a record should return 200 OK.

def update
  @thing = Thing.find(params[:id])
  if @thing.update_attributes(params[:thing])
    respond_to do |format|
      format.html { redirect_to @thing, notice: "Thing was successfully updated." }
      format.json { render :json => @thing, :status => 200 }
    end
  else
    respond_to do |format|
      format.html { render action: "edit" }
      format.json { render :json => { "errors" => @thing.errors }, :status => 400 }
    end
  end
end

Destroying Resources

curl -X DELETE -H "Accept: application/json" http://localhost:3000/things/1 -v

Generally APIs return the object that has been deleted.

def destroy
  @thing = Thing.find(params[:id])
  @thing.destroy
  respond_to do |format|
    format.html { redirect_to things_url }
    format.json { render :json => @thing, :status => 200 }
  end
end

Exception Handling

Return 404 whenever a resource doesn't exist.

def get_thing
  begin
    @thing = Thing.find(params[:id])
  rescue ActiveRecord::RecordNotFound => e
    respond_to do |format|
      format.html { render file: "public/404.html", status: 404 }
      format.json { render :json => { "errors" => e.message }, :status => 404 }
    end
  end
end

More generally, we can handle these errors in the ApplicationController.

class ApplicationController < ActionController::Base

  rescue_from Exception, :with => :handle_exception
  rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found

  private

    def record_not_found(e)
      respond_to do |format|
        format.html { render file: "public/404.html", status: 404 }
        format.json { render :json => { "errors" => e.message }, :status => 404 }
      end
    end

    def handle_exception(e)
      respond_to do |format|
        format.html
        format.json  { render :json => { "errors" => e.message }, :status => 500 }
      end
    end

end

Using from JavaScript

Creating a Thing:

createThing = function(name, description) {
  $.post('/things.json', {
    thing: {
      name: name,
      description: description
    }
  }).success(function(data) {
    console.log("Created " + data.thing.name + " with id " + data.thing.id);
  });
};

Search and Autocomplete:

We can use jquery-ui-bootstrap-rails gem to style JQuery UI.

The :index action responds to the term parameter, rendering JSON or HTML.

def index
  if params[:term]
    @things = Thing.find(:all,:conditions => ['name LIKE ?', "#{params[:term]}%"],  :limit => 5, :order => 'name')
  else
    @things = Thing.all
  end
  respond_to do |format|
    format.html
    format.json { render :json => @things }
  end
end

Add a form to index.html.haml page and a JavaScript include that will add a JQuery autocomplete to the :name field in this form.

= javascript_include_tag 'things'

=form_for :thing do |f|
  =f.text_field :name, :class => 'select'

We'll give each row of thing an ID.

- @things.each do |thing|
  %tr{id: thing.id}

The JavaScript goes into app/assets/javascripts/things.js.coffee. Once the document is loaded, the autocomplete from things.json is attached to the input box using the JQuery UI plugin's autocomplete function. Upon selection it will locate a row in the table and highlight it.

$(document).ready ->
  $('input#thing_name')
    .autocomplete({
      source: '/things.json',
      select: ( event, ui ) ->
        row = $("tr[id=#{ui.item.thing.id}]")
        row.addClass("selected")
        false
    })
    .data("autocomplete")._renderItem = (ul, item) ->
          $("<li></li>")
              .data("item.autocomplete", item)
              .append("<a>#{item.thing.name}</a>")
              .appendTo(ul);

The selected CSS is placed into app/assets/stylesheets/application.css.scss.

tr.selected {
  background-color: green;
  color: white;
  font-weight: bold;
}

REST Without Rails

If you're not using Rails MVC, there're several popular alternatives to exposing a RESTful API in Ruby.