Skip to content

Instantly share code, notes, and snippets.

@jcasimir
Created September 12, 2011 20:08
Show Gist options
  • Save jcasimir/1212245 to your computer and use it in GitHub Desktop.
Save jcasimir/1212245 to your computer and use it in GitHub Desktop.
Exposing an API in Rails 3

Exposing an API

APIs are becoming an essential feature of modern web applications. Rails does a good job of helping your application provide an API using the same MVC structure you're accustomed to.

In the Controller

Let's work with the following example controller:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def create
    @article = Article.new(params[:article])
    if @article.save
      redirect_to article_path(@article), :notice => "Your article was created."
    else
      flash[:notice] = "Article failed to save."
      render :action => :new
    end
  end
  #...
end

We decide to service JSON and XML requests from these two actions. The index will be triggered by a GET request to /articles.json or /articles.xml. The create will be triggered by a POST request to the same paths.

respond_to

The first step is to call respond_to and list the formats our controller will respond to. This is typically done at the beginning of the controller:

class ArticlesController < ApplicationController
  respond_to :html, :json, :xml
  #...
end

Our controller will now attempt to respond to requests for HTML, JSON, or XML.

[Jeff's Says: When starting out with an API, I often forget the :html in the respond_to. The application will work at first because it will match an existing view template for the rendering. But once you start using respond_with your responses will be blank unless you include :html here.]

If you request /articles.json you'll find that the application is still unsuccessfully trying to render a articles.json.erb.

respond_with

You can write a view template for JSON and one for XML, but that's a tremendous pain. Instead we'd like to render the data directly from the controller.

Simple Rendering

In the past we broke down each format response using respond_to in the controller action:

def index
  @articles = Article.all
  respond_to do |format|
    format.html
    format.xml { render :xml => @articles }
    format.json { render :json => @articles }
  end
end

It led to a lot of repetition. In Rails 3, we can instead use respond_with:

def index
  @articles = Article.all
  respond_with(@articles)
end

The respond_with method will first attempt to render a matching view template for the response type, like index.json.erb or index.html.erb. If that template is not found, then the method will call .to_xml or .to_json on the object and render the result.

By combining respond_to and respond_with we can save a lot of boilerplate code that was prevalent in Rails 2.

Object Validation

But how is this technique used when we write a create action that checks object validation? We started with this:

def create
  @article = Article.new(params[:article])
  if @article.save
    redirect_to article_path(@article), :notice => "Your article was created."
  else
    flash[:notice] = "Article failed to save."
    render :action => :new
  end
end

We can achieve the same functionality using respond_with:

def create
  @article = Article.new(params[:article])
  if @article.save
    flash[:notice] = "Your article was created."
  else
    flash[:notice] = "Article failed to save."
  end
  respond_with @article
end

Then refactoring the branch into a ternary...

def create
  @article = Article.new(params[:article])
  flash[:notice] = @article.save ? "Your article was created." : "Article failed to save."
  respond_with @article
end

When we pass @article to respond_with, it will actually check if the object is valid? first. If the object is not valid, then it will call render :new when in a create or render :edit when in an update.

If the object is valid, it will automatically redirect to the show action for that object.

Controlling the Redirect

Maybe you'd rather redirect to the index after successful creation. You can override the redirect by adding the :location option to respond_with:

def create
  @article = Article.new(params[:article])
  flash[:notice] = @article.save ? "Your article was created." : "Article failed to save."
  respond_with @article, :location => articles_path
end

Overriding by Format

Does that action feel too simple? Would you like to bring back some of the heavy syntax from Rails 2? Then override the response by output format:

def create
  @article = Article.new(params[:article])
  flash[:notice] = @article.save ? "Your article was created." : "Article failed to save."
  respond_with @article do |format|
    format.html { @article.valid? ? redirect_to(@article) : render(:new) }
    format.json { render :json => @article }
    format.xml  { render :xml => @article }
  end
end

Filtering Data

When you use respond_with to output JSON or XML it will, by default, dump all the attributes. Next we'll look at how to filter these attributes from the model layer.

Resources

@jimjh
Copy link

jimjh commented Mar 30, 2013

Hey, thanks for the article. I like using respond_with, and would like to learn how to filter unnecessary attributes. Could you show me how to do that?

@toptierlabs
Copy link

I deployed a Rails engine (packed as a gem) that is really useful to test APIs on rails. You just have to mount the engine and go to the url that you specified, i.e. “localhost:3000/api_explorer” to see it. It’s a way of documenting an API also, reading the webservices specification from a file.

The gem is named 'api_explorer' and the repo is http://www.github.com/toptierlabs/api_explorer

Any comments or help improving the api is welcome. :)

@bertomartin
Copy link

@toptierlabs Awesome! Thank you. Ofcourse, thanks also to @jcasimir

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment