cancan 1.2.0 → 1.3.2

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.
@@ -2,16 +2,16 @@ require "spec_helper"
2
2
 
3
3
  describe CanCan::ActiveRecordAdditions do
4
4
  before(:each) do
5
- @model_class = Class.new
5
+ @model_class = Class.new(Person)
6
6
  stub(@model_class).scoped { :scoped_stub }
7
7
  @model_class.send(:include, CanCan::ActiveRecordAdditions)
8
8
  @ability = Object.new
9
9
  @ability.extend(CanCan::Ability)
10
10
  end
11
11
 
12
- it "should call where(:id => nil) when no ability is defined so no records are found" do
13
- stub(@model_class).where(:id => nil).stub!.joins(nil) { :no_where }
14
- @model_class.accessible_by(@ability, :read).should == :no_where
12
+ it "should call where('true=false') when no ability is defined so no records are found" do
13
+ stub(@model_class).where('true=false').stub!.joins(nil) { :no_match }
14
+ @model_class.accessible_by(@ability, :read).should == :no_match
15
15
  end
16
16
 
17
17
  it "should call where with matching ability conditions" do
@@ -25,4 +25,27 @@ describe CanCan::ActiveRecordAdditions do
25
25
  stub(@model_class).scoped(:conditions => {:foos => {:bar => 1}}, :joins => [:foo]) { :found_records }
26
26
  @model_class.accessible_by(@ability).should == :found_records
27
27
  end
28
+
29
+ it "should merge association joins and sanitize conditions" do
30
+ @ability.can :read, @model_class, :foo => {:bar => 1}
31
+ @ability.can :read, @model_class, :too => {:car => 1, :far => {:bar => 1}}
32
+
33
+ condition_variants = [
34
+ '(toos.far.bar=1 AND toos.car=1) OR (foos.bar=1)', # faked sql sanitizer is stupid ;-)
35
+ '(toos.car=1 AND toos.far.bar=1) OR (foos.bar=1)'
36
+ ]
37
+ joins_variants = [
38
+ [:foo, {:too => [:far]}],
39
+ [{:too => [:far]}, :foo]
40
+ ]
41
+
42
+ condition_variants.each do |condition|
43
+ joins_variants.each do |joins|
44
+ stub(@model_class).scoped( :conditions => condition, :joins => joins ) { :found_records }
45
+ end
46
+ end
47
+ # @ability.conditions(:read, @model_class).should == '(too.car=1 AND too.far.bar=1) OR (foo.bar=1)'
48
+ # @ability.associations_hash(:read, @model_class).should == [{:too => [:far]}, :foo]
49
+ @model_class.accessible_by(@ability).should == :found_records
50
+ end
28
51
  end
@@ -7,38 +7,38 @@ describe CanCan::CanDefinition do
7
7
  end
8
8
 
9
9
  it "should return no association joins if none exist" do
10
- @can.association_joins.should be_nil
10
+ @can.associations_hash.should == {}
11
11
  end
12
12
 
13
13
  it "should return no association for joins if just attributes" do
14
14
  @conditions[:foo] = :bar
15
- @can.association_joins.should be_nil
15
+ @can.associations_hash.should == {}
16
16
  end
17
17
 
18
18
  it "should return single association for joins" do
19
19
  @conditions[:foo] = {:bar => 1}
20
- @can.association_joins.should == [:foo]
20
+ @can.associations_hash.should == {:foo => {}}
21
21
  end
22
22
 
23
23
  it "should return multiple associations for joins" do
24
24
  @conditions[:foo] = {:bar => 1}
25
25
  @conditions[:test] = {1 => 2}
26
- @can.association_joins.map(&:to_s).sort.should == [:foo, :test].map(&:to_s).sort
26
+ @can.associations_hash.should == {:foo => {}, :test => {}}
27
27
  end
28
28
 
29
29
  it "should return nested associations for joins" do
30
30
  @conditions[:foo] = {:bar => {1 => 2}}
31
- @can.association_joins.should == [{:foo => [:bar]}]
31
+ @can.associations_hash.should == {:foo => {:bar => {}}}
32
32
  end
33
33
 
34
34
  it "should return table names in conditions for association joins" do
35
35
  @conditions[:foo] = {:bar => 1}
36
36
  @conditions[:test] = 1
37
- @can.conditions(:tableize => true).should == { :foos => { :bar => 1}, :test => 1 }
37
+ @can.tableized_conditions.should == {:foos => {:bar => 1}, :test => 1}
38
38
  end
39
39
 
40
40
  it "should return no association joins if conditions is nil" do
41
41
  can = CanCan::CanDefinition.new(true, :read, Integer, nil, nil)
42
- can.association_joins.should be_nil
42
+ can.associations_hash.should == {}
43
43
  end
44
44
  end
@@ -52,20 +52,26 @@ describe CanCan::ControllerAdditions do
52
52
  @controller.cannot?(:foo, :bar).should be_true
53
53
  end
54
54
 
55
- it "load_and_authorize_resource should setup a before filter which passes call to ResourceAuthorization" do
56
- stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_and_authorize_resource
55
+ it "load_and_authorize_resource should setup a before filter which passes call to ControllerResource" do
56
+ stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.load_and_authorize_resource
57
57
  mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
58
58
  @controller_class.load_and_authorize_resource :foo => :bar
59
59
  end
60
60
 
61
- it "authorize_resource should setup a before filter which passes call to ResourceAuthorization" do
62
- stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.authorize_resource
61
+ it "load_and_authorize_resource should properly pass first argument as the resource name" do
62
+ stub(CanCan::ControllerResource).new(@controller, :project, :foo => :bar).mock!.load_and_authorize_resource
63
+ mock(@controller_class).before_filter({}) { |options, block| block.call(@controller) }
64
+ @controller_class.load_and_authorize_resource :project, :foo => :bar
65
+ end
66
+
67
+ it "authorize_resource should setup a before filter which passes call to ControllerResource" do
68
+ stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.authorize_resource
63
69
  mock(@controller_class).before_filter(:except => :show) { |options, block| block.call(@controller) }
64
70
  @controller_class.authorize_resource :foo => :bar, :except => :show
65
71
  end
66
72
 
67
- it "load_resource should setup a before filter which passes call to ResourceAuthorization" do
68
- stub(CanCan::ResourceAuthorization).new(@controller, @controller.params, :foo => :bar).mock!.load_resource
73
+ it "load_resource should setup a before filter which passes call to ControllerResource" do
74
+ stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.load_resource
69
75
  mock(@controller_class).before_filter(:only => [:show, :index]) { |options, block| block.call(@controller) }
70
76
  @controller_class.load_resource :foo => :bar, :only => [:show, :index]
71
77
  end
@@ -2,58 +2,250 @@ require "spec_helper"
2
2
 
3
3
  describe CanCan::ControllerResource do
4
4
  before(:each) do
5
- @controller = Object.new
5
+ @params = HashWithIndifferentAccess.new(:controller => "abilities")
6
+ @controller = Object.new # simple stub for now
7
+ stub(@controller).params { @params }
6
8
  end
7
9
 
8
- it "should determine model class by constantizing give name" do
9
- CanCan::ControllerResource.new(@controller, :ability).model_class.should == Ability
10
+ it "should load the resource into an instance variable if params[:id] is specified" do
11
+ @params.merge!(:action => "show", :id => 123)
12
+ stub(Ability).find(123) { :some_resource }
13
+ resource = CanCan::ControllerResource.new(@controller)
14
+ resource.load_resource
15
+ @controller.instance_variable_get(:@ability).should == :some_resource
10
16
  end
11
17
 
12
- it "should fetch model through model class and assign it to the instance" do
13
- stub(Ability).find(123) { :some_ability }
14
- CanCan::ControllerResource.new(@controller, :ability).find(123)
18
+ it "should not load resource into an instance variable if already set" do
19
+ @params.merge!(:action => "show", :id => 123)
20
+ @controller.instance_variable_set(:@ability, :some_ability)
21
+ resource = CanCan::ControllerResource.new(@controller)
22
+ resource.load_resource
15
23
  @controller.instance_variable_get(:@ability).should == :some_ability
16
24
  end
17
25
 
18
- it "should fetch model through parent and assign it to the instance" do
19
- parent = Object.new
20
- stub(parent).model_instance.stub!.abilities.stub!.find(123) { :some_ability }
21
- CanCan::ControllerResource.new(@controller, :ability, parent).find(123)
26
+ it "should properly load resource for namespaced controller" do
27
+ @params.merge!(:controller => "admin/abilities", :action => "show", :id => 123)
28
+ stub(Ability).find(123) { :some_resource }
29
+ resource = CanCan::ControllerResource.new(@controller)
30
+ resource.load_resource
31
+ @controller.instance_variable_get(:@ability).should == :some_resource
32
+ end
33
+
34
+ it "should properly load resource for namespaced controller when using '::' for namespace" do
35
+ @params.merge!(:controller => "Admin::AbilitiesController", :action => "show", :id => 123)
36
+ stub(Ability).find(123) { :some_resource }
37
+ resource = CanCan::ControllerResource.new(@controller)
38
+ resource.load_resource
39
+ @controller.instance_variable_get(:@ability).should == :some_resource
40
+ end
41
+
42
+ it "should build a new resource with hash if params[:id] is not specified" do
43
+ @params.merge!(:action => "create", :ability => {:foo => "bar"})
44
+ stub(Ability).new("foo" => "bar") { :some_resource }
45
+ resource = CanCan::ControllerResource.new(@controller)
46
+ resource.load_resource
47
+ @controller.instance_variable_get(:@ability).should == :some_resource
48
+ end
49
+
50
+ it "should build a new resource with no arguments if attribute hash isn't specified" do
51
+ @params[:action] = "new"
52
+ mock(Ability).new { :some_resource }
53
+ resource = CanCan::ControllerResource.new(@controller)
54
+ resource.load_resource
55
+ @controller.instance_variable_get(:@ability).should == :some_resource
56
+ end
57
+
58
+ it "should not build a resource when on index action" do
59
+ @params[:action] = "index"
60
+ resource = CanCan::ControllerResource.new(@controller)
61
+ resource.load_resource
62
+ @controller.instance_variable_get(:@ability).should be_nil
63
+ end
64
+
65
+ it "should perform authorization using controller action and loaded model" do
66
+ @params[:action] = "show"
67
+ @controller.instance_variable_set(:@ability, :some_resource)
68
+ stub(@controller).authorize!(:show, :some_resource) { raise CanCan::AccessDenied }
69
+ resource = CanCan::ControllerResource.new(@controller)
70
+ lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
71
+ end
72
+
73
+ it "should perform authorization using controller action and non loaded model" do
74
+ @params[:action] = "show"
75
+ stub(@controller).authorize!(:show, Ability) { raise CanCan::AccessDenied }
76
+ resource = CanCan::ControllerResource.new(@controller)
77
+ lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
78
+ end
79
+
80
+ it "should call load_resource and authorize_resource for load_and_authorize_resource" do
81
+ @params[:action] = "show"
82
+ resource = CanCan::ControllerResource.new(@controller)
83
+ mock(resource).load_resource
84
+ mock(resource).authorize_resource
85
+ resource.load_and_authorize_resource
86
+ end
87
+
88
+ it "should not build a resource when on custom collection action" do
89
+ @params[:action] = "sort"
90
+ resource = CanCan::ControllerResource.new(@controller, :collection => [:sort, :list])
91
+ resource.load_resource
92
+ @controller.instance_variable_get(:@ability).should be_nil
93
+ end
94
+
95
+ it "should build a resource when on custom new action even when params[:id] exists" do
96
+ @params.merge!(:action => "build", :id => 123)
97
+ stub(Ability).new { :some_resource }
98
+ resource = CanCan::ControllerResource.new(@controller, :new => :build)
99
+ resource.load_resource
100
+ @controller.instance_variable_get(:@ability).should == :some_resource
101
+ end
102
+
103
+ it "should not try to load resource for other action if params[:id] is undefined" do
104
+ @params[:action] = "list"
105
+ resource = CanCan::ControllerResource.new(@controller)
106
+ resource.load_resource
107
+ @controller.instance_variable_get(:@ability).should be_nil
108
+ end
109
+
110
+ it "should be a parent resource when name is provided which doesn't match controller" do
111
+ resource = CanCan::ControllerResource.new(@controller, :person)
112
+ resource.should be_parent
113
+ end
114
+
115
+ it "should not be a parent resource when name is provided which matches controller" do
116
+ resource = CanCan::ControllerResource.new(@controller, :ability)
117
+ resource.should_not be_parent
118
+ end
119
+
120
+ it "should be parent if specified in options" do
121
+ resource = CanCan::ControllerResource.new(@controller, :ability, {:parent => true})
122
+ resource.should be_parent
123
+ end
124
+
125
+ it "should not be parent if specified in options" do
126
+ resource = CanCan::ControllerResource.new(@controller, :person, {:parent => false})
127
+ resource.should_not be_parent
128
+ end
129
+
130
+ it "should load parent resource through proper id parameter when supplying a resource with a different name" do
131
+ @params.merge!(:action => "index", :person_id => 123)
132
+ stub(Person).find(123) { :some_person }
133
+ resource = CanCan::ControllerResource.new(@controller, :person)
134
+ resource.load_resource
135
+ @controller.instance_variable_get(:@person).should == :some_person
136
+ end
137
+
138
+ it "should load parent resource for collection action" do
139
+ @params.merge!(:action => "index", :person_id => 456)
140
+ stub(Person).find(456) { :some_person }
141
+ resource = CanCan::ControllerResource.new(@controller, :person)
142
+ resource.load_resource
143
+ @controller.instance_variable_get(:@person).should == :some_person
144
+ end
145
+
146
+ it "should load resource through the association of another parent resource" do
147
+ @params.merge!(:action => "show", :id => 123)
148
+ person = Object.new
149
+ @controller.instance_variable_set(:@person, person)
150
+ stub(person).abilities.stub!.find(123) { :some_ability }
151
+ resource = CanCan::ControllerResource.new(@controller, :through => :person)
152
+ resource.load_resource
22
153
  @controller.instance_variable_get(:@ability).should == :some_ability
23
154
  end
24
155
 
25
- it "should build model through model class and assign it to the instance" do
26
- stub(Ability).new(123) { :some_ability }
27
- CanCan::ControllerResource.new(@controller, :ability).build(123)
156
+ it "should not load through parent resource if instance isn't loaded" do
157
+ @params.merge!(:action => "show", :id => 123)
158
+ stub(Ability).find(123) { :some_ability }
159
+ resource = CanCan::ControllerResource.new(@controller, :through => :person)
160
+ resource.load_resource
28
161
  @controller.instance_variable_get(:@ability).should == :some_ability
29
162
  end
30
163
 
31
- it "should build model through parent and assign it to the instance" do
32
- parent = Object.new
33
- stub(parent).model_instance.stub!.abilities.stub!.build(123) { :some_ability }
34
- CanCan::ControllerResource.new(@controller, :ability, parent).build(123)
164
+ it "should load through first matching if multiple are given" do
165
+ @params.merge!(:action => "show", :id => 123)
166
+ person = Object.new
167
+ @controller.instance_variable_set(:@person, person)
168
+ stub(person).abilities.stub!.find(123) { :some_ability }
169
+ resource = CanCan::ControllerResource.new(@controller, :through => [:thing, :person])
170
+ resource.load_resource
35
171
  @controller.instance_variable_get(:@ability).should == :some_ability
36
172
  end
37
173
 
38
- it "should not load resource if instance variable is already provided" do
39
- @controller.instance_variable_set(:@ability, :some_ability)
40
- CanCan::ControllerResource.new(@controller, :ability).find(123)
174
+ it "should find record through has_one association with :singleton option" do
175
+ @params.merge!(:action => "show")
176
+ person = Object.new
177
+ @controller.instance_variable_set(:@person, person)
178
+ stub(person).ability { :some_ability }
179
+ resource = CanCan::ControllerResource.new(@controller, :through => :person, :singleton => true)
180
+ resource.load_resource
41
181
  @controller.instance_variable_get(:@ability).should == :some_ability
42
182
  end
43
183
 
44
- it "should use the model class option if provided" do
184
+ it "should build record through has_one association with :singleton option" do
185
+ @params.merge!(:action => "create", :ability => :ability_attributes)
186
+ person = Object.new
187
+ @controller.instance_variable_set(:@person, person)
188
+ stub(person).build_ability(:ability_attributes) { :new_ability }
189
+ resource = CanCan::ControllerResource.new(@controller, :through => :person, :singleton => true)
190
+ resource.load_resource
191
+ @controller.instance_variable_get(:@ability).should == :new_ability
192
+ end
193
+
194
+ it "should only authorize :read action on parent resource" do
195
+ @params.merge!(:action => "new", :person_id => 123)
196
+ stub(Person).find(123) { :some_person }
197
+ stub(@controller).authorize!(:read, :some_person) { raise CanCan::AccessDenied }
198
+ resource = CanCan::ControllerResource.new(@controller, :person)
199
+ lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
200
+ end
201
+
202
+ it "should load the model using a custom class" do
203
+ @params.merge!(:action => "show", :id => 123)
45
204
  stub(Person).find(123) { :some_resource }
46
- CanCan::ControllerResource.new(@controller, :ability, nil, :resource => Person).find(123)
205
+ resource = CanCan::ControllerResource.new(@controller, :class => Person)
206
+ resource.load_resource
47
207
  @controller.instance_variable_get(:@ability).should == :some_resource
48
208
  end
49
209
 
50
- it "should convert string to constant for resource" do
51
- CanCan::ControllerResource.new(@controller, :ability, nil, :resource => "Person").model_class.should == Person
210
+ it "should authorize based on resource name if class is false" do
211
+ @params.merge!(:action => "show", :id => 123)
212
+ stub(@controller).authorize!(:show, :ability) { raise CanCan::AccessDenied }
213
+ resource = CanCan::ControllerResource.new(@controller, :class => false)
214
+ lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
215
+ end
216
+
217
+ it "should load and authorize using custom instance name" do
218
+ @params.merge!(:action => "show", :id => 123)
219
+ stub(Ability).find(123) { :some_ability }
220
+ stub(@controller).authorize!(:show, :some_ability) { raise CanCan::AccessDenied }
221
+ resource = CanCan::ControllerResource.new(@controller, :instance_name => :custom_ability)
222
+ lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
223
+ @controller.instance_variable_get(:@custom_ability).should == :some_ability
224
+ end
225
+
226
+ it "should load resource using custom find_by attribute" do
227
+ @params.merge!(:action => "show", :id => 123)
228
+ stub(Ability).find_by_name!(123) { :some_resource }
229
+ resource = CanCan::ControllerResource.new(@controller, :find_by => :name)
230
+ resource.load_resource
231
+ @controller.instance_variable_get(:@ability).should == :some_resource
232
+ end
233
+
234
+ it "should raise ImplementationRemoved when adding :name option" do
235
+ lambda {
236
+ CanCan::ControllerResource.new(@controller, :name => :foo)
237
+ }.should raise_error(CanCan::ImplementationRemoved)
238
+ end
239
+
240
+ it "should raise ImplementationRemoved exception when specifying :resource option since it is no longer used" do
241
+ lambda {
242
+ CanCan::ControllerResource.new(@controller, :resource => Person)
243
+ }.should raise_error(CanCan::ImplementationRemoved)
52
244
  end
53
245
 
54
- it "should raise an exception when specifying :class option since it is no longer used" do
246
+ it "should raise ImplementationRemoved exception when passing :nested option" do
55
247
  lambda {
56
- CanCan::ControllerResource.new(@controller, :ability, nil, :class => Person)
248
+ CanCan::ControllerResource.new(@controller, :nested => :person)
57
249
  }.should raise_error(CanCan::ImplementationRemoved)
58
250
  end
59
251
  end
@@ -0,0 +1,107 @@
1
+ require "spec_helper"
2
+
3
+ describe CanCan::Query do
4
+ before(:each) do
5
+ @ability = Object.new
6
+ @ability.extend(CanCan::Ability)
7
+ end
8
+
9
+ it "should have false conditions if no abilities match" do
10
+ @ability.query(:destroy, Person).conditions.should == "true=false"
11
+ end
12
+
13
+ it "should return hash for single `can` definition" do
14
+ @ability.can :read, Person, :blocked => false, :user_id => 1
15
+ @ability.query(:read, Person).conditions.should == { :blocked => false, :user_id => 1 }
16
+ end
17
+
18
+ it "should merge multiple can definitions into single SQL string joining with OR" do
19
+ @ability.can :read, Person, :blocked => false
20
+ @ability.can :read, Person, :admin => true
21
+ @ability.query(:read, Person).conditions.should == "(admin=true) OR (blocked=false)"
22
+ end
23
+
24
+ it "should merge multiple can definitions into single SQL string joining with OR and AND" do
25
+ @ability.can :read, Person, :blocked => false, :active => true
26
+ @ability.can :read, Person, :admin => true
27
+ @ability.query(:read, Person).conditions.should orderlessly_match("(blocked=false AND active=true) OR (admin=true)")
28
+ end
29
+
30
+ it "should merge multiple can definitions into single SQL string joining with OR and AND" do
31
+ @ability.can :read, Person, :blocked => false, :active => true
32
+ @ability.can :read, Person, :admin => true
33
+ @ability.query(:read, Person).conditions.should orderlessly_match("(blocked=false AND active=true) OR (admin=true)")
34
+ end
35
+
36
+ it "should return false conditions for cannot clause" do
37
+ @ability.cannot :read, Person
38
+ @ability.query(:read, Person).conditions.should == "true=false"
39
+ end
40
+
41
+ it "should return SQL for single `can` definition in front of default `cannot` condition" do
42
+ @ability.cannot :read, Person
43
+ @ability.can :read, Person, :blocked => false, :user_id => 1
44
+ @ability.query(:read, Person).conditions.should orderlessly_match("blocked=false AND user_id=1")
45
+ end
46
+
47
+ it "should return true condition for single `can` definition in front of default `can` condition" do
48
+ @ability.can :read, Person
49
+ @ability.can :read, Person, :blocked => false, :user_id => 1
50
+ @ability.query(:read, Person).conditions.should == 'true=true'
51
+ end
52
+
53
+ it "should return false condition for single `cannot` definition" do
54
+ @ability.cannot :read, Person, :blocked => true, :user_id => 1
55
+ @ability.query(:read, Person).conditions.should == 'true=false'
56
+ end
57
+
58
+ it "should return `false condition` for single `cannot` definition in front of default `cannot` condition" do
59
+ @ability.cannot :read, Person
60
+ @ability.cannot :read, Person, :blocked => true, :user_id => 1
61
+ @ability.query(:read, Person).conditions.should == 'true=false'
62
+ end
63
+
64
+ it "should return `not (sql)` for single `cannot` definition in front of default `can` condition" do
65
+ @ability.can :read, Person
66
+ @ability.cannot :read, Person, :blocked => true, :user_id => 1
67
+ @ability.query(:read, Person).conditions.should orderlessly_match("not (blocked=true AND user_id=1)")
68
+ end
69
+
70
+ it "should return appropriate sql conditions in complex case" do
71
+ @ability.can :read, Person
72
+ @ability.can :manage, Person, :id => 1
73
+ @ability.can :update, Person, :manager_id => 1
74
+ @ability.cannot :update, Person, :self_managed => true
75
+ @ability.query(:update, Person).conditions.should == 'not (self_managed=true) AND ((manager_id=1) OR (id=1))'
76
+ @ability.query(:manage, Person).conditions.should == {:id=>1}
77
+ @ability.query(:read, Person).conditions.should == 'true=true'
78
+ end
79
+
80
+ it "should have nil joins if no can definitions" do
81
+ @ability.query(:read, Person).joins.should be_nil
82
+ end
83
+
84
+ it "should have nil joins if no nested hashes specified in conditions" do
85
+ @ability.can :read, Person, :blocked => false
86
+ @ability.can :read, Person, :admin => true
87
+ @ability.query(:read, Person).joins.should be_nil
88
+ end
89
+
90
+ it "should merge separate joins into a single array" do
91
+ @ability.can :read, Person, :project => { :blocked => false }
92
+ @ability.can :read, Person, :company => { :admin => true }
93
+ @ability.query(:read, Person).joins.inspect.should orderlessly_match([:company, :project].inspect)
94
+ end
95
+
96
+ it "should merge same joins into a single array" do
97
+ @ability.can :read, Person, :project => { :blocked => false }
98
+ @ability.can :read, Person, :project => { :admin => true }
99
+ @ability.query(:read, Person).joins.should == [:project]
100
+ end
101
+
102
+ it "should merge complex, nested joins" do
103
+ @ability.can :read, Person, :project => { :bar => {:test => true} }, :company => { :bar => {:test => true} }
104
+ @ability.can :read, Person, :project => { :foo => {:bar => true}, :bar => {:zip => :zap} }
105
+ @ability.query(:read, Person).joins.inspect.should orderlessly_match([{:project => [:bar, :foo]}, {:company => [:bar]}].inspect)
106
+ end
107
+ end