hammock

github.com/benhoskings/hammock

DESCRIPTION:

Hammock is a Rails plugin that eliminates redundant code in a very RESTful manner. It does this in lots in lots of different places, but in one manner: it encourages specification in place of implementation.

Hammock enforces RESTful resource access by abstracting actions away from the controller in favour of a clean, model-like callback system.

Hammock tackles the hard and soft sides of security at once with a scoping security system on your models. Specify who can verb what resources under what conditions once, and everything else - the actual security, link generation, index filtering - just happens.

Hammock inspects your routes and resources to generate a routing tree for each resource. Parent resources in a nested route are handled transparently at every point - record retrieval, creation, and linking.

It makes more sense when you see how it works though, so check out the screencast!

REQUIREMENTS:

benhoskings-ambition benhoskings-ambitious-activerecord

INSTALL:

sudo gem install benhoskings-hammock –source gems.github.com

class ApplicationController

include Hammock
...

LICENSE:

Hammock is licensed under the BSD license, which can be found in full in the LICENSE file.

SYNOPSIS

At the moment, you can do this with Hammock:

class ApplicationController < ActionController::Base
  include Hammock
end

class BeersController < ApplicationController
end

class Person < ActiveRecord::Base
end

class Beer < ActiveRecord::Base
  belongs_to :creator, :class_name => 'Person'
  belongs_to :recipient, :class_name => 'Person'

  def read_scope_for account
    proc {|beer| beer.creator_id == account.id || beer.recipient_id == account.id }
  end
  export_scope :read
  export_scope :read, :as => :index

  def write_scope_for account
    proc {|beer| record.creator_id == account.id }
  end
  export_scope :write
end

<% @beers.each do |beer| %>
  From <%= beer.creator.name %> to <%= beer.recipient.name %>, <%= beer.reason %>, rated <%= beer.rating %>
  <%= hamlink_to :edit, beer %>
<% end %>

The scope methods above require just one thing – a context-free proc object that takes an ActiveRecord record as its argument, and returns true iff that record is within the scope for the specified account. Hammock uses the method (e.g. Beer.read_scope_for) to define resource and record scopes for the model:

Beer.readable_by(account): the set of Beer records whose existence can be known by account
Beer#readable_by?(account): returns true if the existence of this Beer instance can be known by account

You define the logic for read, index and write scopes in Beer._scope_for, and the rest just works.

These scope definitions are exploited extensively, to provide index selection, scoping for record selection, and post-selection object checks.

  • They provide the conditions that should be applied to retrieve the index of each resource. The scope is used transperently by Hammock on /beers -> BeersController#index, and is available for use through Beer.indexable_by(account).

  • They provide a scope within which records are searched for on single-record actions. For example, given the request /beers/5 -> BeersController#show=> 5, Rails would generate the following SQL:

    SELECT * FROM “beers” WHERE (beers.“id” = 5) LIMIT 1

Hammock uses the conditions specified in Beer.read_scope_for to generate (assuming an account_id of 3):

SELECT * FROM "beers" WHERE ((beers.creator_id = 3 OR beers.recipient_id = 3) AND beers."id" = 5) LIMIT 1

Hammock uses Beer.read_scope_for on #show, and write_scope_for on #edit, #update and #destroy. These scopes can be accessed as above through Beer.readable_by(account) and Beer.writeable_by(account). This eliminates authorization checks from the action, because if the ID of a Beer is provided that the user doesn’t have access to it will fall outside the scope and will not be found in the DB at all.

  • They are used to discover credentials for already-queried ActiveRecord objects, without touching the database again. Just as Beer.readable_by(account) returns the set of Beer records whose existence can be known by account, @beer.readable_by?(account) returns true iff @beer’s existence can be known by account. This is employed by hamlink_to.

These three uses of the scope, plus another as-yet unimplemented bit, provide the entire security model of the application.

THE MASTER PLAN

Lots of functionality is planned that will take this much further.