Skip to content

Commit

Permalink
Merge pull request #2 from coffeeaddict/feature/purpose-namespace
Browse files Browse the repository at this point in the history
Feature/purpose namespace

[ci skip]
  • Loading branch information
coffeeaddict committed Oct 28, 2012
2 parents b9dee22 + e46fa7e commit 60cba7d
Show file tree
Hide file tree
Showing 17 changed files with 270 additions and 127 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ child = User.find(2)
# define a module (perimeter) for the child to play in
class MyPlayModule < Kindergarten::Perimeter
# use can-can rules to govern the perimeter
govern do |child|
govern do
can :watch, Television
cannot :watch, CableTV

Expand Down
6 changes: 1 addition & 5 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

## Code

* Consider ```sandbox.projects.all``` -> ```sandbox.$purpose.$method```

## Travis

* Switch to mysql for testing jrubies
* Further implement ```sandbox.projects.all``` -> ```sandbox.$purpose.$method```

## Documentation

Expand Down
5 changes: 3 additions & 2 deletions lib/kindergarten.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

require "kindergarten/version"
require "kindergarten/sandbox"
require "kindergarten/exceptions"
require "kindergarten/governesses"
require "kindergarten/purpose"
require "kindergarten/perimeter"
require "kindergarten/governesses"
require "kindergarten/exceptions"

module Kindergarten
class << self
Expand Down
37 changes: 36 additions & 1 deletion lib/kindergarten/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,43 @@ def initialize(action, target, opts)
end
end

class Sandbox
class NoPurposeError < NoMethodError
def initialize(purpose, sandbox)
@purpose = purpose
@sandbox = sandbox
end

def to_s
"undefined purpose '#{@purpose}' for #{@sandbox}"
end
end
end

class Perimeter
# Signals bad sandbox method implementation
class NoExposedMethods < NoMethodError
def initialize(perimeter)
@perimeter = perimeter
super
end

def to_s
"The module #{@perimeter.name} does not expose any methods."
end
end

class NoPurpose < ArgumentError
def initialize(perimeter)
@perimeter = perimeter
super
end

def to_s
"The module #{@perimeter.name} does not have a purpose."
end
end

# Signals bad sandbox method implementation
class Unguarded < SecurityError; end
end
end
1 change: 1 addition & 0 deletions lib/kindergarten/governesses/head_governess.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Kindergarten
#
class HeadGoverness
include CanCan::Ability
attr_reader :child

def initialize(child)
@child = child
Expand Down
54 changes: 27 additions & 27 deletions lib/kindergarten/perimeter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Kindergarten
# class ExamplePerimeter < Kindergarten::Perimeter
# purpose :books
#
# govern do |child|
# govern do
# can :read, Book do |book|
# book.level <= 2
# end
Expand All @@ -21,13 +21,29 @@ module Kindergarten
#
class Perimeter
class << self
attr_reader :sandboxed_methods, :govern_proc
attr_reader :exposed_methods, :govern_proc

# Define a list of sandbox methods
def sandbox(*list)
@sandboxed_methods ||= []
@sandboxed_methods |= list
# 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)
Expand Down Expand Up @@ -88,31 +104,15 @@ def initialize(child, governess)
end
end

delegate :scrub, :rinse, :guard, :unguarded,
:to => :governess

# @return [Array] List of sandbox methods
def sandbox_methods
self.class.sandboxed_methods
end

# @see Governess#scrub
def scrub(*args)
self.governess.scrub(*args)
end

# @see Governess#rinse
def rinse(*args)
self.governess.rinse(*args)
end

# @see Governess#guard
def guard(action, target)
self.governess.guard(action, target)
end

# @see Governess#unguarded
def unguarded(&block)
self.governess.unguarded(&block)
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
Expand Down
41 changes: 41 additions & 0 deletions lib/kindergarten/purpose.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module Kindergarten
# Keep track of a single purpose
class Purpose
attr_reader :name, :methods, :sandbox, :subscriptions

def initialize(name, sandbox)
@name = name
@sandbox = sandbox
@methods = {}
@subscriptions = []
end

def add_perimeter(perimeter, instance)
if perimeter.exposed_methods.blank?
raise Kindergarten::Perimeter::NoExposedMethods.new(perimeter)
end

perimeter.exposed_methods.each do |name|
if @methods.has_key?(name)
warn "WARNING: overriding already sandboxed method #{@name}.#{name}"
end

@methods[name] = instance
end
end

def method_missing(name, *args, &block)
super

rescue NoMethodError => ex
unless methods.has_key?(name)
raise ex
end

perimeter = methods[name]
perimeter.governed(name, sandbox.unguarded?) do
perimeter.send(name, *args, &block)
end
end
end
end
44 changes: 29 additions & 15 deletions lib/kindergarten/sandbox.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module Kindergarten
class Sandbox
attr_reader :child, :governess, :perimeter
attr_reader :child, :governess, :perimeter, :purpose

def initialize(child)
@child = child
@governess = Kindergarten::HeadGoverness.new(child)

@perimeter = []
@purpose = {}
@perimeter = []
def @perimeter.include?(other)
(self.collect(&:class) & [ other.class ]).any?
end
Expand All @@ -25,50 +26,63 @@ def extend_perimeter(*perimeter_classes)

perimeter = perimeter_class.new(child, governess)

raise ArgumentError.new(
"Module must inherit from Kindergarten::Perimeter"
) unless perimeter.kind_of?(Kindergarten::Perimeter)

self.extend_purpose(perimeter.class, perimeter)

# the head governess must know all the rules
unless governess == self.governess || perimeter_class.govern_proc.nil?
self.governess.instance_eval(&perimeter_class.govern_proc)
end

raise ArgumentError.new(
"Module must inherit from Kindergarten::Perimeter"
) unless perimeter.kind_of?(Kindergarten::Perimeter)

@perimeter << perimeter unless @perimeter.include?(perimeter)
end
end
alias_method :load_perimeter, :extend_perimeter
alias_method :load_module, :extend_perimeter

def extend_purpose(perimeter, instance)
name = perimeter.purpose || raise(
Kindergarten::Perimeter::NoPurpose.new(perimeter)
)
name = name.to_sym

self.purpose[name] ||= Kindergarten::Purpose.new(name, self)
self.purpose[name].add_perimeter(perimeter, instance)
end

def unguarded(&block)
@unguarded = true
yield
@unguarded = false
end

def unguarded?
@unguarded == true ? true : false
end

def allows?(action, target)
governess.can?(action, target)
end
alias_method :allowed?, :allows?

def disallows?(action, target)
governess.cannot?(action, target)
end
alias_method :disallowed?, :disallows?

# TODO: Find a purpose and call that - move this block to Purpose
def method_missing(name, *args, &block)
super

rescue NoMethodError => ex
@perimeter.each do |perimeter|
if perimeter.sandbox_methods.include?(name)
return perimeter.governed(name, @unguarded) do
perimeter.send(name, *args, &block)
end
end
unless purpose.has_key?(name)
raise Kindergarten::Sandbox::NoPurposeError.new(name, self)
end

# still here? then there is no part of the perimeter that provides method
raise ex
return purpose[name]
end
end
end
Expand Down
Loading

0 comments on commit 60cba7d

Please sign in to comment.