Skip to content
This repository

Radically RESTful Rails.

Fetching latest commit…


Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 misc
Octocat-spinner-32 public
Octocat-spinner-32 tasks
Octocat-spinner-32 .gitignore
Octocat-spinner-32 History.txt
Octocat-spinner-32 LICENSE
Octocat-spinner-32 Manifest.txt
Octocat-spinner-32 README.rdoc
Octocat-spinner-32 Rakefile
Octocat-spinner-32 VERSION
Octocat-spinner-32 hammock.gemspec
Octocat-spinner-32 init.rb







It makes more sense when you see how it works though. There's a screencast coming soon.



These gems will install automatically as long as you've added the GitHub gem source:

gem sources -a


sudo gem install hammock

in config/environment.rb: do |config|
  config.gem 'hammock'

in app/controllers/application_controller.rb:

class ApplicationController
  include Hammock::RestfulActions


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


At the moment, you can do this with Hammock:

class ApplicationController < ActionController::Base
  include Hammock::RestfulActions

class BeersController < ApplicationController

class Person < ActiveRecord::Base

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 == || beer.recipient_id == }
  export_scope :read

  # TODO - Duplication, yuck. There's a proper DSL in the pipes.
  def self.index_scope_for account
  export_scope :index

  creator_scope_for :write

<% @beers.each do |beer| %>
  From <%= %> to <%= %>, <%= 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.[read,index,write]_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{:id => 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.


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

Something went wrong with that request. Please try again.