lib/eaco/dsl/resource/permissions.rb
module Eaco
module DSL
class Resource < Base
##
# Permission collector, based on +method_missing+.
#
# Example:
#
# 1 authorize Foobar do
# 2 permissions do
# 3 reader :read_foo, :read_bar
# 4 editor reader, :edit_foo, :edit_bar
# 5 owner editor, :destroy
# 6 end
# 7 end
#
# Within the block, each undefined method call defines a new
# method that returns the given arguments.
#
# After evaluating line 3 above:
#
# >> reader
# => #<Set{ :read_foo, :read_bar }>
#
# The method is used then on line 4, giving the +editor+ role the
# same set of permissions granted to the +reader+, plus its own
# set of permissions:
#
# >> editor
# => #<Set{ :read_foo, :read_bar, :edit_foo, :edit_bar }>
#
class Permissions < Base
##
# Evaluates the given block in the context of a new collector
#
# Returns an Hash of permissions, keyed by role.
#
# >> Permissions.eval do
# | permissions do
# | reader :read
# | editor reader, :edit
# | end
# | end
#
# => {
# | reader: #<Set{ :read },
# | editor: #<Set{ :read, :edit }
# | }
#
# @return [Permissions]
#
def self.eval(*, &block)
super
end
##
# Sets up an hash with a default value of a new Set.
#
def initialize(*)
super
@permissions = Hash.new {|hsh, key| hsh[key] = Set.new}
end
##
# Returns the collected permissions in a plain Hash, lacking the
# default block used by the collector's internals - to give to
# the outside an Hash with a predictable behaviour :-).
#
# @return [Hash]
#
def result
Hash.new.merge(@permissions)
end
private
##
# Here the method name is the role code. If we already have defined
# permissions for the given role, those are returned.
#
# Else, {#save_permission} is called to memoize the given permissions
# for the +role+.
#
# @param role [Symbol]
# @param permissions [Array] permissions to grant to the given +role+.
#
# @return [Set]
#
def method_missing(role, *permissions)
if @permissions.key?(role)
@permissions[role]
else
save_permission(role, permissions)
end
end
##
# Memoizes the given set of permissions for the given role.
#
# @param role [Symbol]
# @param permissions [Array]
#
# @return [Set]
#
# @raise [Malformed] if the syntax is not valid.
#
def save_permission(role, permissions)
permissions = permissions.inject(Set.new) do |set, perm|
if perm.is_a?(Symbol)
set.add perm
elsif perm.is_a?(Set)
set.merge perm
else
raise Malformed, <<-EOF
Invalid #{role} permission definition: #{perm.inspect}.
Permissions can be defined only by plain symbols.
EOF
end
end
@permissions[role].merge(permissions)
end
end
end
end
end