Idea
ApplicationPolicy.can? user, :new, Post
ApplicationPolicy.can? user, :create, post
ApplicationPolicy.can? user, :edit, post
ApplicationPolicy.can? user, :register_ship
ApplicationPolicy.authorize! user, :new, Post
ApplicationPolicy.authorize! user, :new, Post
ApplicationPolicy.authorize! user, :create, post
ApplicationPolicy.authorize! user, :edit, post
ApplicationPolicy.authorize! user, :sign_for_ship
module Ship::Policy
extend Authorization::Policy
# this is the same as `def can_maintain_ship_account?`
can :maintain, ShipAccount do |user, account|
user.admin? || member?(user, account)
end
can :create, UpcomingPageMessage do |user, message|
# nested rules, all permissions are just methods
can_maintain_ship_account?(user, message.account) && message.account.active?
end
# support for single rules
can :update_account do |user|
# ...
end
# allow for guest access
can :sign_for_ship, guest: true do |user|
!user
end
# private methods for helpers
private
def member?(user)
# ...
end
end
can
is just a convince helper to create methods on a module:
Ship::Policy#can_maintain_ship_account?(user)
Ship::Policy#can_upcoming_page_message?(user)
Ship::Policy#can_update_account?(user)
Then all policies are grouped into a central application policy
module ApplicationPolicy
extend Authorization::Facade
extend Posts::Policy
extend Ship::Policy
end
quick and dirty
module Authorization
class AccessDenied < StandardError
attr_reader :user, :action, :subject
def initialize(user, action, subject)
@user = user
@action = action
@subject = subject
super 'Not authorized'
end
end
module Policy
def can(actions, subject = nil, allow_guest: false, &block)
Array(actions).each do |action|
define_method Utils.rule_name(action, subject) do |*args|
(args[0] || allow_guest) && !!block.call(*args)
end
end
end
end
module Facade
def can?(user, action, subject = nil)
public_send Utils.name(action, subject), user, subject
end
def authorize!(*args)
raise AccessDenied unless can?(*args)
end
end
module Utils
extend self
def rule_name(action, subject)
name = "can_#{ action }"
name << "_#{ subject_to_string(subject).underscore.gsub('/', '_') }" if subject
name << '?'
end
private
def subject_to_string(subject)
case subject
when Class, String, Symbol then subject.to_s
else subject.class.to_s
end
end
end
end