cancan 1.4.1 → 1.5.0.beta1
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/CHANGELOG.rdoc +21 -0
- data/Gemfile +17 -0
- data/LICENSE +1 -1
- data/README.rdoc +16 -77
- data/Rakefile +8 -0
- data/lib/cancan.rb +8 -3
- data/lib/cancan/ability.rb +24 -26
- data/lib/cancan/controller_additions.rb +50 -0
- data/lib/cancan/controller_resource.rb +33 -15
- data/lib/cancan/exceptions.rb +3 -0
- data/lib/cancan/model_adapters/abstract_adapter.rb +40 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +119 -0
- data/lib/cancan/model_adapters/data_mapper_adapter.rb +33 -0
- data/lib/cancan/model_adapters/default_adapter.rb +7 -0
- data/lib/cancan/model_adapters/mongoid_adapter.rb +41 -0
- data/lib/cancan/{active_record_additions.rb → model_additions.rb} +5 -16
- data/lib/cancan/{can_definition.rb → rule.rb} +29 -25
- data/lib/generators/cancan/ability/USAGE +4 -0
- data/lib/generators/cancan/ability/ability_generator.rb +11 -0
- data/lib/generators/cancan/ability/templates/ability.rb +28 -0
- data/spec/README.rdoc +28 -0
- data/spec/cancan/ability_spec.rb +11 -3
- data/spec/cancan/controller_additions_spec.rb +30 -0
- data/spec/cancan/controller_resource_spec.rb +68 -2
- data/spec/cancan/inherited_resource_spec.rb +3 -1
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +185 -0
- data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +115 -0
- data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +168 -0
- data/spec/cancan/rule_spec.rb +39 -0
- data/spec/spec_helper.rb +2 -24
- metadata +26 -17
- data/lib/cancan/query.rb +0 -97
- data/spec/cancan/active_record_additions_spec.rb +0 -75
- data/spec/cancan/can_definition_spec.rb +0 -57
- data/spec/cancan/query_spec.rb +0 -107
data/spec/cancan/ability_spec.rb
CHANGED
@@ -32,7 +32,7 @@ describe CanCan::Ability do
|
|
32
32
|
@ability.can?(:read, Symbol).should be_true
|
33
33
|
end
|
34
34
|
|
35
|
-
it "should pass to previous
|
35
|
+
it "should pass to previous rule, if block returns false or nil" do
|
36
36
|
@ability.can :read, Symbol
|
37
37
|
@ability.can :read, Integer do |i|
|
38
38
|
i < 5
|
@@ -144,7 +144,7 @@ describe CanCan::Ability do
|
|
144
144
|
@ability.can?(:update, 123).should be_false
|
145
145
|
end
|
146
146
|
|
147
|
-
it "should support custom objects in the
|
147
|
+
it "should support custom objects in the rule" do
|
148
148
|
@ability.can :read, :stats
|
149
149
|
@ability.can?(:read, :stats).should be_true
|
150
150
|
@ability.can?(:update, :stats).should be_false
|
@@ -165,7 +165,7 @@ describe CanCan::Ability do
|
|
165
165
|
@ability.can?(:read, 123).should be_false
|
166
166
|
end
|
167
167
|
|
168
|
-
it "should pass to previous
|
168
|
+
it "should pass to previous rule, if block returns false or nil" do
|
169
169
|
@ability.can :read, :all
|
170
170
|
@ability.cannot :read, Integer do |int|
|
171
171
|
int > 10 ? nil : ( int > 5 )
|
@@ -343,6 +343,14 @@ describe CanCan::Ability do
|
|
343
343
|
end
|
344
344
|
end
|
345
345
|
|
346
|
+
it "should determine model adapter class by asking AbstractAdapter" do
|
347
|
+
model_class = Object.new
|
348
|
+
adapter_class = Object.new
|
349
|
+
stub(CanCan::ModelAdapters::AbstractAdapter).adapter_class(model_class) { adapter_class }
|
350
|
+
stub(adapter_class).new(model_class, []) { :adapter_instance }
|
351
|
+
@ability.model_adapter(model_class, :read).should == :adapter_instance
|
352
|
+
end
|
353
|
+
|
346
354
|
describe "unauthorized message" do
|
347
355
|
after(:each) do
|
348
356
|
I18n.backend = nil
|
@@ -83,4 +83,34 @@ describe CanCan::ControllerAdditions do
|
|
83
83
|
stub(@controller.class).ancestors { ["InheritedResources::Actions"] }
|
84
84
|
@controller.class.cancan_resource_class.should == CanCan::InheritedResource
|
85
85
|
end
|
86
|
+
|
87
|
+
it "cancan_skipper should be an empty hash with :authorize and :load options and remember changes" do
|
88
|
+
@controller_class.cancan_skipper.should == {:authorize => {}, :load => {}}
|
89
|
+
@controller_class.cancan_skipper[:load] = true
|
90
|
+
@controller_class.cancan_skipper[:load].should == true
|
91
|
+
end
|
92
|
+
|
93
|
+
it "skip_authorize_resource should add itself to the cancan skipper with given model name and options" do
|
94
|
+
@controller_class.skip_authorize_resource(:project, :only => [:index, :show])
|
95
|
+
@controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]}
|
96
|
+
@controller_class.skip_authorize_resource(:only => [:index, :show])
|
97
|
+
@controller_class.cancan_skipper[:authorize][nil].should == {:only => [:index, :show]}
|
98
|
+
@controller_class.skip_authorize_resource(:article)
|
99
|
+
@controller_class.cancan_skipper[:authorize][:article].should == {}
|
100
|
+
end
|
101
|
+
|
102
|
+
it "skip_load_resource should add itself to the cancan skipper with given model name and options" do
|
103
|
+
@controller_class.skip_load_resource(:project, :only => [:index, :show])
|
104
|
+
@controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]}
|
105
|
+
@controller_class.skip_load_resource(:only => [:index, :show])
|
106
|
+
@controller_class.cancan_skipper[:load][nil].should == {:only => [:index, :show]}
|
107
|
+
@controller_class.skip_load_resource(:article)
|
108
|
+
@controller_class.cancan_skipper[:load][:article].should == {}
|
109
|
+
end
|
110
|
+
|
111
|
+
it "skip_load_and_authore_resource should add itself to the cancan skipper with given model name and options" do
|
112
|
+
@controller_class.skip_load_and_authorize_resource(:project, :only => [:index, :show])
|
113
|
+
@controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]}
|
114
|
+
@controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]}
|
115
|
+
end
|
86
116
|
end
|
@@ -3,10 +3,12 @@ require "spec_helper"
|
|
3
3
|
describe CanCan::ControllerResource do
|
4
4
|
before(:each) do
|
5
5
|
@params = HashWithIndifferentAccess.new(:controller => "projects")
|
6
|
-
@
|
6
|
+
@controller_class = Class.new
|
7
|
+
@controller = @controller_class.new
|
7
8
|
@ability = Ability.new(nil)
|
8
9
|
stub(@controller).params { @params }
|
9
10
|
stub(@controller).current_ability { @ability }
|
11
|
+
stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} }
|
10
12
|
end
|
11
13
|
|
12
14
|
it "should load the resource into an instance variable if params[:id] is specified" do
|
@@ -91,6 +93,22 @@ describe CanCan::ControllerResource do
|
|
91
93
|
@controller.instance_variable_defined?(:@projects).should be_false
|
92
94
|
end
|
93
95
|
|
96
|
+
it "should not authorize single resource in collection action" do
|
97
|
+
@params[:action] = "index"
|
98
|
+
@controller.instance_variable_set(:@project, :some_project)
|
99
|
+
stub(@controller).authorize!(:index, Project) { raise CanCan::AccessDenied }
|
100
|
+
resource = CanCan::ControllerResource.new(@controller)
|
101
|
+
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should authorize parent resource in collection action" do
|
105
|
+
@params[:action] = "index"
|
106
|
+
@controller.instance_variable_set(:@category, :some_category)
|
107
|
+
stub(@controller).authorize!(:read, :some_category) { raise CanCan::AccessDenied }
|
108
|
+
resource = CanCan::ControllerResource.new(@controller, :category, :parent => true)
|
109
|
+
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
110
|
+
end
|
111
|
+
|
94
112
|
it "should perform authorization using controller action and loaded model" do
|
95
113
|
@params[:action] = "show"
|
96
114
|
@controller.instance_variable_set(:@project, :some_project)
|
@@ -245,7 +263,7 @@ describe CanCan::ControllerResource do
|
|
245
263
|
@params.merge!(:action => "create", :project => {:name => "foobar"})
|
246
264
|
category = Object.new
|
247
265
|
@controller.instance_variable_set(:@category, category)
|
248
|
-
stub(category).build_project { Project.new }
|
266
|
+
stub(category).build_project { |attributes| Project.new(attributes) }
|
249
267
|
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
|
250
268
|
resource.load_resource
|
251
269
|
@controller.instance_variable_get(:@project).name.should == "foobar"
|
@@ -323,4 +341,52 @@ describe CanCan::ControllerResource do
|
|
323
341
|
CanCan::ControllerResource.new(@controller, :nested => :project)
|
324
342
|
}.should raise_error(CanCan::ImplementationRemoved)
|
325
343
|
end
|
344
|
+
|
345
|
+
it "should skip resource behavior for :only actions in array" do
|
346
|
+
stub(@controller_class).cancan_skipper { {:load => {nil => {:only => [:index, :show]}}} }
|
347
|
+
@params.merge!(:action => "index")
|
348
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
349
|
+
CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
|
350
|
+
@params.merge!(:action => "show")
|
351
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
352
|
+
@params.merge!(:action => "other_action")
|
353
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
354
|
+
end
|
355
|
+
|
356
|
+
it "should skip resource behavior for :only one action on resource" do
|
357
|
+
stub(@controller_class).cancan_skipper { {:authorize => {:project => {:only => :index}}} }
|
358
|
+
@params.merge!(:action => "index")
|
359
|
+
CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
|
360
|
+
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
|
361
|
+
@params.merge!(:action => "other_action")
|
362
|
+
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
|
363
|
+
end
|
364
|
+
|
365
|
+
it "should skip resource behavior :except actions in array" do
|
366
|
+
stub(@controller_class).cancan_skipper { {:load => {nil => {:except => [:index, :show]}}} }
|
367
|
+
@params.merge!(:action => "index")
|
368
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
369
|
+
@params.merge!(:action => "show")
|
370
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_false
|
371
|
+
@params.merge!(:action => "other_action")
|
372
|
+
CanCan::ControllerResource.new(@controller).skip?(:load).should be_true
|
373
|
+
CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false
|
374
|
+
end
|
375
|
+
|
376
|
+
it "should skip resource behavior :except one action on resource" do
|
377
|
+
stub(@controller_class).cancan_skipper { {:authorize => {:project => {:except => :index}}} }
|
378
|
+
@params.merge!(:action => "index")
|
379
|
+
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false
|
380
|
+
@params.merge!(:action => "other_action")
|
381
|
+
CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false
|
382
|
+
CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true
|
383
|
+
end
|
384
|
+
|
385
|
+
it "should skip loading and authorization" do
|
386
|
+
stub(@controller_class).cancan_skipper { {:authorize => {nil => {}}, :load => {nil => {}}} }
|
387
|
+
@params.merge!(:action => "new")
|
388
|
+
resource = CanCan::ControllerResource.new(@controller)
|
389
|
+
lambda { resource.load_and_authorize_resource }.should_not raise_error
|
390
|
+
@controller.instance_variable_get(:@project).should be_nil
|
391
|
+
end
|
326
392
|
end
|
@@ -3,10 +3,12 @@ require "spec_helper"
|
|
3
3
|
describe CanCan::InheritedResource do
|
4
4
|
before(:each) do
|
5
5
|
@params = HashWithIndifferentAccess.new(:controller => "projects")
|
6
|
-
@
|
6
|
+
@controller_class = Class.new
|
7
|
+
@controller = @controller_class.new
|
7
8
|
@ability = Ability.new(nil)
|
8
9
|
stub(@controller).params { @params }
|
9
10
|
stub(@controller).current_ability { @ability }
|
11
|
+
stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} }
|
10
12
|
end
|
11
13
|
|
12
14
|
it "show should load resource through @controller.resource" do
|
@@ -0,0 +1,185 @@
|
|
1
|
+
if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
config.extend WithModel
|
6
|
+
end
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
9
|
+
|
10
|
+
describe CanCan::ModelAdapters::ActiveRecordAdapter do
|
11
|
+
with_model :article do
|
12
|
+
table do |t|
|
13
|
+
t.boolean "published"
|
14
|
+
t.boolean "secret"
|
15
|
+
end
|
16
|
+
model do
|
17
|
+
has_many :comments
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
with_model :comment do
|
22
|
+
table do |t|
|
23
|
+
t.boolean "spam"
|
24
|
+
t.integer "article_id"
|
25
|
+
end
|
26
|
+
model do
|
27
|
+
belongs_to :article
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
before(:each) do
|
32
|
+
Article.delete_all
|
33
|
+
Comment.delete_all
|
34
|
+
@ability = Object.new
|
35
|
+
@ability.extend(CanCan::Ability)
|
36
|
+
@article_table = Article.table_name
|
37
|
+
@comment_table = Comment.table_name
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be for only active record classes" do
|
41
|
+
CanCan::ModelAdapters::ActiveRecordAdapter.should_not be_for_class(Object)
|
42
|
+
CanCan::ModelAdapters::ActiveRecordAdapter.should be_for_class(Article)
|
43
|
+
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::ActiveRecordAdapter
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should not fetch any records when no abilities are defined" do
|
47
|
+
Article.create!
|
48
|
+
Article.accessible_by(@ability).should be_empty
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should fetch all articles when one can read all" do
|
52
|
+
@ability.can :read, Article
|
53
|
+
article = Article.create!
|
54
|
+
Article.accessible_by(@ability).should == [article]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should fetch only the articles that are published" do
|
58
|
+
@ability.can :read, Article, :published => true
|
59
|
+
article1 = Article.create!(:published => true)
|
60
|
+
article2 = Article.create!(:published => false)
|
61
|
+
Article.accessible_by(@ability).should == [article1]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should fetch any articles which are published or secret" do
|
65
|
+
@ability.can :read, Article, :published => true
|
66
|
+
@ability.can :read, Article, :secret => true
|
67
|
+
article1 = Article.create!(:published => true, :secret => false)
|
68
|
+
article2 = Article.create!(:published => true, :secret => true)
|
69
|
+
article3 = Article.create!(:published => false, :secret => true)
|
70
|
+
article4 = Article.create!(:published => false, :secret => false)
|
71
|
+
Article.accessible_by(@ability).should == [article1, article2, article3]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should fetch only the articles that are published and not secret" do
|
75
|
+
@ability.can :read, Article, :published => true
|
76
|
+
@ability.cannot :read, Article, :secret => true
|
77
|
+
article1 = Article.create!(:published => true, :secret => false)
|
78
|
+
article2 = Article.create!(:published => true, :secret => true)
|
79
|
+
article3 = Article.create!(:published => false, :secret => true)
|
80
|
+
article4 = Article.create!(:published => false, :secret => false)
|
81
|
+
Article.accessible_by(@ability).should == [article1]
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should only read comments for articles which are published" do
|
85
|
+
@ability.can :read, Comment, :article => { :published => true }
|
86
|
+
comment1 = Comment.create!(:article => Article.create!(:published => true))
|
87
|
+
comment2 = Comment.create!(:article => Article.create!(:published => false))
|
88
|
+
Comment.accessible_by(@ability).should == [comment1]
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should allow conditions in SQL and merge with hash conditions" do
|
92
|
+
@ability.can :read, Article, :published => true
|
93
|
+
@ability.can :read, Article, ["secret=?", true]
|
94
|
+
article1 = Article.create!(:published => true, :secret => false)
|
95
|
+
article4 = Article.create!(:published => false, :secret => false)
|
96
|
+
Article.accessible_by(@ability).should == [article1]
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should not allow to fetch records when ability with just block present" do
|
100
|
+
@ability.can :read, Article do
|
101
|
+
false
|
102
|
+
end
|
103
|
+
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should not allow to check ability on object against SQL conditions without block" do
|
107
|
+
@ability.can :read, Article, ["secret=?", true]
|
108
|
+
lambda { @ability.can? :read, Article.new }.should raise_error(CanCan::Error)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should have false conditions if no abilities match" do
|
112
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should return false conditions for cannot clause" do
|
116
|
+
@ability.cannot :read, Article
|
117
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should return SQL for single `can` definition in front of default `cannot` condition" do
|
121
|
+
@ability.cannot :read, Article
|
122
|
+
@ability.can :read, Article, :published => false, :secret => true
|
123
|
+
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'])
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should return true condition for single `can` definition in front of default `can` condition" do
|
127
|
+
@ability.can :read, Article
|
128
|
+
@ability.can :read, Article, :published => false, :secret => true
|
129
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should return `false condition` for single `cannot` definition in front of default `cannot` condition" do
|
133
|
+
@ability.cannot :read, Article
|
134
|
+
@ability.cannot :read, Article, :published => false, :secret => true
|
135
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should return `not (sql)` for single `cannot` definition in front of default `can` condition" do
|
139
|
+
@ability.can :read, Article
|
140
|
+
@ability.cannot :read, Article, :published => false, :secret => true
|
141
|
+
@ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should return appropriate sql conditions in complex case" do
|
145
|
+
@ability.can :read, Article
|
146
|
+
@ability.can :manage, Article, :id => 1
|
147
|
+
@ability.can :update, Article, :published => true
|
148
|
+
@ability.cannot :update, Article, :secret => true
|
149
|
+
@ability.model_adapter(Article, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))]
|
150
|
+
@ability.model_adapter(Article, :manage).conditions.should == {:id => 1}
|
151
|
+
@ability.model_adapter(Article, :read).conditions.should == "'t'='t'"
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should not forget conditions when calling with SQL string" do
|
155
|
+
@ability.can :read, Article, :published => true
|
156
|
+
@ability.can :read, Article, ['secret=?', false]
|
157
|
+
adapter = @ability.model_adapter(Article, :read)
|
158
|
+
2.times do
|
159
|
+
adapter.conditions.should == %Q[(secret='f') OR ("#{@article_table}"."published" = 't')]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should have nil joins if no rules" do
|
164
|
+
@ability.model_adapter(Article, :read).joins.should be_nil
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should have nil joins if no nested hashes specified in conditions" do
|
168
|
+
@ability.can :read, Article, :published => false
|
169
|
+
@ability.can :read, Article, :secret => true
|
170
|
+
@ability.model_adapter(Article, :read).joins.should be_nil
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should merge separate joins into a single array" do
|
174
|
+
@ability.can :read, Article, :project => { :blocked => false }
|
175
|
+
@ability.can :read, Article, :company => { :admin => true }
|
176
|
+
@ability.model_adapter(Article, :read).joins.inspect.should orderlessly_match([:company, :project].inspect)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should merge same joins into a single array" do
|
180
|
+
@ability.can :read, Article, :project => { :blocked => false }
|
181
|
+
@ability.can :read, Article, :project => { :admin => true }
|
182
|
+
@ability.model_adapter(Article, :read).joins.should == [:project]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
if ENV["MODEL_ADAPTER"] == "data_mapper"
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
DataMapper.setup(:default, 'sqlite::memory:')
|
5
|
+
|
6
|
+
class Article
|
7
|
+
include DataMapper::Resource
|
8
|
+
property :id, Serial
|
9
|
+
property :published, Boolean, :default => false
|
10
|
+
property :secret, Boolean, :default => false
|
11
|
+
property :priority, Integer
|
12
|
+
has n, :comments
|
13
|
+
end
|
14
|
+
|
15
|
+
class Comment
|
16
|
+
include DataMapper::Resource
|
17
|
+
property :id, Serial
|
18
|
+
property :spam, Boolean, :default => false
|
19
|
+
belongs_to :article
|
20
|
+
end
|
21
|
+
|
22
|
+
DataMapper.finalize
|
23
|
+
DataMapper.auto_migrate!
|
24
|
+
|
25
|
+
describe CanCan::ModelAdapters::DataMapperAdapter do
|
26
|
+
before(:each) do
|
27
|
+
Article.destroy
|
28
|
+
Comment.destroy
|
29
|
+
@ability = Object.new
|
30
|
+
@ability.extend(CanCan::Ability)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should be for only data mapper classes" do
|
34
|
+
CanCan::ModelAdapters::DataMapperAdapter.should_not be_for_class(Object)
|
35
|
+
CanCan::ModelAdapters::DataMapperAdapter.should be_for_class(Article)
|
36
|
+
CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::DataMapperAdapter
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not fetch any records when no abilities are defined" do
|
40
|
+
Article.create
|
41
|
+
Article.accessible_by(@ability).should be_empty
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should fetch all articles when one can read all" do
|
45
|
+
@ability.can :read, Article
|
46
|
+
article = Article.create
|
47
|
+
Article.accessible_by(@ability).should == [article]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should fetch only the articles that are published" do
|
51
|
+
@ability.can :read, Article, :published => true
|
52
|
+
article1 = Article.create(:published => true)
|
53
|
+
article2 = Article.create(:published => false)
|
54
|
+
Article.accessible_by(@ability).should == [article1]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should fetch any articles which are published or secret" do
|
58
|
+
@ability.can :read, Article, :published => true
|
59
|
+
@ability.can :read, Article, :secret => true
|
60
|
+
article1 = Article.create(:published => true, :secret => false)
|
61
|
+
article2 = Article.create(:published => true, :secret => true)
|
62
|
+
article3 = Article.create(:published => false, :secret => true)
|
63
|
+
article4 = Article.create(:published => false, :secret => false)
|
64
|
+
Article.accessible_by(@ability).should == [article1, article2, article3]
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should fetch only the articles that are published and not secret" do
|
68
|
+
pending "the `cannot` may require some custom SQL, maybe abstract out from Active Record adapter"
|
69
|
+
@ability.can :read, Article, :published => true
|
70
|
+
@ability.cannot :read, Article, :secret => true
|
71
|
+
article1 = Article.create(:published => true, :secret => false)
|
72
|
+
article2 = Article.create(:published => true, :secret => true)
|
73
|
+
article3 = Article.create(:published => false, :secret => true)
|
74
|
+
article4 = Article.create(:published => false, :secret => false)
|
75
|
+
Article.accessible_by(@ability).should == [article1]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should only read comments for articles which are published" do
|
79
|
+
@ability.can :read, Comment, :article => { :published => true }
|
80
|
+
comment1 = Comment.create(:article => Article.create!(:published => true))
|
81
|
+
comment2 = Comment.create(:article => Article.create!(:published => false))
|
82
|
+
Comment.accessible_by(@ability).should == [comment1]
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should allow conditions in SQL and merge with hash conditions" do
|
86
|
+
@ability.can :read, Article, :published => true
|
87
|
+
@ability.can :read, Article, ["secret=?", true]
|
88
|
+
article1 = Article.create(:published => true, :secret => false)
|
89
|
+
article4 = Article.create(:published => false, :secret => false)
|
90
|
+
Article.accessible_by(@ability).should == [article1]
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should match gt comparison" do
|
94
|
+
@ability.can :read, Article, :priority.gt => 3
|
95
|
+
article1 = Article.create(:priority => 4)
|
96
|
+
article2 = Article.create(:priority => 3)
|
97
|
+
Article.accessible_by(@ability).should == [article1]
|
98
|
+
@ability.should be_able_to(:read, article1)
|
99
|
+
@ability.should_not be_able_to(:read, article2)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should match gte comparison" do
|
103
|
+
@ability.can :read, Article, :priority.gte => 3
|
104
|
+
article1 = Article.create(:priority => 4)
|
105
|
+
article2 = Article.create(:priority => 3)
|
106
|
+
article3 = Article.create(:priority => 2)
|
107
|
+
Article.accessible_by(@ability).should == [article1, article2]
|
108
|
+
@ability.should be_able_to(:read, article1)
|
109
|
+
@ability.should be_able_to(:read, article2)
|
110
|
+
@ability.should_not be_able_to(:read, article3)
|
111
|
+
end
|
112
|
+
|
113
|
+
# TODO: add more comparison specs
|
114
|
+
end
|
115
|
+
end
|