marnen-cancan 2.0.0.alpha.pre.f1cebde51a87be149b4970a3287826bb63c0ac0b

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 (41) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.rdoc +381 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +108 -0
  6. data/Rakefile +18 -0
  7. data/init.rb +1 -0
  8. data/lib/cancan.rb +13 -0
  9. data/lib/cancan/ability.rb +348 -0
  10. data/lib/cancan/controller_additions.rb +392 -0
  11. data/lib/cancan/controller_resource.rb +265 -0
  12. data/lib/cancan/exceptions.rb +53 -0
  13. data/lib/cancan/inherited_resource.rb +20 -0
  14. data/lib/cancan/matchers.rb +14 -0
  15. data/lib/cancan/model_adapters/abstract_adapter.rb +56 -0
  16. data/lib/cancan/model_adapters/active_record_adapter.rb +172 -0
  17. data/lib/cancan/model_adapters/data_mapper_adapter.rb +34 -0
  18. data/lib/cancan/model_adapters/default_adapter.rb +7 -0
  19. data/lib/cancan/model_adapters/mongoid_adapter.rb +54 -0
  20. data/lib/cancan/model_additions.rb +29 -0
  21. data/lib/cancan/rule.rb +178 -0
  22. data/lib/generators/cancan/ability/USAGE +5 -0
  23. data/lib/generators/cancan/ability/ability_generator.rb +16 -0
  24. data/lib/generators/cancan/ability/templates/ability.rb +24 -0
  25. data/lib/generators/cancan/ability/templates/ability_spec.rb +16 -0
  26. data/lib/generators/cancan/ability/templates/ability_test.rb +10 -0
  27. data/spec/README.rdoc +28 -0
  28. data/spec/cancan/ability_spec.rb +541 -0
  29. data/spec/cancan/controller_additions_spec.rb +118 -0
  30. data/spec/cancan/controller_resource_spec.rb +535 -0
  31. data/spec/cancan/exceptions_spec.rb +58 -0
  32. data/spec/cancan/inherited_resource_spec.rb +58 -0
  33. data/spec/cancan/matchers_spec.rb +33 -0
  34. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +278 -0
  35. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +120 -0
  36. data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
  37. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +227 -0
  38. data/spec/cancan/rule_spec.rb +55 -0
  39. data/spec/matchers.rb +13 -0
  40. data/spec/spec_helper.rb +49 -0
  41. metadata +197 -0
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+
3
+ describe CanCan::Unauthorized do
4
+ describe "with action and subject" do
5
+ before(:each) do
6
+ @exception = CanCan::Unauthorized.new(nil, :some_action, :some_subject)
7
+ end
8
+
9
+ it "has action and subject accessors" do
10
+ @exception.action.should == :some_action
11
+ @exception.subject.should == :some_subject
12
+ end
13
+
14
+ it "has a changable default message" do
15
+ @exception.message.should == "You are not authorized to access this page."
16
+ @exception.default_message = "Unauthorized!"
17
+ @exception.message.should == "Unauthorized!"
18
+ end
19
+ end
20
+
21
+ describe "with only a message" do
22
+ before(:each) do
23
+ @exception = CanCan::Unauthorized.new("Access denied!")
24
+ end
25
+
26
+ it "has nil action and subject" do
27
+ @exception.action.should be_nil
28
+ @exception.subject.should be_nil
29
+ end
30
+
31
+ it "has passed message" do
32
+ @exception.message.should == "Access denied!"
33
+ end
34
+ end
35
+
36
+ describe "i18n in the default message" do
37
+ after(:each) do
38
+ I18n.backend = nil
39
+ end
40
+
41
+ it "uses i18n for the default message" do
42
+ I18n.backend.store_translations :en, :unauthorized => {:default => "This is a different message"}
43
+ @exception = CanCan::Unauthorized.new
44
+ @exception.message.should == "This is a different message"
45
+ end
46
+
47
+ it "defaults to a nice message" do
48
+ @exception = CanCan::Unauthorized.new
49
+ @exception.message.should == "You are not authorized to access this page."
50
+ end
51
+
52
+ it "does not use translation if a message is given" do
53
+ @exception = CanCan::Unauthorized.new("Hey! You're not welcome here")
54
+ @exception.message.should == "Hey! You're not welcome here"
55
+ @exception.message.should_not == "You are not authorized to access this page."
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+
3
+ describe CanCan::InheritedResource do
4
+ before(:each) do
5
+ @params = HashWithIndifferentAccess.new(:controller => "projects")
6
+ @controller_class = Class.new
7
+ @controller = @controller_class.new
8
+ @ability = Ability.new(nil)
9
+ @controller.stub(:params) { @params }
10
+ @controller.stub(:current_ability) { @ability }
11
+ # @controller_class.stub(:cancan_skipper) { {:authorize => {}, :load => {}} }
12
+ end
13
+
14
+ it "show should load resource through @controller.resource" do
15
+ @params.merge!(:action => "show", :id => 123)
16
+ @controller.stub(:resource) { :project_resource }
17
+ CanCan::InheritedResource.new(@controller, :load => true).process
18
+ @controller.instance_variable_get(:@project).should == :project_resource
19
+ end
20
+
21
+ it "new should load through @controller.build_resource" do
22
+ @params[:action] = "new"
23
+ @controller.stub(:build_resource) { :project_resource }
24
+ CanCan::InheritedResource.new(@controller, :load => true).process
25
+ @controller.instance_variable_get(:@project).should == :project_resource
26
+ end
27
+
28
+ it "index should load through @controller.association_chain when parent" do
29
+ @params[:action] = "index"
30
+ @controller.stub(:association_chain) { @controller.instance_variable_set(:@project, :project_resource) }
31
+ CanCan::InheritedResource.new(@controller, :load => true, :parent => true).process
32
+ @controller.instance_variable_get(:@project).should == :project_resource
33
+ end
34
+
35
+ it "index should load through @controller.end_of_association_chain" do
36
+ @params[:action] = "index"
37
+ Project.stub(:accessible_by).with(@ability, :index) { :projects }
38
+ @controller.stub(:end_of_association_chain) { Project }
39
+ CanCan::InheritedResource.new(@controller, :load => true).process
40
+ @controller.instance_variable_get(:@projects).should == :projects
41
+ end
42
+
43
+ it "should build a new resource with attributes from current ability" do
44
+ @params[:action] = "new"
45
+ @ability.can(:create, :projects, :name => "from conditions")
46
+ @controller.stub(:build_resource) { Struct.new(:name).new }
47
+ CanCan::InheritedResource.new(@controller, :load => true).process
48
+ @controller.instance_variable_get(:@project).name.should == "from conditions"
49
+ end
50
+
51
+ it "should override initial attributes with params" do
52
+ @params.merge!(:action => "new", :project => {:name => "from params"})
53
+ @ability.can(:create, :projects, :name => "from conditions")
54
+ @controller.stub(:build_resource) { Struct.new(:name).new }
55
+ CanCan::ControllerResource.new(@controller, :load => true).process
56
+ @controller.instance_variable_get(:@project).name.should == "from params"
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe "be_able_to" do
4
+ it "delegates to can?" do
5
+ object = Object.new
6
+ object.should_receive(:can?).with(:read, 123) { true }
7
+ object.should be_able_to(:read, 123)
8
+ end
9
+
10
+ it "reports a nice failure message for should" do
11
+ object = Object.new
12
+ object.should_receive(:can?).with(:read, 123) { false }
13
+ expect do
14
+ object.should be_able_to(:read, 123)
15
+ end.should raise_error('expected to be able to :read 123')
16
+ end
17
+
18
+ it "reports a nice failure message for should not" do
19
+ object = Object.new
20
+ object.should_receive(:can?).with(:read, 123) { true }
21
+ expect do
22
+ object.should_not be_able_to(:read, 123)
23
+ end.should raise_error('expected not to be able to :read 123')
24
+ end
25
+
26
+ it "delegates additional arguments to can? and reports in failure message" do
27
+ object = Object.new
28
+ object.should_receive(:can?).with(:read, 123, 456) { false }
29
+ expect do
30
+ object.should be_able_to(:read, 123, 456)
31
+ end.should raise_error('expected to be able to :read 123 456')
32
+ end
33
+ end
@@ -0,0 +1,278 @@
1
+ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
2
+ require "spec_helper"
3
+
4
+ class Category
5
+ has_many :articles
6
+ end
7
+
8
+ class Article < ActiveRecord::Base
9
+ connection.create_table(table_name) do |t|
10
+ t.integer :category_id
11
+ t.string :name
12
+ t.boolean :published
13
+ t.boolean :secret
14
+ t.integer :priority
15
+ end
16
+ belongs_to :category
17
+ has_many :comments
18
+ end
19
+
20
+ class Comment < ActiveRecord::Base
21
+ connection.create_table(table_name) do |t|
22
+ t.integer :article_id
23
+ t.boolean :spam
24
+ end
25
+ belongs_to :article
26
+ end
27
+
28
+ describe CanCan::ModelAdapters::ActiveRecordAdapter do
29
+ before(:each) do
30
+ Article.delete_all
31
+ Comment.delete_all
32
+ @ability = Object.new
33
+ @ability.extend(CanCan::Ability)
34
+ @article_table = Article.table_name
35
+ @comment_table = Comment.table_name
36
+ end
37
+
38
+ it "is for only active record classes" do
39
+ CanCan::ModelAdapters::ActiveRecordAdapter.should_not be_for_class(Object)
40
+ CanCan::ModelAdapters::ActiveRecordAdapter.should be_for_class(Article)
41
+ CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article).should == CanCan::ModelAdapters::ActiveRecordAdapter
42
+ end
43
+
44
+ it "finds record" do
45
+ article = Article.create!
46
+ CanCan::ModelAdapters::ActiveRecordAdapter.find(Article, article.id).should == article
47
+ end
48
+
49
+ it "does not fetch any records when no abilities are defined" do
50
+ Article.create!
51
+ Article.accessible_by(@ability).should be_empty
52
+ end
53
+
54
+ it "fetches all articles when one can read all" do
55
+ @ability.can :read, :articles
56
+ article = Article.create!
57
+ Article.accessible_by(@ability).should == [article]
58
+ end
59
+
60
+ it "fetches only the articles that are published" do
61
+ @ability.can :read, :articles, :published => true
62
+ article1 = Article.create!(:published => true)
63
+ article2 = Article.create!(:published => false)
64
+ Article.accessible_by(@ability).should == [article1]
65
+ end
66
+
67
+ it "fetches any articles which are published or secret" do
68
+ @ability.can :read, :articles, :published => true
69
+ @ability.can :read, :articles, :secret => true
70
+ article1 = Article.create!(:published => true, :secret => false)
71
+ article2 = Article.create!(:published => true, :secret => true)
72
+ article3 = Article.create!(:published => false, :secret => true)
73
+ article4 = Article.create!(:published => false, :secret => false)
74
+ Article.accessible_by(@ability).should == [article1, article2, article3]
75
+ end
76
+
77
+ it "fetches only the articles that are published and not secret" do
78
+ @ability.can :read, :articles, :published => true
79
+ @ability.cannot :read, :articles, :secret => true
80
+ article1 = Article.create!(:published => true, :secret => false)
81
+ article2 = Article.create!(:published => true, :secret => true)
82
+ article3 = Article.create!(:published => false, :secret => true)
83
+ article4 = Article.create!(:published => false, :secret => false)
84
+ Article.accessible_by(@ability).should == [article1]
85
+ end
86
+
87
+ it "only reads comments for articles which are published" do
88
+ @ability.can :read, :comments, :article => { :published => true }
89
+ comment1 = Comment.create!(:article => Article.create!(:published => true))
90
+ comment2 = Comment.create!(:article => Article.create!(:published => false))
91
+ Comment.accessible_by(@ability).should == [comment1]
92
+ end
93
+
94
+ it "only reads comments for visible categories through articles" do
95
+ pending "does ActiveRecord no longer support a deep nested hash of conditions?"
96
+ @ability.can :read, :comments, :article => { :category => { :visible => true } }
97
+ comment1 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => true)))
98
+ comment2 = Comment.create!(:article => Article.create!(:category => Category.create!(:visible => false)))
99
+ Comment.accessible_by(@ability).should == [comment1]
100
+ end
101
+
102
+ it "allows conditions in SQL and merge with hash conditions" do
103
+ @ability.can :read, :articles, :published => true
104
+ @ability.can :read, :articles, ["secret=?", true]
105
+ article1 = Article.create!(:published => true, :secret => false)
106
+ article2 = Article.create!(:published => true, :secret => true)
107
+ article3 = Article.create!(:published => false, :secret => true)
108
+ article4 = Article.create!(:published => false, :secret => false)
109
+ Article.accessible_by(@ability).should == [article1, article2, article3]
110
+ end
111
+
112
+ it "allows a scope for conditions" do
113
+ @ability.can :read, :articles, Article.where(:secret => true)
114
+ article1 = Article.create!(:secret => true)
115
+ article2 = Article.create!(:secret => false)
116
+ Article.accessible_by(@ability).should == [article1]
117
+ end
118
+
119
+ it "fetches only associated records when using with a scope for conditions" do
120
+ @ability.can :read, :articles, Article.where(:secret => true)
121
+ category1 = Category.create!(:visible => false)
122
+ category2 = Category.create!(:visible => true)
123
+ article1 = Article.create!(:secret => true, :category => category1)
124
+ article2 = Article.create!(:secret => true, :category => category2)
125
+ # for some reason the objects aren't comparing equally here so it's necessary to compare by id
126
+ category1.articles.accessible_by(@ability).map(&:id).should == [article1.id]
127
+ end
128
+
129
+ it "raises an exception when trying to merge scope with other conditions" do
130
+ @ability.can :read, :articles, :published => true
131
+ @ability.can :read, :articles, Article.where(:secret => true)
132
+ lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for read articles ability.")
133
+ end
134
+
135
+ it "does not allow to fetch records when ability with just block present" do
136
+ @ability.can :read, :articles do
137
+ false
138
+ end
139
+ lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error)
140
+ end
141
+
142
+ it "does not allow to check ability on object against SQL conditions without block" do
143
+ @ability.can :read, :articles, ["secret=?", true]
144
+ lambda { @ability.can? :read, Article.new }.should raise_error(CanCan::Error)
145
+ end
146
+
147
+ it "has false conditions if no abilities match" do
148
+ @ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
149
+ end
150
+
151
+ it "returns false conditions for cannot clause" do
152
+ @ability.cannot :read, :articles
153
+ @ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
154
+ end
155
+
156
+ it "returns SQL for single `can` definition in front of default `cannot` condition" do
157
+ @ability.cannot :read, :articles
158
+ @ability.can :read, :articles, :published => false, :secret => true
159
+ @ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'])
160
+ end
161
+
162
+ it "returns true condition for single `can` definition in front of default `can` condition" do
163
+ @ability.can :read, :articles
164
+ @ability.can :read, :articles, :published => false, :secret => true
165
+ @ability.model_adapter(Article, :read).conditions.should eq(:secret => true, :published => false)
166
+ end
167
+
168
+ it "returns `false condition` for single `cannot` definition in front of default `cannot` condition" do
169
+ @ability.cannot :read, :articles
170
+ @ability.cannot :read, :articles, :published => false, :secret => true
171
+ @ability.model_adapter(Article, :read).conditions.should == "'t'='f'"
172
+ end
173
+
174
+ it "returns `not (sql)` for single `cannot` definition in front of default `can` condition" do
175
+ @ability.can :read, :articles
176
+ @ability.cannot :read, :articles, :published => false, :secret => true
177
+ @ability.model_adapter(Article, :read).conditions.should orderlessly_match(%Q["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
178
+ end
179
+
180
+ it "returns appropriate sql conditions in complex case" do
181
+ @ability.can :read, :articles
182
+ @ability.can :access, :articles, :id => 1
183
+ @ability.can :update, :articles, :published => true
184
+ @ability.cannot :update, :articles, :secret => true
185
+ @ability.model_adapter(Article, :update).conditions.should == %Q[not ("#{@article_table}"."secret" = 't') AND (("#{@article_table}"."published" = 't') OR ("#{@article_table}"."id" = 1))]
186
+ @ability.model_adapter(Article, :access).conditions.should == {:id => 1}
187
+ @ability.model_adapter(Article, :read).conditions.should == {:id => 1} # used to be "t=t" but changed with new specificity rule (issue #321)
188
+ end
189
+
190
+ it "does not forget conditions when calling with SQL string" do
191
+ @ability.can :read, :articles, :published => true
192
+ @ability.can :read, :articles, ['secret=?', false]
193
+ adapter = @ability.model_adapter(Article, :read)
194
+ 2.times do
195
+ adapter.conditions.should == %Q[(secret='f') OR ("#{@article_table}"."published" = 't')]
196
+ end
197
+ end
198
+
199
+ it "has nil joins if no rules" do
200
+ @ability.model_adapter(Article, :read).joins.should be_nil
201
+ end
202
+
203
+ it "has nil joins if no nested hashes specified in conditions" do
204
+ @ability.can :read, :articles, :published => false
205
+ @ability.can :read, :articles, :secret => true
206
+ @ability.model_adapter(Article, :read).joins.should be_nil
207
+ end
208
+
209
+ it "merges separate joins into a single array" do
210
+ @ability.can :read, :articles, :project => { :blocked => false }
211
+ @ability.can :read, :articles, :company => { :admin => true }
212
+ @ability.model_adapter(Article, :read).joins.inspect.should orderlessly_match([:company, :project].inspect)
213
+ end
214
+
215
+ it "merges same joins into a single array" do
216
+ @ability.can :read, :articles, :project => { :blocked => false }
217
+ @ability.can :read, :articles, :project => { :admin => true }
218
+ @ability.model_adapter(Article, :read).joins.should == [:project]
219
+ end
220
+
221
+ it "restricts articles given a MetaWhere condition" do
222
+ pending
223
+ @ability.can :read, :articles, :priority.lt => 2
224
+ article1 = Article.create!(:priority => 1)
225
+ article2 = Article.create!(:priority => 3)
226
+ Article.accessible_by(@ability).should == [article1]
227
+ @ability.should be_able_to(:read, article1)
228
+ @ability.should_not be_able_to(:read, article2)
229
+ end
230
+
231
+ it "should merge MetaWhere and non-MetaWhere conditions" do
232
+ pending
233
+ @ability.can :read, Article, :priority.lt => 2
234
+ @ability.can :read, Article, :priority => 1
235
+ article1 = Article.create!(:priority => 1)
236
+ article2 = Article.create!(:priority => 3)
237
+ Article.accessible_by(@ability).should == [article1]
238
+ @ability.should be_able_to(:read, article1)
239
+ @ability.should_not be_able_to(:read, article2)
240
+ end
241
+
242
+ it "matches any MetaWhere condition" do
243
+ pending
244
+ adapter = CanCan::ModelAdapters::ActiveRecordAdapter
245
+ article1 = Article.new(:priority => 1, :name => "Hello World")
246
+ adapter.matches_condition?(article1, :priority.eq, 1).should be_true
247
+ adapter.matches_condition?(article1, :priority.eq, 2).should be_false
248
+ adapter.matches_condition?(article1, :priority.eq_any, [1, 2]).should be_true
249
+ adapter.matches_condition?(article1, :priority.eq_any, [2, 3]).should be_false
250
+ adapter.matches_condition?(article1, :priority.eq_all, [1, 1]).should be_true
251
+ adapter.matches_condition?(article1, :priority.eq_all, [1, 2]).should be_false
252
+ adapter.matches_condition?(article1, :priority.ne, 2).should be_true
253
+ adapter.matches_condition?(article1, :priority.ne, 1).should be_false
254
+ adapter.matches_condition?(article1, :priority.in, [1, 2]).should be_true
255
+ adapter.matches_condition?(article1, :priority.in, [2, 3]).should be_false
256
+ adapter.matches_condition?(article1, :priority.nin, [2, 3]).should be_true
257
+ adapter.matches_condition?(article1, :priority.nin, [1, 2]).should be_false
258
+ adapter.matches_condition?(article1, :priority.lt, 2).should be_true
259
+ adapter.matches_condition?(article1, :priority.lt, 1).should be_false
260
+ adapter.matches_condition?(article1, :priority.lteq, 1).should be_true
261
+ adapter.matches_condition?(article1, :priority.lteq, 0).should be_false
262
+ adapter.matches_condition?(article1, :priority.gt, 0).should be_true
263
+ adapter.matches_condition?(article1, :priority.gt, 1).should be_false
264
+ adapter.matches_condition?(article1, :priority.gteq, 1).should be_true
265
+ adapter.matches_condition?(article1, :priority.gteq, 2).should be_false
266
+ adapter.matches_condition?(article1, :name.like, "%ello worl%").should be_true
267
+ adapter.matches_condition?(article1, :name.like, "hello world").should be_true
268
+ adapter.matches_condition?(article1, :name.like, "hello%").should be_true
269
+ adapter.matches_condition?(article1, :name.like, "h%d").should be_true
270
+ adapter.matches_condition?(article1, :name.like, "%helo%").should be_false
271
+ adapter.matches_condition?(article1, :name.like, "hello").should be_false
272
+ adapter.matches_condition?(article1, :name.like, "hello.world").should be_false
273
+ # For some reason this is reporting "The not_matches MetaWhere condition is not supported."
274
+ # adapter.matches_condition?(article1, :name.nlike, "%helo%").should be_true
275
+ # adapter.matches_condition?(article1, :name.nlike, "%ello worl%").should be_false
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,120 @@
1
+ if ENV["MODEL_ADAPTER"] == "data_mapper"
2
+ require "spec_helper"
3
+
4
+ DataMapper.setup(:default, 'sqlite::memory:')
5
+
6
+ class DataMapperArticle
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, :data_mapper_comments
13
+ end
14
+
15
+ class DataMapperComment
16
+ include DataMapper::Resource
17
+ property :id, Serial
18
+ property :spam, Boolean, :default => false
19
+ belongs_to :data_mapper_article
20
+ end
21
+
22
+ DataMapper.finalize
23
+ DataMapper.auto_migrate!
24
+
25
+ describe CanCan::ModelAdapters::DataMapperAdapter do
26
+ before(:each) do
27
+ DataMapperArticle.destroy
28
+ DataMapperComment.destroy
29
+ @ability = Object.new
30
+ @ability.extend(CanCan::Ability)
31
+ end
32
+
33
+ it "is for only data mapper classes" do
34
+ CanCan::ModelAdapters::DataMapperAdapter.should_not be_for_class(Object)
35
+ CanCan::ModelAdapters::DataMapperAdapter.should be_for_class(DataMapperArticle)
36
+ CanCan::ModelAdapters::AbstractAdapter.adapter_class(DataMapperArticle).should == CanCan::ModelAdapters::DataMapperAdapter
37
+ end
38
+
39
+ it "finds record" do
40
+ article = DataMapperArticle.create
41
+ CanCan::ModelAdapters::DataMapperAdapter.find(DataMapperArticle, article.id).should == article
42
+ end
43
+
44
+ it "does not fetch any records when no abilities are defined" do
45
+ DataMapperArticle.create
46
+ DataMapperArticle.accessible_by(@ability).should be_empty
47
+ end
48
+
49
+ it "fetches all articles when one can read all" do
50
+ @ability.can :read, :data_mapper_articles
51
+ article = DataMapperArticle.create
52
+ DataMapperArticle.accessible_by(@ability).should == [article]
53
+ end
54
+
55
+ it "fetches only the articles that are published" do
56
+ @ability.can :read, :data_mapper_articles, :published => true
57
+ article1 = DataMapperArticle.create(:published => true)
58
+ article2 = DataMapperArticle.create(:published => false)
59
+ DataMapperArticle.accessible_by(@ability).should == [article1]
60
+ end
61
+
62
+ it "fetches any articles which are published or secret" do
63
+ @ability.can :read, :data_mapper_articles, :published => true
64
+ @ability.can :read, :data_mapper_articles, :secret => true
65
+ article1 = DataMapperArticle.create(:published => true, :secret => false)
66
+ article2 = DataMapperArticle.create(:published => true, :secret => true)
67
+ article3 = DataMapperArticle.create(:published => false, :secret => true)
68
+ article4 = DataMapperArticle.create(:published => false, :secret => false)
69
+ DataMapperArticle.accessible_by(@ability).should == [article1, article2, article3]
70
+ end
71
+
72
+ it "fetches only the articles that are published and not secret" do
73
+ pending "the `cannot` may require some custom SQL, maybe abstract out from Active Record adapter"
74
+ @ability.can :read, :data_mapper_articles, :published => true
75
+ @ability.cannot :read, :data_mapper_articles, :secret => true
76
+ article1 = DataMapperArticle.create(:published => true, :secret => false)
77
+ article2 = DataMapperArticle.create(:published => true, :secret => true)
78
+ article3 = DataMapperArticle.create(:published => false, :secret => true)
79
+ article4 = DataMapperArticle.create(:published => false, :secret => false)
80
+ DataMapperArticle.accessible_by(@ability).should == [article1]
81
+ end
82
+
83
+ it "only reads comments for articles which are published" do
84
+ @ability.can :read, :data_mapper_comments, :data_mapper_article => { :published => true }
85
+ comment1 = DataMapperComment.create(:data_mapper_article => DataMapperArticle.create!(:published => true))
86
+ comment2 = DataMapperComment.create(:data_mapper_article => DataMapperArticle.create!(:published => false))
87
+ DataMapperComment.accessible_by(@ability).should == [comment1]
88
+ end
89
+
90
+ it "allows conditions in SQL and merge with hash conditions" do
91
+ @ability.can :read, :data_mapper_articles, :published => true
92
+ @ability.can :read, :data_mapper_articles, ["secret=?", true]
93
+ article1 = DataMapperArticle.create(:published => true, :secret => false)
94
+ article4 = DataMapperArticle.create(:published => false, :secret => false)
95
+ DataMapperArticle.accessible_by(@ability).should == [article1]
96
+ end
97
+
98
+ it "matches gt comparison" do
99
+ @ability.can :read, :data_mapper_articles, :priority.gt => 3
100
+ article1 = DataMapperArticle.create(:priority => 4)
101
+ article2 = DataMapperArticle.create(:priority => 3)
102
+ DataMapperArticle.accessible_by(@ability).should == [article1]
103
+ @ability.should be_able_to(:read, article1)
104
+ @ability.should_not be_able_to(:read, article2)
105
+ end
106
+
107
+ it "matches gte comparison" do
108
+ @ability.can :read, :data_mapper_articles, :priority.gte => 3
109
+ article1 = DataMapperArticle.create(:priority => 4)
110
+ article2 = DataMapperArticle.create(:priority => 3)
111
+ article3 = DataMapperArticle.create(:priority => 2)
112
+ DataMapperArticle.accessible_by(@ability).should == [article1, article2]
113
+ @ability.should be_able_to(:read, article1)
114
+ @ability.should be_able_to(:read, article2)
115
+ @ability.should_not be_able_to(:read, article3)
116
+ end
117
+
118
+ # TODO: add more comparison specs
119
+ end
120
+ end