Permissive gives your ActiveRecord models granular permission support = Permissive combines a model-based permissions system with bitmasking to create a flexible approach to maintaining permissions on your ActiveRecord models. It supports an easy-to-use set of methods for accessing and determining permissions, including some fun metaprogramming. Installation - 1. Get yourself some code. You can install as a gem: `gem install permissive` or as a plugin: `script/plugin install git://github.com/flipsasser/permissive.git` 2. Generate a migration so you can get some sweet table action: `script/generate permissive_migration` `rake db:migrate` Usage - First, define a few permissions constants. We'll define them in `Rails.root/config/initializers/permissive.rb`. The best practice is to name them in a verb format that follows this pattern: "Object can `DO_PERMISSION_NAME`". Permission constants need to be int values counting up from zero. We use ints because Permissive uses bit masking to keep permissions data compact and performant. module Permissive::Permissions MANAGE_GAMES = 0 CONTROL_RIDES = 1 PUNCH = 2 end And that's all it takes to configure permissions! Now that we have them, let's grant them to a model or two: class Employee < ActiveRecord::Base acts_as_permissive validates_presence_of :first_name, :last_name end class Company < ActiveRecord::Base validates_presence_of :name end Easy-peasy, right? Let's try granting a few permissions: @james = Employee.create(:first_name => 'James', :last_name => 'Brennan') @frigo = Employee.create(:first_name => 'Tommy', :last_name => 'Frigo') @adventureland = Company.create(:name => 'Adventureland') # Okay, let's do some granting. We'll start by scoping to a specific company. @james.can!(:manage_games, :on => @adventureland) # Now let's do some permission checking. @james.can?(:manage_games, :on => @adventureland) #=> true # We can also use the metaprogramming syntax: @james.can_manage_games_on?(@adventureland) #=> true @james.can_control_rides_on?(@adventureland) #=> false # We can check for multiple permissions, too: @james.can?(:manage_games, :control_rides) #=> false # OR: @james.can_manage_games_and_control_rides? # Scoping can be done through any object @frigo.can!(:punch, :on => @james) @frigo.can_punch_on?(@james) #=> true # And the permissions aren't reciprocal @james.can_punch_on?(@frigo) #=> false # Of course, we can grant global (non-scoped) permissions, too: @frigo.can!(:control_rides) @frigo.can_control_rides? #=> true # BUT! Global permissions don't override scoped permissions. @frigo.can_control_rides_on?(@adventureland) #=> false # Likewise, scoped permissions don't bubble up globally: @james.can_manage_games? #=> false # And, last but not least, let's take all of those great permissions away: @james.revoke(:manage_games, :on => @adventureland) # We can revoke all permissions, in any scope, too: @frigo.revoke(:all) And that's it! Scoping - Permissive supports scoping at the class-configuration level, which adds relationships to permitted objects: class Employee < ActiveRecord::Base acts_as_permissive :scope => :company end @frigo.permissive_companies #=> [Company 1, Company 2] Replacing Permissions - Sometimes you want to overwrite all previous permissions in a can! method. That's pretty easy: just add :reset => true to the options. @frigo.can!(:control_rides, :on => @adventureland, :reset => true) Next Steps - There's a number of things I want to add to the permissive settings. At the moment, Permissive currently support scoping at the class level, BUT all it really does is add a `has_many` relationship. `@employee.can!(:do_anything)` will still work, as will `@employee.can!(:do_something, :on => @something_that_isnt_a_company)`. That's pretty confusing to me. Adding more granular permissions might be cooler: class Employee < ActiveRecord::Base has_permissions do on :companies on :employees end end which might yield something like @employee.permissive_companies # and @employee.can_control_rides_in_company @adventureland I'd also like to support a more intelligent grammar: @james.can_punch? @frigo @frigo.can!(:control_rides, :in => @adventureland) Meta-programmed methods for granting and revoking would be cool, too: @james.can_punch! @frigo @frigo.cannot_control_rides_in! @adventureland And while we're on the subject of metaprogramming, let's add some OR-ing to the whole thing: @james.can_control_rides_or_manage_games_in? @adventureland I'd also like to enable Permissive::Templates (pre-set permission groups, like roles): administrator = Permissive::Template.named('Administrator') @james.acts_like administrator Next up! I currently use a manual reset to grant permissions through a controller. It would by great to DRY this stuff up and provide some decent path for moving permissions into HTML forms. Right now, it looks something like this: <%= check_box_tag("employee[permissions][]", Permissive::Permissions::CONTROL_RIDES, @employee.can_control_rides?) %> Control rides .. and in the controller: def update @employee.can!(params[:employees].delete(:permissions), :revert => true) respond_to do |format| ... end end Finally, I'd like to use the `grant_mask` support that exists on the Permissive::Permission model to control what people can or cannot allow others to do. This would necessitate one of two things - first, a quick way of iterating over a person's granting permissions, e.g.: <% current_user.grant_permissions.each do |permission| %> <% end %> and second, write-time checking of grantor permissions. Something like this, maybe: def update current_user.grant(params[:employees][:permissions], :to => @employee) end which would allow the Permissive::Permission model to make sure whatever `current_user` is granting to @employee, they're **allowed** to grant to @employee. And that's it! Like all of my projects, I extracted it from some live development - which means it, too, is still in development. So please feel free to contribute! Copyright (c) 2009 Flip Sasser & Simon Parsons, released under the MIT license