Skip to content

Instantly share code, notes, and snippets.

@foca
Created May 13, 2011 15:31
Show Gist options
  • Save foca/970743 to your computer and use it in GitHub Desktop.
Save foca/970743 to your computer and use it in GitHub Desktop.
Permit the Frog
# Permit the Frog
# ===============
#
# NOTE: The idea is to release it as a gem eventually, but for now, until we have
# used it in the real world for a while, let's just keep it here.
#
# Define permissions with something like this:
#
# class Permit::Set
# it.can :read, Product do |user, target|
# target.members.include?(user)
# end
#
# it.can :manage, [Product, Account] do |user, target|
# target.owner == user
# end
#
# it.can :manage, User do |user, target|
# target.id == user.id
# end
#
# it.can :read, User # true for any user
# end
#
# Internally this creates an instance of Permit::Set, where the permissions are
# stored. Calling Permit#Set.it will define permissions in Permit::Set.default.
#
# If you want to define a new set of permissions, just do:
#
# set = Permit::Set.new
# set.can :read, Product # etc
#
# In order to check for permission, call Permit.default_set.can?
#
# permissions = Permit::Set.default
# permissions.can?(user, :read, product) # true if the user is a member
#
# This is a bit too verbose, so Permit provides helpers to reduce this:
#
# Include the Permit::Helpers module and you can do:
#
# can(user).manage?(product)
# can(user).read?(product)
#
# If the class that gets Permit::Helpers included defines a `current_user` method
# then you can shorten this to:
#
# can.manage?(product)
# can.read?(product)
#
# Permit::Helpers also gives you `default_permissions_set`, which defaults to
# `Permit::Set.default`, but you can redefine it to use a custom default set.
#
# Rails
# =====
#
# Since ActiveSupport::Dependencies is retarded, you need to define permissions
# inside the application, and can't use an initializer, for this, our solution is
# to define. In app/models/permissions.rb:
#
# class Permissions < Permit::Set
# it.can…
# end
#
# Then, in ApplicationController:
#
# class ApplicationController < ActionController::Base
# include Permit::Helpers
# helper_method :can
#
# def default_permissions_set
# Permissions.default
# end
# end
module Permit
class Set
class << self
def default
@default ||= new
end
def default=(set)
@default = set
end
alias_method :it, :default
end
def can(ability, object_or_class, &block)
block ||= proc { |*args| true }
permissions[object_or_class][ability.to_s] = block
end
def can?(user, ability, object, *args)
abilities_for(object).fetch(ability.to_s).call(user, object, *args)
rescue KeyError
false
end
def abilities_for(object)
abilities = permissions.detect do |target, _|
object.is_a?(target)
end
abilities && abilities.last || permissions.fetch(:all, {})
end
def any?(&block)
permissions.any?(&block)
end
def permissions
@permissions ||= Hash.new {|h,k| h[k] = {} }
end
end
module Helpers
def can(user=current_user)
AbilityChecker.new(user, default_permissions_set)
end
def default_permissions_set
Permit::Set.default
end
class AbilityChecker < Struct.new(:user, :permissions_set)
def method_missing(method, object, *args, &block)
return super unless respond_to?(method)
ability = method.to_s.gsub(/\?$/, '')
permissions_set.can?(user, ability, object, *args)
end
def respond_to?(method, include_private=false)
permissions_set.any? do |object, abilities|
abilities.include?(method.to_s.gsub(/\?$/, ''))
end
end
end
end
end
@EmmanuelOga
Copy link

At the expense of a slightly less beautiful syntax, a lot simpler code to achieve the same:

https://gist.github.com/970853

module Permissions
  def self.for(*where, &block)
    where.each { |klass| klass.class_eval(&block) }
  end

  module Helper
    def can?(action, target, who=current_user)
      perm = "can_#{action}?"
      who.respond_to?(perm) && who.send(perm, target)
    end
  end
end

Example:

Permissions.for(Product, Account) do
  def can_manage?(user)
    user_id == user.id
  end
end

can?(:read, prod)

etc...

@foca
Copy link
Author

foca commented May 13, 2011 via email

@EmmanuelOga
Copy link

"Er, no, that isn't even remotely similar to Permit."
I know, it is a lot simpler :-)

The meaning of those can_something? methods is clearly defined. It is all right to make each class responsible for knowing whether someone can perform actions on them or not.

Your "small objects" are hidden and are merely a devise to create a nice dsl-ly syntax, so I don't see the advantage to complicate things just for a debatable nicer syntax.

@foca
Copy link
Author

foca commented May 13, 2011 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment