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.
@@ -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