thwart 0.0.0
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/.document +5 -0
- data/.gitignore +24 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +30 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +1 -0
- data/examples/a_complete_example.rb +56 -0
- data/examples/example_helper.rb +45 -0
- data/lib/thwart/action_group_builder.rb +62 -0
- data/lib/thwart/actions_store.rb +89 -0
- data/lib/thwart/actor.rb +36 -0
- data/lib/thwart/canable.rb +30 -0
- data/lib/thwart/dsl.rb +43 -0
- data/lib/thwart/enforcer.rb +15 -0
- data/lib/thwart/resource.rb +27 -0
- data/lib/thwart/role.rb +81 -0
- data/lib/thwart/role_builder.rb +143 -0
- data/lib/thwart/role_registry.rb +75 -0
- data/lib/thwart.rb +67 -0
- data/spec/action_group_builder_spec.rb +68 -0
- data/spec/actions_store_spec.rb +74 -0
- data/spec/actor_spec.rb +45 -0
- data/spec/canable_spec.rb +41 -0
- data/spec/dsl_spec.rb +103 -0
- data/spec/enforcer_spec.rb +17 -0
- data/spec/resource_spec.rb +42 -0
- data/spec/role_builder_spec.rb +197 -0
- data/spec/role_registry_spec.rb +137 -0
- data/spec/role_spec.rb +146 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/thwart_spec.rb +60 -0
- metadata +139 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'active_support/core_ext/hash/deep_merge'
|
2
|
+
|
3
|
+
module Thwart
|
4
|
+
class ActionOrGroupNotFoundError < StandardError; end
|
5
|
+
class OustideRoleDefinitionError < StandardError; end
|
6
|
+
|
7
|
+
class RoleBuilder
|
8
|
+
include ActiveSupport::Callbacks
|
9
|
+
define_callbacks :build_role, :scope => [:kind, :name]
|
10
|
+
|
11
|
+
attr_accessor :last_built_role
|
12
|
+
attr_reader :actionables_store
|
13
|
+
delegate :actionables, :to => :actionables_store
|
14
|
+
|
15
|
+
def self.empty_role
|
16
|
+
Class.new do
|
17
|
+
include Thwart::Role
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(a_store)
|
22
|
+
@actionables_store = a_store
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_role(name, options = {}, &block)
|
26
|
+
@role = self.class.empty_role.new # Start empty role
|
27
|
+
@current_response = true # Assume the first permission definitions are allows
|
28
|
+
run_callbacks :build_role do
|
29
|
+
# Add parents
|
30
|
+
@role.name = name
|
31
|
+
@role.parents = options[:parents] if options[:parents]
|
32
|
+
|
33
|
+
# Run DSL block
|
34
|
+
if block_given?
|
35
|
+
@dsl ||= Thwart::Dsl.new
|
36
|
+
@dsl.all = true
|
37
|
+
@dsl.evaluate(self, &block)
|
38
|
+
end
|
39
|
+
self.last_built_role = @role
|
40
|
+
end
|
41
|
+
# Unset the @role instance variable to disable DSL methods and return the role
|
42
|
+
r = @role
|
43
|
+
@role = nil
|
44
|
+
return r
|
45
|
+
end
|
46
|
+
|
47
|
+
def define_permission(name, *resources, &block)
|
48
|
+
ensure_in_dsl!
|
49
|
+
# Shift off first argument sent from method missing and convert any action groups to an array of actions
|
50
|
+
raise ArgumentError, "Unrecognized action or action group #{name}" if !self.actionables.has_key?(name)
|
51
|
+
names = self.actionables[name]
|
52
|
+
|
53
|
+
# Pop of last hash argument from method missing and merge in default options and :if block
|
54
|
+
options = {:if => false, :unless => false}
|
55
|
+
options.merge!(resources.pop) if resources.last.respond_to?(:keys)
|
56
|
+
options[:if] = block if block_given?
|
57
|
+
# Allow :all or blank resource specifiers
|
58
|
+
if resources.nil? || resources.empty? || resources.any? {|r| r == :all}
|
59
|
+
resources = [:_other]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generate response based on @current_response and optional procs
|
63
|
+
generated_response = generate_response(options)
|
64
|
+
response_hash = hash_with_value(resources, generated_response)
|
65
|
+
|
66
|
+
# Merge into existing role definition
|
67
|
+
@role.responses.deep_merge!(hash_with_value(names) do |k|
|
68
|
+
response_hash
|
69
|
+
end)
|
70
|
+
end
|
71
|
+
|
72
|
+
def default(bool)
|
73
|
+
ensure_in_dsl!
|
74
|
+
@role.default_response = bool
|
75
|
+
end
|
76
|
+
|
77
|
+
def include(*args)
|
78
|
+
ensure_in_dsl!
|
79
|
+
@role.parents += args
|
80
|
+
end
|
81
|
+
|
82
|
+
def allow(&block)
|
83
|
+
evaluate_with_response(true, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def deny(&block)
|
87
|
+
evaluate_with_response(false, &block)
|
88
|
+
end
|
89
|
+
|
90
|
+
def evaluate_with_response(response, &block)
|
91
|
+
ensure_in_dsl!
|
92
|
+
old = @current_response
|
93
|
+
@current_response = response
|
94
|
+
@dsl.evaluate(self, &block)
|
95
|
+
@current_response = old
|
96
|
+
end
|
97
|
+
|
98
|
+
def respond_to?(name)
|
99
|
+
return true if self.actionables.has_key?(name)
|
100
|
+
super
|
101
|
+
end
|
102
|
+
|
103
|
+
def method_missing(name, *args, &block)
|
104
|
+
return define_permission(name, *args, &block) if self.respond_to?(name)
|
105
|
+
super
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def generate_response(options)
|
110
|
+
# If a proc has been supplied
|
111
|
+
if(options[:if].respond_to?(:call) || options[:unless].respond_to?(:call))
|
112
|
+
# Copy variable for scope
|
113
|
+
check = @current_response
|
114
|
+
if options[:unless].respond_to?(:call)
|
115
|
+
check = !check
|
116
|
+
options[:if] = options[:unless]
|
117
|
+
end
|
118
|
+
|
119
|
+
return Proc.new do |*args|
|
120
|
+
check == options[:if].call(*args)
|
121
|
+
end
|
122
|
+
else
|
123
|
+
@current_response
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def hash_with_value(array, val = nil, &block)
|
128
|
+
array.inject({}) do |acc, k|
|
129
|
+
acc[k] = if block_given?
|
130
|
+
yield k
|
131
|
+
else
|
132
|
+
val
|
133
|
+
end
|
134
|
+
acc
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def ensure_in_dsl!
|
139
|
+
raise OustideRoleDefinitionError, "You can only define role permissions inside a role defintion block!" if @role.nil?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Thwart
|
2
|
+
class DuplicateRoleError < StandardError; end
|
3
|
+
class MissingRoleError < StandardError; end
|
4
|
+
|
5
|
+
class RoleRegistry
|
6
|
+
def roles
|
7
|
+
@roles ||= []
|
8
|
+
@roles
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(role)
|
12
|
+
raise DuplicateRoleError, "Role #{role} already exists in the role registry!" if self.has_role?(role)
|
13
|
+
@roles << role
|
14
|
+
end
|
15
|
+
|
16
|
+
def query(actor, resource, action)
|
17
|
+
role = self.find_actor_role(actor)
|
18
|
+
resource = self.find_resource_identifier(resource)
|
19
|
+
if role.nil? || !self.has_role?(role)
|
20
|
+
raise MissingRoleError, "Role #{role} could not be found in the registry!" if Thwart.actor_must_play_role
|
21
|
+
else
|
22
|
+
q = [role]
|
23
|
+
while r = q.shift
|
24
|
+
resp = r.query(actor, resource, action)
|
25
|
+
if resp != nil
|
26
|
+
return resp # positive/negative response from the role, a rule governs the role on this query
|
27
|
+
else
|
28
|
+
q = q | r.parents # add this roles parents to the query queue
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Thwart.default_query_response # return was not called above, return the default
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_role?(role)
|
37
|
+
self.roles.include?(role)
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_actor_role(actor)
|
41
|
+
r = actor.thwart_role if actor.respond_to?(:thwart_role)
|
42
|
+
r ||= r.to_sym if r.respond_to?(:to_sym)
|
43
|
+
if r.is_a?(Symbol)
|
44
|
+
r = self.roles.find {|a| a.name == r}
|
45
|
+
end
|
46
|
+
r
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_resource_identifier(resource)
|
50
|
+
r ||= resource.thwart_name if resource.respond_to?(:thwart_name)
|
51
|
+
if resource.class != Class
|
52
|
+
r ||= resource.class.thwart_name if resource.class.respond_to?(:thwart_name)
|
53
|
+
r ||= resource.class.name.downcase.to_sym if Thwart.all_classes_are_resources
|
54
|
+
end
|
55
|
+
r
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(role_creator = nil)
|
59
|
+
self.monitor_builder(role_creator) if !role_creator.nil?
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def monitor_builder(role_creator)
|
64
|
+
registry = self
|
65
|
+
unless role_creator.nil?
|
66
|
+
role_creator.class.set_callback :build_role, :after do |object|
|
67
|
+
registry.add(object.last_built_role)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
@role_creator.class.reset_callbacks(:build_role)
|
71
|
+
end
|
72
|
+
@role_creator = role_creator
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/thwart.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/callbacks'
|
3
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
|
+
require "active_support/core_ext/array/wrap"
|
6
|
+
|
7
|
+
|
8
|
+
require 'thwart/canable'
|
9
|
+
require 'thwart/actions_store'
|
10
|
+
require 'thwart/action_group_builder'
|
11
|
+
require 'thwart/role_registry'
|
12
|
+
require 'thwart/role_builder'
|
13
|
+
require 'thwart/role'
|
14
|
+
require 'thwart/resource'
|
15
|
+
require 'thwart/actor'
|
16
|
+
require 'thwart/enforcer'
|
17
|
+
require 'thwart/dsl'
|
18
|
+
|
19
|
+
module Thwart
|
20
|
+
# autoload :Cans, 'thwart/canable'
|
21
|
+
# autoload :Ables, 'thwart/canable'
|
22
|
+
# autoload :ActionsStore, 'thwart/actions_store'
|
23
|
+
# autoload :ActionGroupBuilder, 'thwart/action_group_builder'
|
24
|
+
# autoload :RoleRegistry, 'thwart/role_registry'
|
25
|
+
# autoload :RoleBuilder, 'thwart/role_builder'
|
26
|
+
# autoload :Role, 'thwart/role'
|
27
|
+
# autoload :DefaultRole, 'thwart/role'
|
28
|
+
# autoload :Resource, 'thwart/resource'
|
29
|
+
# autoload :Actor, 'thwart/actor'
|
30
|
+
# autoload :Dsl, 'thwart/dsl'
|
31
|
+
|
32
|
+
# The default can => able methods for CRUD
|
33
|
+
CrudActions = {:create => :creatable, :view => :viewable, :update => :updatable, :destroy => :destroyable}
|
34
|
+
|
35
|
+
Actions = ActionsStore.new
|
36
|
+
Roles = RoleRegistry.new
|
37
|
+
|
38
|
+
class << self
|
39
|
+
attr_reader :actionables_dsl, :role_dsl
|
40
|
+
attr_accessor :default_query_response, :role_registry, :actor_must_play_role, :all_classes_are_resources
|
41
|
+
delegate :create_action, :to => "Thwart::Actions"
|
42
|
+
delegate :create_action_group, :to => :actionables_dsl
|
43
|
+
delegate :create_role, :to => :role_dsl
|
44
|
+
delegate :query, :to => "Thwart::Roles"
|
45
|
+
|
46
|
+
def configure(&block)
|
47
|
+
# Create builder DSLs for this configuration block
|
48
|
+
@actionables_dsl = ActionGroupBuilder.new(Actions)
|
49
|
+
@role_dsl = RoleBuilder.new(@actionables_dsl)
|
50
|
+
Roles.monitor_builder(@role_dsl)
|
51
|
+
|
52
|
+
# Configure
|
53
|
+
dsl = Thwart::Dsl.new(:role => :create_role, :action => :create_action, :action_group => :create_action_group)
|
54
|
+
dsl.all = false
|
55
|
+
dsl.evaluate(self, &block)
|
56
|
+
|
57
|
+
# Unset and stop monitoring builder DSLs so they can be GC'd
|
58
|
+
@actionables_dsl = nil
|
59
|
+
@role_dsl = nil
|
60
|
+
Roles.monitor_builder(nil)
|
61
|
+
self
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class MissingAttributeError < StandardError; end
|
66
|
+
class NoPermissionError < StandardError; end
|
67
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::ActionGroupBuilder do
|
4
|
+
before do
|
5
|
+
@store = double("Actions Store")
|
6
|
+
@store.class.stub(:set_callback)
|
7
|
+
@builder = Thwart::ActionGroupBuilder.new(@store)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should add some simple actionables" do
|
11
|
+
@builder.add_actionable(:sing)
|
12
|
+
@builder.add_actionable(:dance, [:laugh, :play])
|
13
|
+
@builder.actionables.should == {:sing => [:sing], :dance => [:laugh, :play]}
|
14
|
+
end
|
15
|
+
it "should have the crud action group if added" do
|
16
|
+
@store.should_receive(:add_crud!)
|
17
|
+
Thwart::CrudActions.keys.each do |k|
|
18
|
+
@builder.add_actionable(k)
|
19
|
+
end
|
20
|
+
@builder.add_crud_group!
|
21
|
+
@builder.actionables.include?(:crud).should == true
|
22
|
+
end
|
23
|
+
it "should set an action group" do
|
24
|
+
@builder.add_actionable(:one)
|
25
|
+
@builder.add_actionable(:two)
|
26
|
+
@builder.create_action_group(:stuff, [:one, :two])
|
27
|
+
@builder.actionables[:stuff].should == [:one, :two]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should initialize a callback on the action creator" do
|
31
|
+
store_class = Class.new do
|
32
|
+
include ActiveSupport::Callbacks
|
33
|
+
end
|
34
|
+
store_class.should_receive(:set_callback)
|
35
|
+
Thwart::ActionGroupBuilder.new(store_class.new)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "action group resolution" do
|
39
|
+
it "should find existing actions" do
|
40
|
+
@builder.stub(:actionables).and_return({:view => [:view]})
|
41
|
+
@builder.resolve_action_group(:view).should == [:view]
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with crud and one group" do
|
45
|
+
before do
|
46
|
+
@actions = [:create, :update, :destroy]
|
47
|
+
@actionables = @actions.inject({}) {|acc, k| acc[k] = [k]; acc}.merge({:manage => @actions})
|
48
|
+
@builder.stub(:actionables).and_return(@actionables)
|
49
|
+
end
|
50
|
+
it "should find arrays of existing actions" do
|
51
|
+
@builder.resolve_action_group(@actions).should == @actions
|
52
|
+
end
|
53
|
+
it "should build action groups of other action groups" do
|
54
|
+
@builder.resolve_action_group(:manage).should == @actions
|
55
|
+
end
|
56
|
+
|
57
|
+
context "and extra actions" do
|
58
|
+
before do
|
59
|
+
@actionables = @actionables.merge([:one, :two, :three].inject({}) {|acc, k| acc[k] = [k]; acc}).merge({:another => [:one, :two]})
|
60
|
+
@builder.stub(:actionables).and_return(@actionables)
|
61
|
+
end
|
62
|
+
it "should build action groups of other action groups and other actions" do
|
63
|
+
@builder.resolve_action_group([:manage, :another, :three]).should include(:one, :two, :three, :create, :update, :destroy)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::ActionsStore do
|
4
|
+
before do
|
5
|
+
@actions = Thwart::ActionsStore.new
|
6
|
+
end
|
7
|
+
describe "with no actions set up" do
|
8
|
+
before do
|
9
|
+
@actions.should_receive(:actions).and_return({})
|
10
|
+
end
|
11
|
+
it "shouldn't find any can methods" do
|
12
|
+
@actions.find_can(:can_view?).should == false
|
13
|
+
end
|
14
|
+
it "shouldn't find any able methods" do
|
15
|
+
@actions.find_able(:viewable_by?).should == false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
describe "with a custom action" do
|
19
|
+
before do
|
20
|
+
@actions.create_action(:foo, :fooable)
|
21
|
+
end
|
22
|
+
it "should have the can and able present" do
|
23
|
+
@actions.has_can?(:foo).should == true
|
24
|
+
@actions.has_able?(:fooable).should == true
|
25
|
+
end
|
26
|
+
it "shouln't have any other cans or ables present" do
|
27
|
+
@actions.has_can?(:bar).should == false
|
28
|
+
@actions.has_able?(:barable).should == false
|
29
|
+
end
|
30
|
+
it "should find the can method" do
|
31
|
+
@actions.find_can(:can_foo?).should == :foo
|
32
|
+
end
|
33
|
+
it "should find the able method" do
|
34
|
+
@actions.find_able(:fooable_by?).should == :fooable
|
35
|
+
end
|
36
|
+
it "should get the can from the able" do
|
37
|
+
@actions.can_from_able(:fooable).should == :foo
|
38
|
+
end
|
39
|
+
end
|
40
|
+
it "should create several actions from an array" do
|
41
|
+
some_actions = {:bleh => :blehable, :beef => :beefable}
|
42
|
+
@actions.create_action(some_actions)
|
43
|
+
@actions.actions.should == some_actions
|
44
|
+
end
|
45
|
+
describe "with the crud actions" do
|
46
|
+
before do
|
47
|
+
@actions.add_crud!
|
48
|
+
end
|
49
|
+
# This isnt DRY at all but I want to know which one is failing
|
50
|
+
it "should have C for create" do
|
51
|
+
@actions.has_can?(:create).should == true
|
52
|
+
end
|
53
|
+
it "should have R for ...er... view" do
|
54
|
+
@actions.has_can?(:view).should == true
|
55
|
+
end
|
56
|
+
it "should have U for update" do
|
57
|
+
@actions.has_can?(:update).should == true
|
58
|
+
end
|
59
|
+
it "should have D for delete" do
|
60
|
+
@actions.has_can?(:destroy).should == true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should fire the add callback and set the last added" do
|
65
|
+
klass = Thwart::ActionsStore.clone
|
66
|
+
actionz = klass.new
|
67
|
+
receiver = double('receiver')
|
68
|
+
receiver.should_receive(:before).with(actionz)
|
69
|
+
klass.set_callback :add, :before, receiver
|
70
|
+
|
71
|
+
actionz.create_action(:do, :doable)
|
72
|
+
actionz.last_action.should == :do
|
73
|
+
end
|
74
|
+
end
|
data/spec/actor_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::Actor do
|
4
|
+
it "should not add the thwart_role method until thwart_access is called" do
|
5
|
+
actor_class = generic_model("Generic Resource") do
|
6
|
+
include Thwart::Actor
|
7
|
+
end
|
8
|
+
actor_class.new.should_not respond_to(:thwart_role)
|
9
|
+
actor_class.thwart_access
|
10
|
+
actor_class.new.should respond_to(:thwart_role)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "thwart_role defining and finding" do
|
14
|
+
before do
|
15
|
+
@actor_class = generic_model("Generic Resource") do
|
16
|
+
include Thwart::Actor
|
17
|
+
def arbitrary_attribute
|
18
|
+
:arb_return
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
it "should return a default role" do
|
23
|
+
@actor_class.thwart_access do
|
24
|
+
role :a_role
|
25
|
+
end
|
26
|
+
@actor_class.new.thwart_role.should == :a_role
|
27
|
+
end
|
28
|
+
it "should allow the specifcation of a method" do
|
29
|
+
@actor_class.thwart_access do
|
30
|
+
role_method :arbitrary_attribute
|
31
|
+
end
|
32
|
+
@actor_class.new.thwart_role.should == :arb_return
|
33
|
+
end
|
34
|
+
it "should allow the specification of a proc" do
|
35
|
+
@actor_class.thwart_access do
|
36
|
+
role_proc Proc.new { |a|
|
37
|
+
a.nil?.should == false
|
38
|
+
:proc_return
|
39
|
+
}
|
40
|
+
end
|
41
|
+
@actor_class.new.thwart_role.should == :proc_return
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "with some inheriting models set up" do
|
4
|
+
before do
|
5
|
+
@with_cans = generic_model do
|
6
|
+
include Thwart::Cans
|
7
|
+
end.new
|
8
|
+
@with_ables = generic_model do
|
9
|
+
include Thwart::Ables
|
10
|
+
end.new
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Thwart::Cans do
|
14
|
+
it "shouldn't find non existant methods" do
|
15
|
+
Thwart::Actions.should_receive(:find_can).with(:can_view?).at_least(1).and_return(false)
|
16
|
+
lambda {@with_cans.can_view?(@with_ables) }.should raise_error(NoMethodError)
|
17
|
+
end
|
18
|
+
it "should find a method" do
|
19
|
+
# rspec seems to call respond_to which calls this and it needs to work
|
20
|
+
# Thwart::Actions.should_receive(:find_can).with(:can_view?).at_least(1).and_return(:view)
|
21
|
+
Thwart::Actions.should_receive(:actions).any_number_of_times.and_return({:view => :viewable})
|
22
|
+
Thwart.should_receive(:query).with(@with_cans, @with_ables, :view)
|
23
|
+
@with_cans.can_view?(@with_ables)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe Thwart::Ables do
|
28
|
+
it "shouldn't find any methods" do
|
29
|
+
Thwart::Actions.should_receive(:find_able).with(:viewable_by?).at_least(1).and_return(false)
|
30
|
+
lambda {@with_ables.viewable_by?(@with_cans) }.should raise_error(NoMethodError)
|
31
|
+
end
|
32
|
+
it "should find a method" do
|
33
|
+
# rspec seems to call respond_to which calls this and it needs to work properly
|
34
|
+
# Thwart::Actions.should_receive(:find_able).with(:viewable_by?).at_least(1).and_return(:viewable)
|
35
|
+
Thwart::Actions.should_receive(:actions).any_number_of_times.and_return({:view => :viewable})
|
36
|
+
Thwart::Actions.should_receive(:can_from_able).with(:viewable).and_return(:view)
|
37
|
+
Thwart.should_receive(:query).with(@with_cans, @with_ables, :view)
|
38
|
+
@with_ables.viewable_by?(@with_cans)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/spec/dsl_spec.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::Dsl do
|
4
|
+
before do
|
5
|
+
@target = double("target")
|
6
|
+
end
|
7
|
+
describe "with no given map" do
|
8
|
+
before do
|
9
|
+
@dsl = Thwart::Dsl.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should pass methods on to the target" do
|
13
|
+
@target.should_receive(:foo)
|
14
|
+
@dsl.evaluate(@target) do
|
15
|
+
foo
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should pass writer methods on to the target" do
|
20
|
+
@target.should_receive(:foo=).with("bar")
|
21
|
+
@dsl.evaluate(@target) do
|
22
|
+
foo "bar"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "shouldn't find non existant methods" do
|
27
|
+
lambda { @dsl.evaluate(@target) do
|
28
|
+
foo "bar"
|
29
|
+
end }.should raise_error(NoMethodError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "with an extra map" do
|
34
|
+
before do
|
35
|
+
@dsl = Thwart::Dsl.new :imperative => :foo=
|
36
|
+
end
|
37
|
+
it "should map attributes on to the target" do
|
38
|
+
@target.should_receive(:foo=).with("bar")
|
39
|
+
@dsl.evaluate(@target) do
|
40
|
+
imperative "bar"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "with method_missing defined on the target" do
|
46
|
+
before do
|
47
|
+
target_class = Class.new do
|
48
|
+
def respond_to?(name)
|
49
|
+
return true if [:test1, :test2].include?(name)
|
50
|
+
super
|
51
|
+
end
|
52
|
+
def method_missing(name, *args)
|
53
|
+
return self.test_method_called(name, *args) if self.respond_to?(name)
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
@target = target_class.new
|
58
|
+
end
|
59
|
+
it "should have a proper target to test with" do
|
60
|
+
@target.should_receive(:test_method_called).with(:test1)
|
61
|
+
@target.should_receive(:test_method_called).with(:test2)
|
62
|
+
@target.should_receive(:test_method_called).with(:test1, :foo, :bar)
|
63
|
+
@target.respond_to?(:test1).should == true
|
64
|
+
@target.respond_to?(:test2).should == true
|
65
|
+
@target.test1
|
66
|
+
@target.test2
|
67
|
+
@target.test1 :foo, :bar
|
68
|
+
end
|
69
|
+
context "with a all = true DSL" do
|
70
|
+
before do
|
71
|
+
@dsl = Thwart::Dsl.new :test2 => :something_else
|
72
|
+
@dsl.all = true
|
73
|
+
end
|
74
|
+
it "should call method missing on the target if the DSL doesn't have the method defined" do
|
75
|
+
@target.should_receive(:test_method_called).with(:test1)
|
76
|
+
@dsl.evaluate @target do
|
77
|
+
test1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
it "should call the method in the method map if the DSL has the method in the map" do
|
81
|
+
@target.should_not_receive(:method_missing)
|
82
|
+
@target.should_receive(:something_else)
|
83
|
+
@dsl.evaluate @target do
|
84
|
+
test2
|
85
|
+
end
|
86
|
+
end
|
87
|
+
it "should properly pass arguments to the missing method" do
|
88
|
+
@target.should_receive(:test_method_called).with(:test1, :foo, :baz)
|
89
|
+
@dsl.evaluate @target do
|
90
|
+
test1 :foo, :baz
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
it "shouldn't call method missing on the target if the DSL doesn't have the method defined and all is false" do
|
95
|
+
@target.should_not_receive(:test_method_called).with(:test1)
|
96
|
+
@dsl = Thwart::Dsl.new
|
97
|
+
@dsl.all = false
|
98
|
+
lambda { @dsl.evaluate @target do
|
99
|
+
test1
|
100
|
+
end }.should raise_error(NameError)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe Thwart::Enforcer do
|
5
|
+
context "access enforcment" do
|
6
|
+
it "should need the current user method defined on the controller" do
|
7
|
+
lambda { instance_with_module(Thwart::Enforcer).thwart_access }.should raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
it "should need the params hash to have an action key" do
|
10
|
+
class_with_module(Thwart::Enforcer)
|
11
|
+
lambda { @controller.thwart_access }.should raise_error(ArgumentError)
|
12
|
+
end
|
13
|
+
it "should need the params[:actions] to be a recognized action"
|
14
|
+
it "should thwart access by raising an error if the user doesn't have permission"
|
15
|
+
it "should return true if the user does have permission"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Thwart::Resource do
|
4
|
+
describe "without a special name" do
|
5
|
+
before do
|
6
|
+
resource_class = generic_model("Generic Resource") do
|
7
|
+
include Thwart::Resource
|
8
|
+
thwart_access
|
9
|
+
end
|
10
|
+
@resource = resource_class.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have set its own name" do
|
14
|
+
@resource.class.thwart_name.should == "generic"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "with a special name" do
|
19
|
+
before do
|
20
|
+
resource_class = generic_model("Generic Resource") do
|
21
|
+
include Thwart::Resource
|
22
|
+
thwart_access do
|
23
|
+
name "special"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@resource = resource_class.new
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should have a different name" do
|
30
|
+
@resource.class.thwart_name.should == "special"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "without a name" do
|
35
|
+
it "should raise an error if no name could be found" do
|
36
|
+
lambda { @resource = Class.new do
|
37
|
+
include Thwart::Resource
|
38
|
+
thwart_access
|
39
|
+
end }.should raise_error(Thwart::MissingAttributeError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|