lib/kindergarten/perimeter.rb
module Kindergarten
# A Perimeter is used to define the places where the child can play.
#
# @example
# class ExamplePerimeter < Kindergarten::Perimeter
# purpose :books
#
# govern do
# can :read, Book do |book|
# book.level <= 2
# end
# end
#
# def read(book)
# guard(:read, book)
# book.read
# end
#
# sandbox :read
# end
#
class Perimeter
class << self
attr_reader :exposed_methods, :govern_proc
# Defines a list of sandboxed methods
#
# Can be called multiple times to grow the list.
#
# @example
# class BondModule < Kindergarten::Perimeter
# # ...
# expose :m, :q
#
# # ...
# expose :enemies
# end
#
# BondModule.exposed_methods
# => [ :m, :q, :enemies ]
#
def expose(*list)
@exposed_methods ||= []
@exposed_methods |= list
end
alias_method :sandbox, :expose
# Instruct the Governess how to govern this perimeter
def govern(&proc)
@govern_proc = proc
end
# Get/set the purpose of the perimeter
def purpose(*purpose)
purpose.any? ? @purpose = purpose[0] : @purpose
end
# Get/set the governess of the perimeter
def governess(*klass)
klass.any? ? @governess = klass[0] : @governess
end
# Subscribe to an event from a given purpose
# @param [Symbol] purpose Listen to other perimeters that have this
# purpose
# @param [Symbol] event Listen for events with this name
# @param [Proc,Symbol] block Invoke this on the event
# @example Symbol form
# subscribe :users, :create, :user_created
#
# def user_created(event)
# # ...
# end
#
# @example Block form
# subscribe :users, :update do |event|
# # ...
# end
#
def subscribe(purpose, event, block)
@callbacks ||= {}
@callbacks[purpose] ||= {}
@callbacks[purpose][event] ||= []
@callbacks[purpose][event] << block
end
def subscriptions
@callbacks ||= {}
end
end
attr_reader :child, :governess, :sandbox
# Obtain an un-sandboxed instance for testing purposes
#
# @return [Perimeter] with the given child and/or governess
#
def self.instance(child=nil, governess=nil)
self.new(child, governess)
end
def initialize(sandbox, governess)
if sandbox.is_a? Kindergarten::Sandbox
@sandbox = sandbox
@child = sandbox.child
else
@child = sandbox
end
@governess = governess
unless @governess.nil? || self.class.govern_proc.nil?
@governess.instance_eval(&self.class.govern_proc)
end
end
delegate :scrub, :rinse, :guard, :unguarded,
:to => :governess
# @return [Array] List of sandbox methods
def sandbox_methods
self.class.exposed_methods
end
# Perform a block under the watchful eye off the governess
def governed(method, unguarded=false, &block)
if unguarded == true
self.governess.unguarded do
self.governess.governed(method, &block)
end
else
self.governess.governed(method, &block)
end
end
def fire(event, payload=nil)
if @sandbox.nil?
Kindergarten.warning("There is no sandbox, is this a test-perimeter?")
return
end
@sandbox.purpose[self.class.purpose].fire(event, payload)
end
end
end