cancan 1.2.0 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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