thwart 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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