coffeeaddict/kindergarten

View on GitHub
lib/kindergarten/perimeter.rb

Summary

Maintainability
A
0 mins
Test Coverage
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