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.
Files changed (36) hide show
  1. data/CHANGELOG.rdoc +21 -0
  2. data/Gemfile +17 -0
  3. data/LICENSE +1 -1
  4. data/README.rdoc +16 -77
  5. data/Rakefile +8 -0
  6. data/lib/cancan.rb +8 -3
  7. data/lib/cancan/ability.rb +24 -26
  8. data/lib/cancan/controller_additions.rb +50 -0
  9. data/lib/cancan/controller_resource.rb +33 -15
  10. data/lib/cancan/exceptions.rb +3 -0
  11. data/lib/cancan/model_adapters/abstract_adapter.rb +40 -0
  12. data/lib/cancan/model_adapters/active_record_adapter.rb +119 -0
  13. data/lib/cancan/model_adapters/data_mapper_adapter.rb +33 -0
  14. data/lib/cancan/model_adapters/default_adapter.rb +7 -0
  15. data/lib/cancan/model_adapters/mongoid_adapter.rb +41 -0
  16. data/lib/cancan/{active_record_additions.rb → model_additions.rb} +5 -16
  17. data/lib/cancan/{can_definition.rb → rule.rb} +29 -25
  18. data/lib/generators/cancan/ability/USAGE +4 -0
  19. data/lib/generators/cancan/ability/ability_generator.rb +11 -0
  20. data/lib/generators/cancan/ability/templates/ability.rb +28 -0
  21. data/spec/README.rdoc +28 -0
  22. data/spec/cancan/ability_spec.rb +11 -3
  23. data/spec/cancan/controller_additions_spec.rb +30 -0
  24. data/spec/cancan/controller_resource_spec.rb +68 -2
  25. data/spec/cancan/inherited_resource_spec.rb +3 -1
  26. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +185 -0
  27. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +115 -0
  28. data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
  29. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +168 -0
  30. data/spec/cancan/rule_spec.rb +39 -0
  31. data/spec/spec_helper.rb +2 -24
  32. metadata +26 -17
  33. data/lib/cancan/query.rb +0 -97
  34. data/spec/cancan/active_record_additions_spec.rb +0 -75
  35. data/spec/cancan/can_definition_spec.rb +0 -57
  36. data/spec/cancan/query_spec.rb +0 -107
@@ -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 can definition, if block returns false or nil" do
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 can definition" do
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 can definition, if block returns false or nil" do
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
- @controller = Object.new # simple stub for now
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
- @controller = Object.new # simple stub for now
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