kindergarten 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +89 -0
- data/Rakefile +7 -0
- data/TODO.md +6 -0
- data/kindergarten.gemspec +25 -0
- data/lib/kindergarten.rb +17 -0
- data/lib/kindergarten/exceptions.rb +19 -0
- data/lib/kindergarten/governesses.rb +19 -0
- data/lib/kindergarten/governesses/easy_governess.rb +11 -0
- data/lib/kindergarten/governesses/head_governess.rb +144 -0
- data/lib/kindergarten/governesses/strict_governess.rb +48 -0
- data/lib/kindergarten/orm/active_record.rb +42 -0
- data/lib/kindergarten/orm/governess.rb +41 -0
- data/lib/kindergarten/perimeter.rb +129 -0
- data/lib/kindergarten/sandbox.rb +76 -0
- data/lib/kindergarten/version.rb +3 -0
- data/spec/kindergarten/governess_spec.rb +67 -0
- data/spec/kindergarten/perimeter_spec.rb +112 -0
- data/spec/kindergarten/sandbox_spec.rb +52 -0
- data/spec/kindergarten_spec.rb +12 -0
- data/spec/orm/active_record_spec.rb +125 -0
- data/spec/orm_helper.rb +15 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/01_rspec_helper.rb +9 -0
- data/spec/support/db/.gitkeep +0 -0
- data/spec/support/dining_perimeter.rb +14 -0
- data/spec/support/drinking_perimeter.rb +39 -0
- data/spec/support/log/.gitkeep +0 -0
- data/spec/support/puppet_perimeter.rb +19 -0
- data/spec/support/spec_perimeter.rb +35 -0
- metadata +146 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Kindergarten::ORM
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.extend(ClassMethods)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def update_attributes(hash)
|
|
8
|
+
self.class.check(:update_attributes, hash)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def create(*args)
|
|
13
|
+
check(:create, *args)
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def new(*args)
|
|
18
|
+
check(:new, *args)
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def check(method, *args)
|
|
23
|
+
required = self.force_rinsed? ?
|
|
24
|
+
Kindergarten::RinsedHash :
|
|
25
|
+
Kindergarten::ScrubbedHash
|
|
26
|
+
|
|
27
|
+
if args[0].is_a?(Array)
|
|
28
|
+
args.each do |input|
|
|
29
|
+
raise Unscrubbed unless input.is_a?(required)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
elsif args[0].is_a?(Hash)
|
|
33
|
+
raise Unscrubbed unless args[0].is_a?(required)
|
|
34
|
+
|
|
35
|
+
elsif args.any?
|
|
36
|
+
warn "WARNING: #{self.name}.#{method} called with unkown signature"
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Kindergarten
|
|
2
|
+
module ORM
|
|
3
|
+
|
|
4
|
+
# An exception to throw when unscrubbed input is provided
|
|
5
|
+
class Unscrubbed < ArgumentError
|
|
6
|
+
def initialize(msg=nil)
|
|
7
|
+
@msg = msg || "Unscrubbed input provided"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_s
|
|
11
|
+
@msg
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Provide ORM tidyness
|
|
16
|
+
module Governess
|
|
17
|
+
def self.included(base)
|
|
18
|
+
if base.ancestors.include?(::ActiveRecord::Base)
|
|
19
|
+
base.send(:include, Kindergarten::ORM::ActiveRecord)
|
|
20
|
+
else
|
|
21
|
+
raise "Your ORM #{base.superclass} for #{base} is not supported (yet)"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
base.extend Modes
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module Modes
|
|
28
|
+
def force_rinsed
|
|
29
|
+
@force_rinsed = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def force_rinsed?
|
|
33
|
+
@force_rinsed == true ? true : false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
require 'kindergarten/orm/active_record'
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module Kindergarten
|
|
2
|
+
# A Perimeter is used to define the places where the child can play.
|
|
3
|
+
#
|
|
4
|
+
# @example
|
|
5
|
+
# class ExamplePerimeter < Kindergarten::Perimeter
|
|
6
|
+
# purpose :books
|
|
7
|
+
#
|
|
8
|
+
# govern do |child|
|
|
9
|
+
# can :read, Book do |book|
|
|
10
|
+
# book.level <= 2
|
|
11
|
+
# end
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# def read(book)
|
|
15
|
+
# guard(:read, book)
|
|
16
|
+
# book.read
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# sandbox :read
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
class Perimeter
|
|
23
|
+
class << self
|
|
24
|
+
attr_reader :sandboxed_methods, :govern_proc
|
|
25
|
+
|
|
26
|
+
# Define a list of sandbox methods
|
|
27
|
+
def sandbox(*list)
|
|
28
|
+
@sandboxed_methods ||= []
|
|
29
|
+
@sandboxed_methods |= list
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Instruct the Governess how to govern this perimeter
|
|
33
|
+
def govern(&proc)
|
|
34
|
+
@govern_proc = proc
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get/set the purpose of the perimeter
|
|
38
|
+
def purpose(*purpose)
|
|
39
|
+
purpose.any? ? @purpose = purpose[0] : @purpose
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get/set the governess of the perimeter
|
|
43
|
+
def governess(*klass)
|
|
44
|
+
klass.any? ? @governess = klass[0] : @governess
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Subscribe to an event from a given purpose
|
|
48
|
+
# @param [Symbol] purpose Listen to other perimeters that have this
|
|
49
|
+
# purpose
|
|
50
|
+
# @param [Symbol] event Listen for events with this name
|
|
51
|
+
# @param [Proc,Symbol] block Invoke this on the event
|
|
52
|
+
# @example Symbol form
|
|
53
|
+
# subscribe :users, :create, :user_created
|
|
54
|
+
#
|
|
55
|
+
# def user_created(event)
|
|
56
|
+
# # ...
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# @example Block form
|
|
60
|
+
# subscribe :users, :update do |event|
|
|
61
|
+
# # ...
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
def subscribe(purpose, event, block)
|
|
65
|
+
@callbacks ||= {}
|
|
66
|
+
@callbacks[purpose] ||= {}
|
|
67
|
+
@callbacks[purpose][event] ||= []
|
|
68
|
+
@callbacks[purpose][event] << block
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
attr_reader :child, :governess
|
|
73
|
+
|
|
74
|
+
# Obtain an un-sandboxed instance for testing purposes
|
|
75
|
+
#
|
|
76
|
+
# @return [Perimeter] with the given child and/or governess
|
|
77
|
+
#
|
|
78
|
+
def self.instance(child=nil, governess=nil)
|
|
79
|
+
self.new(child, governess)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def initialize(child, governess)
|
|
83
|
+
@child = child
|
|
84
|
+
@governess = governess
|
|
85
|
+
|
|
86
|
+
unless @governess.nil? || self.class.govern_proc.nil?
|
|
87
|
+
@governess.instance_eval(&self.class.govern_proc)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [Array] List of sandbox methods
|
|
92
|
+
def sandbox_methods
|
|
93
|
+
self.class.sandboxed_methods
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @see Governess#scrub
|
|
97
|
+
def scrub(*args)
|
|
98
|
+
self.governess.scrub(*args)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @see Governess#rinse
|
|
102
|
+
def rinse(*args)
|
|
103
|
+
self.governess.rinse(*args)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @see Governess#guard
|
|
107
|
+
def guard(action, target)
|
|
108
|
+
self.governess.guard(action, target)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @see Governess#unguarded
|
|
112
|
+
def unguarded(&block)
|
|
113
|
+
self.governess.unguarded(&block)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def governed(method, unguarded=false, &block)
|
|
117
|
+
if unguarded == true
|
|
118
|
+
self.governess.unguarded do
|
|
119
|
+
self.governess.governed(method, &block)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
else
|
|
123
|
+
self.governess.governed(method, &block)
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Kindergarten
|
|
2
|
+
class Sandbox
|
|
3
|
+
attr_reader :child, :governess, :perimeter
|
|
4
|
+
|
|
5
|
+
def initialize(child)
|
|
6
|
+
@child = child
|
|
7
|
+
@governess = Kindergarten::HeadGoverness.new(child)
|
|
8
|
+
|
|
9
|
+
@perimeter = []
|
|
10
|
+
def @perimeter.include?(other)
|
|
11
|
+
(self.collect(&:class) & [ other.class ]).any?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
@unguarded = false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def extend_perimeter(*perimeter_classes)
|
|
18
|
+
perimeter_classes.each do |perimeter_class|
|
|
19
|
+
# if the perimeter specifies a governess, use that - otherwise appoint
|
|
20
|
+
# the head governess
|
|
21
|
+
child = self.child
|
|
22
|
+
governess = perimeter_class.governess ?
|
|
23
|
+
perimeter_class.governess.new(child) :
|
|
24
|
+
self.governess
|
|
25
|
+
|
|
26
|
+
perimeter = perimeter_class.new(child, governess)
|
|
27
|
+
|
|
28
|
+
# the head governess must know all the rules
|
|
29
|
+
unless governess == self.governess || perimeter_class.govern_proc.nil?
|
|
30
|
+
self.governess.instance_eval(&perimeter_class.govern_proc)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
raise ArgumentError.new(
|
|
34
|
+
"Module must inherit from Kindergarten::Perimeter"
|
|
35
|
+
) unless perimeter.kind_of?(Kindergarten::Perimeter)
|
|
36
|
+
|
|
37
|
+
@perimeter << perimeter unless @perimeter.include?(perimeter)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
alias_method :load_perimeter, :extend_perimeter
|
|
41
|
+
alias_method :load_module, :extend_perimeter
|
|
42
|
+
|
|
43
|
+
def unguarded(&block)
|
|
44
|
+
@unguarded = true
|
|
45
|
+
yield
|
|
46
|
+
@unguarded = false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def allows?(action, target)
|
|
50
|
+
governess.can?(action, target)
|
|
51
|
+
end
|
|
52
|
+
alias_method :allowed?, :allows?
|
|
53
|
+
|
|
54
|
+
def disallows?(action, target)
|
|
55
|
+
governess.cannot?(action, target)
|
|
56
|
+
end
|
|
57
|
+
alias_method :disallowed?, :disallows?
|
|
58
|
+
|
|
59
|
+
def method_missing(name, *args, &block)
|
|
60
|
+
super
|
|
61
|
+
rescue NoMethodError => ex
|
|
62
|
+
@perimeter.each do |perimeter|
|
|
63
|
+
if perimeter.sandbox_methods.include?(name)
|
|
64
|
+
return perimeter.governed(name, @unguarded) do
|
|
65
|
+
perimeter.send(name, *args, &block)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# still here? then there is no part of the perimeter that provides method
|
|
71
|
+
raise ex
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Kindergarten::HeadGoverness do
|
|
4
|
+
before(:each) do
|
|
5
|
+
@governess = Kindergarten::HeadGoverness.new("child")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "should include CanCan ability" do
|
|
9
|
+
@governess.should be_kind_of(CanCan::Ability)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe :governing do
|
|
13
|
+
it "should guard the child" do
|
|
14
|
+
expect {
|
|
15
|
+
@governess.guard(:free, "Willy")
|
|
16
|
+
}.to raise_error(Kindergarten::AccessDenied)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should keep a closed eye" do
|
|
20
|
+
expect {
|
|
21
|
+
@governess.unguarded do
|
|
22
|
+
@governess.guard(:free, "Willy")
|
|
23
|
+
end
|
|
24
|
+
}.to_not raise_error(Kindergarten::AccessDenied)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe :washing do
|
|
29
|
+
it "should scrub attributes" do
|
|
30
|
+
attr = { a: 1, b: 2, c: 3 }
|
|
31
|
+
|
|
32
|
+
scrubbed = @governess.scrub(attr, :a, :c)
|
|
33
|
+
scrubbed.should_not be_has_key(:b)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should return a ScrubbedHash after scrubbing" do
|
|
37
|
+
attr = { a: 1, b: 2, c: 3 }
|
|
38
|
+
|
|
39
|
+
scrubbed = @governess.scrub(attr, :a, :c)
|
|
40
|
+
scrubbed.should be_kind_of(Kindergarten::ScrubbedHash)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "should rinse attributes" do
|
|
44
|
+
attr = { a: "1", b: "2a", c: "3" }
|
|
45
|
+
rinsed = @governess.rinse(attr, a: /(\d+)/, b: /(\D+)/)
|
|
46
|
+
|
|
47
|
+
rinsed.should_not be_has_key(:c)
|
|
48
|
+
rinsed[:a].should eq "1"
|
|
49
|
+
rinsed[:b].should eq "a"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should pass attributes" do
|
|
53
|
+
attr = { a: "1", b: "2a", c: "3" }
|
|
54
|
+
rinsed = @governess.rinse(attr, a: :pass, c: :pass)
|
|
55
|
+
|
|
56
|
+
rinsed.should_not be_has_key(:b)
|
|
57
|
+
rinsed[:a].should eq "1"
|
|
58
|
+
rinsed[:c].should eq "3"
|
|
59
|
+
end
|
|
60
|
+
it "should return a RinsedHash after rinsing" do
|
|
61
|
+
attr = { a: "1", b: "2a", c: "3" }
|
|
62
|
+
rinsed = @governess.rinse(attr, a: /(\d+)/, b: /(\d+)/)
|
|
63
|
+
|
|
64
|
+
rinsed.should be_kind_of(Kindergarten::RinsedHash)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Kindergarten::Perimeter do
|
|
4
|
+
describe :class do
|
|
5
|
+
it "should have a :sandbox method" do
|
|
6
|
+
SpecPerimeter.should respond_to(:sandbox)
|
|
7
|
+
SpecPerimeter.should respond_to(:sandboxed_methods)
|
|
8
|
+
end
|
|
9
|
+
it "should return sandboxed methods" do
|
|
10
|
+
SpecPerimeter.sandboxed_methods.should_not be_empty
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should have a :govern method" do
|
|
14
|
+
SpecPerimeter.should respond_to(:govern)
|
|
15
|
+
SpecPerimeter.should respond_to(:govern_proc)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "should return a govern proc" do
|
|
19
|
+
SpecPerimeter.govern_proc.should be_kind_of(Proc)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe :instance do
|
|
24
|
+
it "should have an initialize method with 2 arguments" do
|
|
25
|
+
SpecPerimeter.instance.method(:initialize).arity.should == 2
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should have a :guard method" do
|
|
29
|
+
SpecPerimeter.instance.should respond_to(:guard)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should have an :unguarded method" do
|
|
33
|
+
SpecPerimeter.instance.should respond_to(:unguarded)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should have a :scrub method" do
|
|
37
|
+
SpecPerimeter.instance.should respond_to(:scrub)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should have a :rinse method" do
|
|
41
|
+
SpecPerimeter.instance.should respond_to(:rinse)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "should have a :sandbox_methods method" do
|
|
45
|
+
SpecPerimeter.instance.should respond_to(:sandbox_methods)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe :sandbox do
|
|
50
|
+
before(:each) do
|
|
51
|
+
@sandbox = Kindergarten.sandbox("child")
|
|
52
|
+
@sandbox.extend_perimeter(SpecPerimeter)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "should have the SpecPerimeter" do
|
|
56
|
+
@sandbox.perimeter.collect(&:class).should include(SpecPerimeter)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should fill the governess" do
|
|
60
|
+
@sandbox.governess.should_not be_empty
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "should have the sandboxed method" do
|
|
64
|
+
@sandbox.sandboxed.should eq "child"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "should have the guarded method" do
|
|
68
|
+
expect {
|
|
69
|
+
@sandbox.guarded
|
|
70
|
+
}.to raise_error(Kindergarten::AccessDenied)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "should not have the unboxed method" do
|
|
74
|
+
expect {
|
|
75
|
+
@sanbox.unboxed
|
|
76
|
+
}.to raise_error(NoMethodError)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should have the not_guarded method" do
|
|
80
|
+
@sandbox.not_guarded.should eq "OK"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should have the unsafe method" do
|
|
84
|
+
expect {
|
|
85
|
+
@sandbox.unsafe
|
|
86
|
+
}.to raise_error(Kindergarten::Perimeter::Unguarded)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe :unguarded do
|
|
91
|
+
before(:each) do
|
|
92
|
+
@sandbox = Kindergarten.sandbox("child")
|
|
93
|
+
@sandbox.extend_perimeter(SpecPerimeter)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "should allow the unsafe method" do
|
|
97
|
+
expect {
|
|
98
|
+
@sandbox.unguarded do
|
|
99
|
+
@sandbox.unsafe
|
|
100
|
+
end
|
|
101
|
+
}.to_not raise_error(Kindergarten::Perimeter::Unguarded)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "should allow the not_guarded method" do
|
|
105
|
+
expect {
|
|
106
|
+
@sandbox.unguarded do
|
|
107
|
+
@sandbox.not_guarded
|
|
108
|
+
end
|
|
109
|
+
}.to_not raise_error(Kindergarten::Perimeter::Unguarded)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|