cancan 1.4.1 → 1.5.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.
- data/CHANGELOG.rdoc +21 -0
- data/Gemfile +17 -0
- data/LICENSE +1 -1
- data/README.rdoc +20 -90
- data/Rakefile +8 -0
- 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/cancan.rb +8 -3
- 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 +24 -16
- 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
|
@@ -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
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
if ENV["MODEL_ADAPTER"] == "mongoid"
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
class MongoidCategory
|
|
5
|
+
include Mongoid::Document
|
|
6
|
+
|
|
7
|
+
references_many :mongoid_projects
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class MongoidProject
|
|
11
|
+
include Mongoid::Document
|
|
12
|
+
|
|
13
|
+
referenced_in :mongoid_category
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Mongoid.configure do |config|
|
|
17
|
+
config.master = Mongo::Connection.new('127.0.0.1', 27017).db("cancan_mongoid_spec")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe CanCan::ModelAdapters::MongoidAdapter do
|
|
21
|
+
context "Mongoid defined" do
|
|
22
|
+
before(:each) do
|
|
23
|
+
@ability = Object.new
|
|
24
|
+
@ability.extend(CanCan::Ability)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
after(:each) do
|
|
28
|
+
Mongoid.master.collections.select do |collection|
|
|
29
|
+
collection.name !~ /system/
|
|
30
|
+
end.each(&:drop)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should be for only Mongoid classes" do
|
|
34
|
+
CanCan::ModelAdapters::MongoidAdapter.should_not be_for_class(Object)
|
|
35
|
+
CanCan::ModelAdapters::MongoidAdapter.should be_for_class(MongoidProject)
|
|
36
|
+
CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject).should == CanCan::ModelAdapters::MongoidAdapter
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should compare properties on mongoid documents with the conditions hash" do
|
|
40
|
+
model = MongoidProject.new
|
|
41
|
+
@ability.can :read, MongoidProject, :id => model.id
|
|
42
|
+
@ability.should be_able_to(:read, model)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "should return [] when no ability is defined so no records are found" do
|
|
46
|
+
MongoidProject.create(:title => 'Sir')
|
|
47
|
+
MongoidProject.create(:title => 'Lord')
|
|
48
|
+
MongoidProject.create(:title => 'Dude')
|
|
49
|
+
|
|
50
|
+
MongoidProject.accessible_by(@ability, :read).entries.should == []
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "should return the correct records based on the defined ability" do
|
|
54
|
+
@ability.can :read, MongoidProject, :title => "Sir"
|
|
55
|
+
sir = MongoidProject.create(:title => 'Sir')
|
|
56
|
+
lord = MongoidProject.create(:title => 'Lord')
|
|
57
|
+
dude = MongoidProject.create(:title => 'Dude')
|
|
58
|
+
|
|
59
|
+
MongoidProject.accessible_by(@ability, :read).should == [sir]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "should return everything when the defined ability is manage all" do
|
|
63
|
+
@ability.can :manage, :all
|
|
64
|
+
sir = MongoidProject.create(:title => 'Sir')
|
|
65
|
+
lord = MongoidProject.create(:title => 'Lord')
|
|
66
|
+
dude = MongoidProject.create(:title => 'Dude')
|
|
67
|
+
|
|
68
|
+
MongoidProject.accessible_by(@ability, :read).entries.should == [sir, lord, dude]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
describe "Mongoid::Criteria where clause Symbol extensions using MongoDB expressions" do
|
|
73
|
+
it "should handle :field.in" do
|
|
74
|
+
obj = MongoidProject.create(:title => 'Sir')
|
|
75
|
+
@ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
|
|
76
|
+
@ability.can?(:read, obj).should == true
|
|
77
|
+
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
|
78
|
+
|
|
79
|
+
obj2 = MongoidProject.create(:title => 'Lord')
|
|
80
|
+
@ability.can?(:read, obj2).should == false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe "activates only when there are Criteria in the hash" do
|
|
84
|
+
it "Calls where on the model class when there are criteria" do
|
|
85
|
+
obj = MongoidProject.create(:title => 'Bird')
|
|
86
|
+
@conditions = {:title.nin => ["Fork", "Spoon"]}
|
|
87
|
+
|
|
88
|
+
@ability.can :read, MongoidProject, @conditions
|
|
89
|
+
@ability.should be_able_to(:read, obj)
|
|
90
|
+
end
|
|
91
|
+
it "Calls the base version if there are no mongoid criteria" do
|
|
92
|
+
obj = MongoidProject.new(:title => 'Bird')
|
|
93
|
+
@conditions = {:id => obj.id}
|
|
94
|
+
@ability.can :read, MongoidProject, @conditions
|
|
95
|
+
@ability.should be_able_to(:read, obj)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should handle :field.nin" do
|
|
100
|
+
obj = MongoidProject.create(:title => 'Sir')
|
|
101
|
+
@ability.can :read, MongoidProject, :title.nin => ["Lord", "Madam"]
|
|
102
|
+
@ability.can?(:read, obj).should == true
|
|
103
|
+
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
|
104
|
+
|
|
105
|
+
obj2 = MongoidProject.create(:title => 'Lord')
|
|
106
|
+
@ability.can?(:read, obj2).should == false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "should handle :field.size" do
|
|
110
|
+
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
|
|
111
|
+
@ability.can :read, MongoidProject, :titles.size => 2
|
|
112
|
+
@ability.can?(:read, obj).should == true
|
|
113
|
+
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
|
114
|
+
|
|
115
|
+
obj2 = MongoidProject.create(:titles => ['Palatin', 'Margrave', 'Marquis'])
|
|
116
|
+
@ability.can?(:read, obj2).should == false
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "should handle :field.exists" do
|
|
120
|
+
obj = MongoidProject.create(:titles => ['Palatin', 'Margrave'])
|
|
121
|
+
@ability.can :read, MongoidProject, :titles.exists => true
|
|
122
|
+
@ability.can?(:read, obj).should == true
|
|
123
|
+
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
|
124
|
+
|
|
125
|
+
obj2 = MongoidProject.create
|
|
126
|
+
@ability.can?(:read, obj2).should == false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "should handle :field.gt" do
|
|
130
|
+
obj = MongoidProject.create(:age => 50)
|
|
131
|
+
@ability.can :read, MongoidProject, :age.gt => 45
|
|
132
|
+
@ability.can?(:read, obj).should == true
|
|
133
|
+
MongoidProject.accessible_by(@ability, :read).should == [obj]
|
|
134
|
+
|
|
135
|
+
obj2 = MongoidProject.create(:age => 40)
|
|
136
|
+
@ability.can?(:read, obj2).should == false
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "should handle instance not saved to database" do
|
|
140
|
+
obj = MongoidProject.new(:title => 'Sir')
|
|
141
|
+
@ability.can :read, MongoidProject, :title.in => ["Sir", "Madam"]
|
|
142
|
+
@ability.can?(:read, obj).should == true
|
|
143
|
+
|
|
144
|
+
# accessible_by only returns saved records
|
|
145
|
+
MongoidProject.accessible_by(@ability, :read).entries.should == []
|
|
146
|
+
|
|
147
|
+
obj2 = MongoidProject.new(:title => 'Lord')
|
|
148
|
+
@ability.can?(:read, obj2).should == false
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "should call where with matching ability conditions" do
|
|
153
|
+
obj = MongoidProject.create(:foo => {:bar => 1})
|
|
154
|
+
@ability.can :read, MongoidProject, :foo => {:bar => 1}
|
|
155
|
+
MongoidProject.accessible_by(@ability, :read).entries.first.should == obj
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "should not allow to fetch records when ability with just block present" do
|
|
159
|
+
@ability.can :read, MongoidProject do
|
|
160
|
+
false
|
|
161
|
+
end
|
|
162
|
+
lambda {
|
|
163
|
+
MongoidProject.accessible_by(@ability)
|
|
164
|
+
}.should raise_error(CanCan::Error)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
# Most of Rule functionality is tested in Ability specs
|
|
4
|
+
describe CanCan::Rule do
|
|
5
|
+
before(:each) do
|
|
6
|
+
@conditions = {}
|
|
7
|
+
@rule = CanCan::Rule.new(true, :read, Integer, @conditions, nil)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "should return no association joins if none exist" do
|
|
11
|
+
@rule.associations_hash.should == {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "should return no association for joins if just attributes" do
|
|
15
|
+
@conditions[:foo] = :bar
|
|
16
|
+
@rule.associations_hash.should == {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should return single association for joins" do
|
|
20
|
+
@conditions[:foo] = {:bar => 1}
|
|
21
|
+
@rule.associations_hash.should == {:foo => {}}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should return multiple associations for joins" do
|
|
25
|
+
@conditions[:foo] = {:bar => 1}
|
|
26
|
+
@conditions[:test] = {1 => 2}
|
|
27
|
+
@rule.associations_hash.should == {:foo => {}, :test => {}}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "should return nested associations for joins" do
|
|
31
|
+
@conditions[:foo] = {:bar => {1 => 2}}
|
|
32
|
+
@rule.associations_hash.should == {:foo => {:bar => {}}}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should return no association joins if conditions is nil" do
|
|
36
|
+
rule = CanCan::Rule.new(true, :read, Integer, nil, nil)
|
|
37
|
+
rule.associations_hash.should == {}
|
|
38
|
+
end
|
|
39
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
require 'bundler/setup'
|
|
3
|
+
|
|
3
4
|
Bundler.require(:default)
|
|
5
|
+
|
|
4
6
|
require 'supermodel' # shouldn't Bundler do this already?
|
|
5
7
|
require 'active_support/all'
|
|
6
8
|
require 'matchers'
|
|
@@ -27,28 +29,4 @@ end
|
|
|
27
29
|
|
|
28
30
|
class Project < SuperModel::Base
|
|
29
31
|
belongs_to :category
|
|
30
|
-
|
|
31
|
-
class << self
|
|
32
|
-
protected
|
|
33
|
-
|
|
34
|
-
def sanitize_sql(hash_cond)
|
|
35
|
-
case hash_cond
|
|
36
|
-
when Hash
|
|
37
|
-
sanitize_hash(hash_cond).join(' AND ')
|
|
38
|
-
when Array
|
|
39
|
-
hash_cond.shift.gsub('?'){"#{hash_cond.shift.inspect}"}
|
|
40
|
-
when String then hash_cond
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def sanitize_hash(hash)
|
|
45
|
-
hash.map do |name, value|
|
|
46
|
-
if Hash === value
|
|
47
|
-
sanitize_hash(value).map{|cond| "#{name}.#{cond}"}
|
|
48
|
-
else
|
|
49
|
-
"#{name}=#{value}"
|
|
50
|
-
end
|
|
51
|
-
end.flatten
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
32
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cancan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 3
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 1
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
version: 1.
|
|
8
|
+
- 5
|
|
9
|
+
- 0
|
|
10
|
+
version: 1.5.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Ryan Bates
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date:
|
|
18
|
+
date: 2011-01-11 00:00:00 -08:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|
|
@@ -26,14 +26,12 @@ dependencies:
|
|
|
26
26
|
requirements:
|
|
27
27
|
- - ~>
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
hash:
|
|
29
|
+
hash: 11
|
|
30
30
|
segments:
|
|
31
31
|
- 2
|
|
32
|
+
- 1
|
|
32
33
|
- 0
|
|
33
|
-
|
|
34
|
-
- beta
|
|
35
|
-
- 22
|
|
36
|
-
version: 2.0.0.beta.22
|
|
34
|
+
version: 2.1.0
|
|
37
35
|
type: :development
|
|
38
36
|
version_requirements: *id001
|
|
39
37
|
- !ruby/object:Gem::Dependency
|
|
@@ -94,25 +92,35 @@ extra_rdoc_files: []
|
|
|
94
92
|
|
|
95
93
|
files:
|
|
96
94
|
- lib/cancan/ability.rb
|
|
97
|
-
- lib/cancan/active_record_additions.rb
|
|
98
|
-
- lib/cancan/can_definition.rb
|
|
99
95
|
- lib/cancan/controller_additions.rb
|
|
100
96
|
- lib/cancan/controller_resource.rb
|
|
101
97
|
- lib/cancan/exceptions.rb
|
|
102
98
|
- lib/cancan/inherited_resource.rb
|
|
103
99
|
- lib/cancan/matchers.rb
|
|
104
|
-
- lib/cancan/
|
|
100
|
+
- lib/cancan/model_adapters/abstract_adapter.rb
|
|
101
|
+
- lib/cancan/model_adapters/active_record_adapter.rb
|
|
102
|
+
- lib/cancan/model_adapters/data_mapper_adapter.rb
|
|
103
|
+
- lib/cancan/model_adapters/default_adapter.rb
|
|
104
|
+
- lib/cancan/model_adapters/mongoid_adapter.rb
|
|
105
|
+
- lib/cancan/model_additions.rb
|
|
106
|
+
- lib/cancan/rule.rb
|
|
105
107
|
- lib/cancan.rb
|
|
108
|
+
- lib/generators/cancan/ability/ability_generator.rb
|
|
109
|
+
- lib/generators/cancan/ability/templates/ability.rb
|
|
110
|
+
- lib/generators/cancan/ability/USAGE
|
|
106
111
|
- spec/cancan/ability_spec.rb
|
|
107
|
-
- spec/cancan/active_record_additions_spec.rb
|
|
108
|
-
- spec/cancan/can_definition_spec.rb
|
|
109
112
|
- spec/cancan/controller_additions_spec.rb
|
|
110
113
|
- spec/cancan/controller_resource_spec.rb
|
|
111
114
|
- spec/cancan/exceptions_spec.rb
|
|
112
115
|
- spec/cancan/inherited_resource_spec.rb
|
|
113
116
|
- spec/cancan/matchers_spec.rb
|
|
114
|
-
- spec/cancan/
|
|
117
|
+
- spec/cancan/model_adapters/active_record_adapter_spec.rb
|
|
118
|
+
- spec/cancan/model_adapters/data_mapper_adapter_spec.rb
|
|
119
|
+
- spec/cancan/model_adapters/default_adapter_spec.rb
|
|
120
|
+
- spec/cancan/model_adapters/mongoid_adapter_spec.rb
|
|
121
|
+
- spec/cancan/rule_spec.rb
|
|
115
122
|
- spec/matchers.rb
|
|
123
|
+
- spec/README.rdoc
|
|
116
124
|
- spec/spec.opts
|
|
117
125
|
- spec/spec_helper.rb
|
|
118
126
|
- CHANGELOG.rdoc
|
data/lib/cancan/query.rb
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
module CanCan
|
|
2
|
-
|
|
3
|
-
# Generates the sql conditions and association joins for use in ActiveRecord queries.
|
|
4
|
-
# Normally you will not use this class directly, but instead through ActiveRecordAdditions#accessible_by.
|
|
5
|
-
class Query
|
|
6
|
-
def initialize(sanitizer, can_definitions)
|
|
7
|
-
@sanitizer = sanitizer
|
|
8
|
-
@can_definitions = can_definitions
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
# Returns conditions intended to be used inside a database query. Normally you will not call this
|
|
12
|
-
# method directly, but instead go through ActiveRecordAdditions#accessible_by.
|
|
13
|
-
#
|
|
14
|
-
# If there is only one "can" definition, a hash of conditions will be returned matching the one defined.
|
|
15
|
-
#
|
|
16
|
-
# can :manage, User, :id => 1
|
|
17
|
-
# query(:manage, User).conditions # => { :id => 1 }
|
|
18
|
-
#
|
|
19
|
-
# If there are multiple "can" definitions, a SQL string will be returned to handle complex cases.
|
|
20
|
-
#
|
|
21
|
-
# can :manage, User, :id => 1
|
|
22
|
-
# can :manage, User, :manager_id => 1
|
|
23
|
-
# cannot :manage, User, :self_managed => true
|
|
24
|
-
# query(:manage, User).conditions # => "not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))"
|
|
25
|
-
#
|
|
26
|
-
def conditions
|
|
27
|
-
if @can_definitions.size == 1 && @can_definitions.first.base_behavior
|
|
28
|
-
# Return the conditions directly if there's just one definition
|
|
29
|
-
@can_definitions.first.tableized_conditions
|
|
30
|
-
else
|
|
31
|
-
@can_definitions.reverse.inject(false_sql) do |sql, can_definition|
|
|
32
|
-
merge_conditions(sql, can_definition.tableized_conditions, can_definition.base_behavior)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Returns the associations used in conditions for the :joins option of a search.
|
|
38
|
-
# See ActiveRecordAdditions#accessible_by for use in Active Record.
|
|
39
|
-
def joins
|
|
40
|
-
joins_hash = {}
|
|
41
|
-
@can_definitions.each do |can_definition|
|
|
42
|
-
merge_joins(joins_hash, can_definition.associations_hash)
|
|
43
|
-
end
|
|
44
|
-
clean_joins(joins_hash) unless joins_hash.empty?
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def merge_conditions(sql, conditions_hash, behavior)
|
|
50
|
-
if conditions_hash.blank?
|
|
51
|
-
behavior ? true_sql : false_sql
|
|
52
|
-
else
|
|
53
|
-
conditions = sanitize_sql(conditions_hash)
|
|
54
|
-
case sql
|
|
55
|
-
when true_sql
|
|
56
|
-
behavior ? true_sql : "not (#{conditions})"
|
|
57
|
-
when false_sql
|
|
58
|
-
behavior ? conditions : false_sql
|
|
59
|
-
else
|
|
60
|
-
behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})"
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def false_sql
|
|
66
|
-
sanitize_sql(['?=?', true, false])
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def true_sql
|
|
70
|
-
sanitize_sql(['?=?', true, true])
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def sanitize_sql(conditions)
|
|
74
|
-
@sanitizer.send(:sanitize_sql, conditions)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Takes two hashes and does a deep merge.
|
|
78
|
-
def merge_joins(base, add)
|
|
79
|
-
add.each do |name, nested|
|
|
80
|
-
if base[name].is_a?(Hash) && !nested.empty?
|
|
81
|
-
merge_joins(base[name], nested)
|
|
82
|
-
else
|
|
83
|
-
base[name] = nested
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Removes empty hashes and moves everything into arrays.
|
|
89
|
-
def clean_joins(joins_hash)
|
|
90
|
-
joins = []
|
|
91
|
-
joins_hash.each do |name, nested|
|
|
92
|
-
joins << (nested.empty? ? name : {name => clean_joins(nested)})
|
|
93
|
-
end
|
|
94
|
-
joins
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
require "spec_helper"
|
|
2
|
-
|
|
3
|
-
describe CanCan::ActiveRecordAdditions do
|
|
4
|
-
before(:each) do
|
|
5
|
-
@model_class = Class.new(Project)
|
|
6
|
-
stub(@model_class).scoped { :scoped_stub }
|
|
7
|
-
@model_class.send(:include, CanCan::ActiveRecordAdditions)
|
|
8
|
-
@ability = Object.new
|
|
9
|
-
@ability.extend(CanCan::Ability)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
it "should call where('true=false') when no ability is defined so no records are found" do
|
|
13
|
-
stub(@model_class).joins { true } # just so it responds to .joins as well
|
|
14
|
-
stub(@model_class).where('true=false').stub!.joins(nil) { :no_match }
|
|
15
|
-
@model_class.accessible_by(@ability, :read).should == :no_match
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it "should call where with matching ability conditions" do
|
|
19
|
-
@ability.can :read, @model_class, :foo => {:bar => 1}
|
|
20
|
-
stub(@model_class).joins { true } # just so it responds to .joins as well
|
|
21
|
-
stub(@model_class).where(:foos => { :bar => 1 }).stub!.joins([:foo]) { :found_records }
|
|
22
|
-
@model_class.accessible_by(@ability, :read).should == :found_records
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
it "should default to :read ability and use scoped when where isn't available" do
|
|
26
|
-
@ability.can :read, @model_class, :foo => {:bar => 1}
|
|
27
|
-
stub(@model_class).scoped(:conditions => {:foos => {:bar => 1}}, :joins => [:foo]) { :found_records }
|
|
28
|
-
@model_class.accessible_by(@ability).should == :found_records
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it "should merge association joins and sanitize conditions" do
|
|
32
|
-
@ability.can :read, @model_class, :foo => {:bar => 1}
|
|
33
|
-
@ability.can :read, @model_class, :too => {:car => 1, :far => {:bar => 1}}
|
|
34
|
-
|
|
35
|
-
condition_variants = [
|
|
36
|
-
'(toos.fars.bar=1 AND toos.car=1) OR (foos.bar=1)', # faked sql sanitizer is stupid ;-)
|
|
37
|
-
'(toos.car=1 AND toos.fars.bar=1) OR (foos.bar=1)'
|
|
38
|
-
]
|
|
39
|
-
joins_variants = [
|
|
40
|
-
[:foo, {:too => [:far]}],
|
|
41
|
-
[{:too => [:far]}, :foo]
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
condition_variants.each do |condition|
|
|
45
|
-
joins_variants.each do |joins|
|
|
46
|
-
stub(@model_class).scoped( :conditions => condition, :joins => joins ) { :found_records }
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
# @ability.conditions(:read, @model_class).should == '(too.car=1 AND too.far.bar=1) OR (foo.bar=1)'
|
|
50
|
-
# @ability.associations_hash(:read, @model_class).should == [{:too => [:far]}, :foo]
|
|
51
|
-
@model_class.accessible_by(@ability).should == :found_records
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "should allow to define sql conditions by not hash" do
|
|
55
|
-
@ability.can :read, @model_class, :foo => 1
|
|
56
|
-
@ability.can :read, @model_class, ['bar = ?', 1]
|
|
57
|
-
stub(@model_class).scoped( :conditions => '(bar = 1) OR (foo=1)', :joins => nil ) { :found_records }
|
|
58
|
-
stub(@model_class).scoped{|*args| args.inspect}
|
|
59
|
-
@model_class.accessible_by(@ability).should == :found_records
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it "should not allow to fetch records when ability with just block present" do
|
|
63
|
-
@ability.can :read, @model_class do false end
|
|
64
|
-
lambda {
|
|
65
|
-
@model_class.accessible_by(@ability)
|
|
66
|
-
}.should raise_error(CanCan::Error)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it "should not allow to check ability on object when nonhash sql ability definition without block present" do
|
|
70
|
-
@ability.can :read, @model_class, ['bar = ?', 1]
|
|
71
|
-
lambda {
|
|
72
|
-
@ability.can? :read, @model_class.new
|
|
73
|
-
}.should raise_error(CanCan::Error)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
require "spec_helper"
|
|
2
|
-
|
|
3
|
-
# Most of CanDefinition functionality is tested in Ability specs
|
|
4
|
-
describe CanCan::CanDefinition do
|
|
5
|
-
before(:each) do
|
|
6
|
-
@conditions = {}
|
|
7
|
-
@can = CanCan::CanDefinition.new(true, :read, Integer, @conditions, nil)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
it "should return no association joins if none exist" do
|
|
11
|
-
@can.associations_hash.should == {}
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
it "should return no association for joins if just attributes" do
|
|
15
|
-
@conditions[:foo] = :bar
|
|
16
|
-
@can.associations_hash.should == {}
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "should return single association for joins" do
|
|
20
|
-
@conditions[:foo] = {:bar => 1}
|
|
21
|
-
@can.associations_hash.should == {:foo => {}}
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
it "should return multiple associations for joins" do
|
|
25
|
-
@conditions[:foo] = {:bar => 1}
|
|
26
|
-
@conditions[:test] = {1 => 2}
|
|
27
|
-
@can.associations_hash.should == {:foo => {}, :test => {}}
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it "should return nested associations for joins" do
|
|
31
|
-
@conditions[:foo] = {:bar => {1 => 2}}
|
|
32
|
-
@can.associations_hash.should == {:foo => {:bar => {}}}
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "should tableize correctly for absurdly complex permissions" do
|
|
36
|
-
@conditions[:unit] = {:property=>{:landlord=>{:weasle_id=>560}}}
|
|
37
|
-
@conditions[:test] = 1
|
|
38
|
-
@can.tableized_conditions.should == {:units => {:properties => {:landlords=>{:weasle_id=>560}}}, :test => 1}
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it "should tableize correctly for complex permissions" do
|
|
42
|
-
@conditions[:unit] = {:property=>{:landlord_id=>560}}
|
|
43
|
-
@conditions[:test] = 1
|
|
44
|
-
@can.tableized_conditions.should == {:units => {:properties => {:landlord_id=>560}}, :test => 1}
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "should return table names in conditions for association joins" do
|
|
48
|
-
@conditions[:foo] = {:bar => 1}
|
|
49
|
-
@conditions[:test] = 1
|
|
50
|
-
@can.tableized_conditions.should == {:foos => {:bar => 1}, :test => 1}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
it "should return no association joins if conditions is nil" do
|
|
54
|
-
can = CanCan::CanDefinition.new(true, :read, Integer, nil, nil)
|
|
55
|
-
can.associations_hash.should == {}
|
|
56
|
-
end
|
|
57
|
-
end
|