Created
May 13, 2011 15:31
-
-
Save foca/970743 to your computer and use it in GitHub Desktop.
Permit the Frog
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
Er, no, that isn't even remotely similar to Permit.
That's adding a lot of methods in classes that add behavior completely
unrelated to the class itself. Why does a Product know about
authorization? The whole point of having a 3rd party object manage
this is to not break SRP.
Small objects that only have 1 responsibility are A Good Thing.
…On Fri, May 13, 2011 at 1:43 PM, EmmanuelOga ***@***.*** wrote:
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...
##
Reply to this email directly or view it on GitHub:
https://gist.github.com/970743
"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.
No, a Product has the responsibility of understanding and operating on
what a product should do (get sold, for example). The fact that a user
can create a product or not is NOT a responsibility of the Product
class.
The Permit::Set is an object that has only one responsibility:
defining and querying permissions. It's not for the DSL.
Seriously, go out and read a book or two on Object Oriented
Programming, SOLID, and in particular, what the Single Responsibility
Principle is.
…On Fri, May 13, 2011 at 1:54 PM, EmmanuelOga ***@***.*** wrote:
"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.
##
Reply to this email directly or view it on GitHub:
https://gist.github.com/970743
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
At the expense of a slightly less beautiful syntax, a lot simpler code to achieve the same:
https://gist.github.com/970853
Example:
etc...