Skip to content

Instantly share code, notes, and snippets.

@aphexddb
Last active December 17, 2015 19:58
Show Gist options
  • Save aphexddb/5663894 to your computer and use it in GitHub Desktop.
Save aphexddb/5663894 to your computer and use it in GitHub Desktop.
Creating a flexible, modern API in Rails 3
# app/controllers/api_controller.rb
class ApiController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
# do not use CSRF for CORS options
skip_before_filter :verify_authenticity_token, :only => [:options]
# http://www.tsheffler.com/blog/?p=428
before_filter :cors_preflight_check
after_filter :cors_set_access_control_headers
def options
# dummy action, OPTIONS should be caught by cors_preflight_check
end
protected
# For all responses in this controller, return the CORS access control headers.
def cors_set_access_control_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, PUT, PATCH'
headers['Access-Control-Max-Age'] = "1728000"
end
# If this is a preflight OPTIONS request, then short-circuit the
# request, return only the necessary headers and return an empty
# text/plain.
def cors_preflight_check
if request.method == 'OPTIONS'
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, PUT, PATCH'
headers['Access-Control-Allow-Headers'] = '*, X-Requested-With, X-Prototype-Version, X-CSRF-Token'
headers['Access-Control-Max-Age'] = '1728000'
render :text => '', :content_type => 'text/plain'
end
end
end
# app/controllers/api/v1/api_info_controller.rb
module Api
module V1
class ApiInfoController < ApiController
def index
# Information on v1 of the API
end
def options
# dummy action, OPTIONS should be caught by cors_preflight_check in API controller
end
end
end
end
# config/application.rb
# ... your application config here
module Api
class Application < Rails::Application
# ... your application config here
# Enable CORS, and do so before Devise kicks in
# (Ordering matters! https://github.com/nov/rack-oauth2/issues/18)
config.middleware.insert_before Warden::Manager, Rack::Cors do
allow do
origins %r{^https?:\/\/[a-z0-9\-]+.#{CONFIG['website_host']}:?\d*$}i
resource '*',
headers: ['Origin', 'Accept', 'Content-Type'],
methods: [:get, :put, :create, :delete]
end
end
end
end
# app/views/api/v1/posts/index.html.jbuilder
json.posts @posts do |post|
json.id post.id
json.created_at post.created_at
json.author post.author
json.edit api_v1_post_url(post.id)
json.delete api_v1_post_url(post.id)
end
json.new new_api_v1_post_url
json.search api_v1_posts_url
# app/views/api/v1/api_info/index.json.jbuilder
json.apiVersion "1.0"
json.swaggerVersion "1.1"
json.basePath "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}#{request.env['PATH_INFO']}"
json.apis [
{
path: "/posts/{post_id}",
description: "Operations about posts",
operations: [
{
httpMethod: "GET",
nickname: "getPostById",
responseClass: "Post",
parameters: [
paramType: "path",
name: "post_id",
description: "ID of post that needs to be fetched",
dataType: "string",
required: true,
allowableValues: {
max: 10000000,
min: 1,
valueType: "RANGE"
},
allowMultiple: false
],
summary: "Find post by its unique ID",
notes: "Only posts which you have permission to see will be returned",
errorResponses: []
}
]
}
]
# app/controllers/api/v1/posts_controller.rb
module Api
module V1
class PostsController < ApiController
# add devise, cancan, etc. for your app here
def index
@posts = Post.all
end
def show
@post = Post.find(params[:id])
end
def create
# todo
end
def update
# todo
end
def destroy
# should respond with code 100
# todo
end
end
end
end

This is a guide to creating a basic API in Rails for consumption by Ember.js or your favorite framework. Key areas are support of CORS requests and Jbuilder for creating a self-documenting API.

Add these gems to your Gemfile

gem 'rack-cors', :require => 'rack/cors'
gem 'jbuilder'
Rocket::Application.routes.draw do
current_api_routes = lambda do
resources :messages, :via => :any
# OPTIONS request for the API
match "*path", :to => 'api_info#options', :via => :options
end
namespace :api, defaults: {format: 'json'} do
scope :module => :v1, &current_api_routes
namespace :v1, &current_api_routes
match ":api/*path", :to => redirect("/api/v1/%{path}"), :via => :any
end
# OPTIONS request for the API
match "*path", :to => 'api#options', :via => :options
# public
root :to => 'home#index'
end
# app/views/api/v1/posts/show.html.jbuilder
json.post do
json.id @post.id
json.created_at @post.created_at
json.author @post.author
json.edit api_v1_post_url(@post.id)
json.delete api_v1_post_url(@post.id)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment