Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

162 lines (143 sloc) 6.62 kb

Windsor

Windsor is a Ruby gem for Ruby on Rails that can help you build RESTful APIs more easily.

It provides:

  • Better handling of HTTP methods and better use of HTTP status codes and headers
  • Better support for hyperlinks in representations, making it easier to create some standard links as well as some links that are generated from your model relationships.
  • Better support for collections of resources with built-in features like AtomPub-style pagination and query parameter filtering.

Current state:

Under heavy development! This gem is currently very tied to ActiveModel for creating APIs, but we're working on decoupling that slightly so that Windsor controllers are more generally applicable and extendable.

Install:

gem install windsor

with Bundler:

gem 'windsor'    

Usage:

If you have a 'User' model, you can expose it as a RESTful resource by simply creating a controller that inherits from the Windsor controller and has the same name as the model:

class UsersController < WindsorController
end

The controller extracts the model name from the controller name, and you can route to it like you normally would:

scope :path => '/api' do
  resources :users
end

You get a bunch of default implementations of actions: GET /api/users will return a collection of users POST /api/users will allow you to add a user to that collection
GET /api/user/{id} will return a particular user with a matching id DELETE /api/user/{id} will allow you to delete that user with the matching id PUT /api/user/{id} will allow you to delete that user with the matching id

Here's how a GET to /api/users/1 would look:

{
  "username" : "greggg",
  "email_address" : "greggg@example.com",
  "links" : {
    "self" : { "href" : "http://example.com/api/users/1" },
    "index" : { "href" : "http://example.com/api/users" }
  }
}

Here's how a GET to /api/users/ (the collection) would look:

{
  "users" : [
    {
      "username" : "greggg",
      "email_address" : "greggg@example.com",
      "links" : {
        "self" : { "href" : "http://example.com/api/users/1" }
      }
    },
    {
      "username" : "samdec",
      "email_address" : "samdec@example.com",
      "links" : {
        "self" : { "href" : "http://example.com/api/users/2" }
      }
    },
    {
      "username" : "seth",
      "email_address" : "seth@example.com",
      "links" : {
        "self" : { "href" : "http://example.com/api/users/3" }
      }
    }
  ],
  "links" : {
    "self" : { "href" : "http://example.com/api/users" }
  },
  "pagination" : {
    "total_items" : 9,
    "max_page_size" : 3,
    "links" : {
      "next" : { "href" : "http://example.com/api/users?page=2" },
      "first" : { "href" : "http://example.com/api/users?page=1" },
      "last" : { "href" : "http://example.com/api/users?page=3" }
    }
  }
}

What if we don't want to allow an action (like DELETE for example)?

class UsersController < WindsorController
  def set_actions
    actions :all, :except => [:destroy]  # allow all actions, except destroy
  end
end

If you have a model that is a child of another model simply set up your nested routes:

scope :path => '/api' do
  resources :accounts do
    resources :users
  end
end

then in your Users controller:

class UsersController < WindsorController
  def view_scope
    { :account_id => params[:account_id] }
  end

  def create_scope
    { :account_id => params[:account_id] }
  end
end

to get hypermedia links between your account and user:

class UsersController < WindsorController
  def to_representation(object)
    object["links"]["account"] = 
      { "href" => url_for(:controller => "accounts", :action => "show", :id => params[:account_id] ) } 
  end
end

class AccountsController < WindsorController
  def to_representation(object)
    object["links"]["users"] = { "href" => url_for(:controller => "users", :action => "index") }
  end
end

What if we also don't want to show some of the attributes that are on the model (like created_at, updated_at, and id for example)?

class UsersController < WindsorController
  def set_actions
    actions :all, :except => [:destroy]  # allow all actions, except destroy
  end

  def set_attributes
    attributes :all, :except => [:created_at, :updated_at, :id]  #show all attributes, except created_at, updated_at, and id
  end
end

Windsor allows you to override any of its methods in order to achieve what you need. view_scope, create_scope, and to_representation are there to be overridden to support customizing your resource, but you can also override things like model_class and get_controller_name in case you want your controller name to be different than your model name.

view_scope returns a hash that gets passed into the ActiveRecord query. Modify this to create a default filter when viewing items (collections are filtered automatically by query parameters, though you can use view_scope to add more filters). create_scope is the same except its added to the creation of an object.

to_representation is the last place your representation goes before being rendered. The entire representation is passed in (as a hash) and here you can add or subtract any fields you want.

If you want to have a model name that is different than the controller name, for example a User model, but an AdminUsers controller, you need to override two methods in the AdminUsers controller: model_class and get_controller_name. Just make model_class return User and get_controller_name return "AdminUsers".

If need be you can also override any of the controller actions and build your own custom action while still retaining some of the error handling and helper methods from Windsor.

This is currently really only useful if you have a single model, or a subset of a model, that you want to expose for basic CRUD with a RESTful interface without much extra processing logic, but that need arises quite often. The ultimate goal of Windsor is to allow you to easily define resources indepedent of your models, be able to perform CRUD on those resources, and link your resources together, with as little boilerplate as possible.

Jump to Line
Something went wrong with that request. Please try again.