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. There’s a screencast coming soon.
REQUIREMENTS:
benhoskings-ambition
benhoskings-ambitious-activerecord
These gems will install automatically as long as you’ve added the GitHub gem source:
gem sources -a http://gems.github.com
INSTALL:
sudo gem install hammock
in config/environment.rb:
Rails::Initializer.run do |config|
config.gem 'hammock'
...
in app/controllers/application_controller.rb:
class ApplicationController
include Hammock::RestfulActions
...
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::RestfulActions
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 self.read_scope_for account
L{|beer| beer.creator_id == account.id || beer.recipient_id == account.id }
end
export_scope :read
# TODO - Duplication, yuck. There's a proper DSL in the pipes.
def self.index_scope_for account
L{|beer| beer.creator_id == account.id || beer.recipient_id == account.id }
end
export_scope :index
creator_scope_for :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 lambda 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.