Decoupling GUI from API in Ruby on Rails 3

Suraj N. Kurapati

  1. config/routes.rb
    1. app/controllers/application_controller.rb
      1. app/controllers/users_controller.rb

        Having built web applications with Rails 3 and Sencha ExtJS for the past year or so, I developed a simple way to decouple GUI concerns from APIs:

        If a format is specified in the requested URI, then serve the API. Otherwise, serve the GUI while indicating that the response is non-authoritative, using HTTP status code 203.

        The implementation, from routes to controllers, is demonstrated below. For example, when GET /users is requested, the GUI is served; but when GET /users.json is requested, the API is served. Similarly, when POST /users is requested, the GUI is served; but when POST /users.json is requested, the API is served and it responds with the newly created user.

        config/routes.rb

        # You can have the root of your site routed with "root"
        # just remember to delete public/index.html.
        root :to => 'application#index'
        
        # pass unknown URLs to GUI (HTML5 history.pushState)
        # http://railscasts.com/episodes/46-catch-all-route
        match '*path' => 'application#index'
        

        app/controllers/application_controller.rb

        # serve GUI when no format is specified
        before_filter do
          unless params[:format]
            render nothing: true, layout: true, status: :non_authoritative_information
          end
        end
        
        # serve API when a format is specified
        def index
          respond_with nil, location: nil, status: :not_implemented
        end
        alias new     index
        alias create  index
        alias show    index
        alias edit    index
        alias update  index
        alias destroy index
        
        # serve these formats in respond_with()
        respond_to :json
        

        app/controllers/users_controller.rb

        class UsersController < ApplicationController
          def create
            user = User.new(params[:user])
            respond_with(
              {
                success: !!user.save,
                errors: user.errors,
                user: user,
              },
              location: url_for(user)
            )
          end
        end