cancancan 1.15.0 → 1.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +38 -0
- data/.rubocop_todo.yml +48 -0
- data/.travis.yml +8 -2
- data/Appraisals +1 -0
- data/CHANGELOG.rdoc +5 -0
- data/Gemfile +1 -1
- data/README.md +58 -41
- data/Rakefile +7 -3
- data/cancancan.gemspec +13 -12
- data/gemfiles/activerecord_4.2.gemfile +1 -0
- data/lib/cancan.rb +2 -2
- data/lib/cancan/ability.rb +26 -24
- data/lib/cancan/controller_additions.rb +33 -23
- data/lib/cancan/controller_resource.rb +83 -56
- data/lib/cancan/exceptions.rb +1 -1
- data/lib/cancan/matchers.rb +2 -2
- data/lib/cancan/model_adapters/abstract_adapter.rb +8 -8
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +48 -35
- data/lib/cancan/model_adapters/active_record_adapter.rb +18 -17
- data/lib/cancan/model_adapters/mongoid_adapter.rb +26 -21
- data/lib/cancan/model_adapters/sequel_adapter.rb +12 -12
- data/lib/cancan/model_additions.rb +0 -1
- data/lib/cancan/rule.rb +23 -17
- data/lib/cancan/version.rb +1 -1
- data/lib/generators/cancan/ability/ability_generator.rb +1 -1
- data/spec/cancan/ability_spec.rb +189 -180
- data/spec/cancan/controller_additions_spec.rb +77 -64
- data/spec/cancan/controller_resource_spec.rb +230 -228
- data/spec/cancan/exceptions_spec.rb +20 -20
- data/spec/cancan/inherited_resource_spec.rb +21 -21
- data/spec/cancan/matchers_spec.rb +12 -12
- data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +38 -32
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +155 -145
- data/spec/cancan/model_adapters/default_adapter_spec.rb +2 -2
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +87 -88
- data/spec/cancan/model_adapters/sequel_adapter_spec.rb +44 -47
- data/spec/cancan/rule_spec.rb +18 -18
- data/spec/spec_helper.rb +2 -2
- data/spec/support/ability.rb +0 -1
- metadata +60 -19
@@ -1,58 +1,58 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe CanCan::AccessDenied do
|
4
|
-
describe
|
4
|
+
describe 'with action and subject' do
|
5
5
|
before(:each) do
|
6
6
|
@exception = CanCan::AccessDenied.new(nil, :some_action, :some_subject)
|
7
7
|
end
|
8
8
|
|
9
|
-
it
|
9
|
+
it 'has action and subject accessors' do
|
10
10
|
expect(@exception.action).to eq(:some_action)
|
11
11
|
expect(@exception.subject).to eq(:some_subject)
|
12
12
|
end
|
13
13
|
|
14
|
-
it
|
15
|
-
expect(@exception.message).to eq(
|
16
|
-
@exception.default_message =
|
17
|
-
expect(@exception.message).to eq(
|
14
|
+
it 'has a changable default message' do
|
15
|
+
expect(@exception.message).to eq('You are not authorized to access this page.')
|
16
|
+
@exception.default_message = 'Unauthorized!'
|
17
|
+
expect(@exception.message).to eq('Unauthorized!')
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
describe
|
21
|
+
describe 'with only a message' do
|
22
22
|
before(:each) do
|
23
|
-
@exception = CanCan::AccessDenied.new(
|
23
|
+
@exception = CanCan::AccessDenied.new('Access denied!')
|
24
24
|
end
|
25
25
|
|
26
|
-
it
|
26
|
+
it 'has nil action and subject' do
|
27
27
|
expect(@exception.action).to be_nil
|
28
28
|
expect(@exception.subject).to be_nil
|
29
29
|
end
|
30
30
|
|
31
|
-
it
|
32
|
-
expect(@exception.message).to eq(
|
31
|
+
it 'has passed message' do
|
32
|
+
expect(@exception.message).to eq('Access denied!')
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
describe
|
36
|
+
describe 'i18n in the default message' do
|
37
37
|
after(:each) do
|
38
38
|
I18n.backend = nil
|
39
39
|
end
|
40
40
|
|
41
|
-
it
|
42
|
-
I18n.backend.store_translations :en, :
|
41
|
+
it 'uses i18n for the default message' do
|
42
|
+
I18n.backend.store_translations :en, unauthorized: { default: 'This is a different message' }
|
43
43
|
@exception = CanCan::AccessDenied.new
|
44
|
-
expect(@exception.message).to eq(
|
44
|
+
expect(@exception.message).to eq('This is a different message')
|
45
45
|
end
|
46
46
|
|
47
|
-
it
|
47
|
+
it 'defaults to a nice message' do
|
48
48
|
@exception = CanCan::AccessDenied.new
|
49
|
-
expect(@exception.message).to eq(
|
49
|
+
expect(@exception.message).to eq('You are not authorized to access this page.')
|
50
50
|
end
|
51
51
|
|
52
|
-
it
|
52
|
+
it 'does not use translation if a message is given' do
|
53
53
|
@exception = CanCan::AccessDenied.new("Hey! You're not welcome here")
|
54
54
|
expect(@exception.message).to eq("Hey! You're not welcome here")
|
55
|
-
expect(@exception.message).to_not eq(
|
55
|
+
expect(@exception.message).to_not eq('You are not authorized to access this page.')
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe CanCan::InheritedResource do
|
4
4
|
let(:ability) { Ability.new(nil) }
|
5
|
-
let(:params) { HashWithIndifferentAccess.new(:
|
5
|
+
let(:params) { HashWithIndifferentAccess.new(controller: 'models') }
|
6
6
|
let(:controller_class) { Class.new }
|
7
7
|
let(:controller) { controller_class.new }
|
8
8
|
|
@@ -10,7 +10,7 @@ describe CanCan::InheritedResource do
|
|
10
10
|
class Model
|
11
11
|
attr_accessor :name
|
12
12
|
|
13
|
-
def initialize(attributes={})
|
13
|
+
def initialize(attributes = {})
|
14
14
|
attributes.each do |attribute, value|
|
15
15
|
send("#{attribute}=", value)
|
16
16
|
end
|
@@ -19,53 +19,53 @@ describe CanCan::InheritedResource do
|
|
19
19
|
|
20
20
|
allow(controller).to receive(:params) { params }
|
21
21
|
allow(controller).to receive(:current_ability) { ability }
|
22
|
-
allow(controller_class).to receive(:cancan_skipper) { {:
|
22
|
+
allow(controller_class).to receive(:cancan_skipper) { { authorize: {}, load: {} } }
|
23
23
|
end
|
24
24
|
|
25
|
-
it
|
26
|
-
params.merge!(:
|
25
|
+
it 'show loads resource through controller.resource' do
|
26
|
+
params.merge!(action: 'show', id: 123)
|
27
27
|
allow(controller).to receive(:resource) { :model_resource }
|
28
28
|
CanCan::InheritedResource.new(controller).load_resource
|
29
29
|
expect(controller.instance_variable_get(:@model)).to eq(:model_resource)
|
30
30
|
end
|
31
31
|
|
32
|
-
it
|
33
|
-
params[:action] =
|
32
|
+
it 'new loads through controller.build_resource' do
|
33
|
+
params[:action] = 'new'
|
34
34
|
allow(controller).to receive(:build_resource) { :model_resource }
|
35
35
|
CanCan::InheritedResource.new(controller).load_resource
|
36
36
|
expect(controller.instance_variable_get(:@model)).to eq(:model_resource)
|
37
37
|
end
|
38
38
|
|
39
|
-
it
|
40
|
-
params[:action] =
|
39
|
+
it 'index loads through controller.association_chain when parent' do
|
40
|
+
params[:action] = 'index'
|
41
41
|
allow(controller).to receive(:association_chain) { controller.instance_variable_set(:@model, :model_resource) }
|
42
|
-
CanCan::InheritedResource.new(controller, :
|
42
|
+
CanCan::InheritedResource.new(controller, parent: true).load_resource
|
43
43
|
expect(controller.instance_variable_get(:@model)).to eq(:model_resource)
|
44
44
|
end
|
45
45
|
|
46
|
-
it
|
47
|
-
params[:action] =
|
46
|
+
it 'index loads through controller.end_of_association_chain' do
|
47
|
+
params[:action] = 'index'
|
48
48
|
allow(Model).to receive(:accessible_by).with(ability, :index) { :projects }
|
49
49
|
allow(controller).to receive(:end_of_association_chain) { Model }
|
50
50
|
CanCan::InheritedResource.new(controller).load_resource
|
51
51
|
expect(controller.instance_variable_get(:@models)).to eq(:projects)
|
52
52
|
end
|
53
53
|
|
54
|
-
it
|
55
|
-
params[:action] =
|
56
|
-
ability.can(:create, Model, :
|
54
|
+
it 'builds a new resource with attributes from current ability' do
|
55
|
+
params[:action] = 'new'
|
56
|
+
ability.can(:create, Model, name: 'from conditions')
|
57
57
|
allow(controller).to receive(:build_resource) { Struct.new(:name).new }
|
58
58
|
resource = CanCan::InheritedResource.new(controller)
|
59
59
|
resource.load_resource
|
60
|
-
expect(controller.instance_variable_get(:@model).name).to eq(
|
60
|
+
expect(controller.instance_variable_get(:@model).name).to eq('from conditions')
|
61
61
|
end
|
62
62
|
|
63
|
-
it
|
64
|
-
params.merge!(:
|
65
|
-
ability.can(:create, Model, :
|
63
|
+
it 'overrides initial attributes with params' do
|
64
|
+
params.merge!(action: 'new', model: { name: 'from params' })
|
65
|
+
ability.can(:create, Model, name: 'from conditions')
|
66
66
|
allow(controller).to receive(:build_resource) { Struct.new(:name).new }
|
67
67
|
resource = CanCan::ControllerResource.new(controller)
|
68
68
|
resource.load_resource
|
69
|
-
expect(controller.instance_variable_get(:@model).name).to eq(
|
69
|
+
expect(controller.instance_variable_get(:@model).name).to eq('from params')
|
70
70
|
end
|
71
71
|
end
|
@@ -1,29 +1,29 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
4
|
-
it
|
3
|
+
describe 'be_able_to' do
|
4
|
+
it 'delegates to can?' do
|
5
5
|
expect(object = double).to receive(:can?).with(:read, 123) { true }
|
6
6
|
expect(object).to be_able_to(:read, 123)
|
7
7
|
end
|
8
8
|
|
9
|
-
it
|
9
|
+
it 'reports a nice failure message for should' do
|
10
10
|
expect(object = double).to receive(:can?).with(:read, 123) { false }
|
11
|
-
expect
|
11
|
+
expect do
|
12
12
|
expect(object).to be_able_to(:read, 123)
|
13
|
-
|
13
|
+
end.to raise_error('expected to be able to :read 123')
|
14
14
|
end
|
15
15
|
|
16
|
-
it
|
16
|
+
it 'reports a nice failure message for should not' do
|
17
17
|
expect(object = double).to receive(:can?).with(:read, 123) { true }
|
18
|
-
expect
|
18
|
+
expect do
|
19
19
|
expect(object).to_not be_able_to(:read, 123)
|
20
|
-
|
20
|
+
end.to raise_error('expected not to be able to :read 123')
|
21
21
|
end
|
22
22
|
|
23
|
-
it
|
23
|
+
it 'delegates additional arguments to can? and reports in failure message' do
|
24
24
|
expect(object = double).to receive(:can?).with(:read, 123, 456) { false }
|
25
|
-
expect
|
25
|
+
expect do
|
26
26
|
expect(object).to be_able_to(:read, 123, 456)
|
27
|
-
|
27
|
+
end.to raise_error('expected to be able to :read 123 456')
|
28
28
|
end
|
29
29
|
end
|
@@ -1,24 +1,24 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
|
4
4
|
describe CanCan::ModelAdapters::ActiveRecord4Adapter do
|
5
5
|
context 'with sqlite3' do
|
6
6
|
before :each do
|
7
|
-
ActiveRecord::Base.establish_connection(:
|
7
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
8
8
|
ActiveRecord::Migration.verbose = false
|
9
9
|
ActiveRecord::Schema.define do
|
10
10
|
create_table(:parents) do |t|
|
11
|
-
t.timestamps :
|
11
|
+
t.timestamps null: false
|
12
12
|
end
|
13
13
|
|
14
14
|
create_table(:children) do |t|
|
15
|
-
t.timestamps :
|
15
|
+
t.timestamps null: false
|
16
16
|
t.integer :parent_id
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
class Parent < ActiveRecord::Base
|
21
|
-
has_many :children,
|
21
|
+
has_many :children, -> { order(id: :desc) }
|
22
22
|
end
|
23
23
|
|
24
24
|
class Child < ActiveRecord::Base
|
@@ -28,18 +28,19 @@ if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
|
|
28
28
|
(@ability = double).extend(CanCan::Ability)
|
29
29
|
end
|
30
30
|
|
31
|
-
it
|
31
|
+
it 'respects scope on included associations' do
|
32
32
|
@ability.can :read, [Parent, Child]
|
33
33
|
|
34
34
|
parent = Parent.create!
|
35
|
-
child1 = Child.create!(:
|
36
|
-
child2 = Child.create!(:
|
35
|
+
child1 = Child.create!(parent: parent, created_at: 1.hours.ago)
|
36
|
+
child2 = Child.create!(parent: parent, created_at: 2.hours.ago)
|
37
37
|
|
38
|
-
expect(Parent.accessible_by(@ability).order(:
|
38
|
+
expect(Parent.accessible_by(@ability).order(created_at: :asc).includes(:children).first.children)
|
39
|
+
.to eq [child2, child1]
|
39
40
|
end
|
40
41
|
|
41
42
|
if ActiveRecord::VERSION::MINOR >= 1
|
42
|
-
it
|
43
|
+
it 'allows filters on enums' do
|
43
44
|
ActiveRecord::Schema.define do
|
44
45
|
create_table(:shapes) do |t|
|
45
46
|
t.integer :color, default: 0, null: false
|
@@ -57,9 +58,9 @@ if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
|
|
57
58
|
# A condition with a single value.
|
58
59
|
@ability.can :read, Shape, color: Shape.colors[:green]
|
59
60
|
|
60
|
-
expect(@ability.cannot?
|
61
|
-
expect(@ability.can?
|
62
|
-
expect(@ability.cannot?
|
61
|
+
expect(@ability.cannot?(:read, red)).to be true
|
62
|
+
expect(@ability.can?(:read, green)).to be true
|
63
|
+
expect(@ability.cannot?(:read, blue)).to be true
|
63
64
|
|
64
65
|
accessible = Shape.accessible_by(@ability)
|
65
66
|
expect(accessible).to contain_exactly(green)
|
@@ -68,15 +69,15 @@ if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
|
|
68
69
|
@ability.can :update, Shape, color: [Shape.colors[:red],
|
69
70
|
Shape.colors[:blue]]
|
70
71
|
|
71
|
-
expect(@ability.can?
|
72
|
-
expect(@ability.cannot?
|
73
|
-
expect(@ability.can?
|
72
|
+
expect(@ability.can?(:update, red)).to be true
|
73
|
+
expect(@ability.cannot?(:update, green)).to be true
|
74
|
+
expect(@ability.can?(:update, blue)).to be true
|
74
75
|
|
75
76
|
accessible = Shape.accessible_by(@ability, :update)
|
76
77
|
expect(accessible).to contain_exactly(red, blue)
|
77
78
|
end
|
78
79
|
|
79
|
-
it
|
80
|
+
it 'allows dual filter on enums' do
|
80
81
|
ActiveRecord::Schema.define do
|
81
82
|
create_table(:discs) do |t|
|
82
83
|
t.integer :color, default: 0, null: false
|
@@ -97,10 +98,10 @@ if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
|
|
97
98
|
# A condition with a dual filter.
|
98
99
|
@ability.can :read, Disc, color: Disc.colors[:green], shape: Disc.shapes[:rectangle]
|
99
100
|
|
100
|
-
expect(@ability.cannot?
|
101
|
-
expect(@ability.cannot?
|
102
|
-
expect(@ability.can?
|
103
|
-
expect(@ability.cannot?
|
101
|
+
expect(@ability.cannot?(:read, red_triangle)).to be true
|
102
|
+
expect(@ability.cannot?(:read, green_triangle)).to be true
|
103
|
+
expect(@ability.can?(:read, green_rectangle)).to be true
|
104
|
+
expect(@ability.cannot?(:read, blue_rectangle)).to be true
|
104
105
|
|
105
106
|
accessible = Disc.accessible_by(@ability)
|
106
107
|
expect(accessible).to contain_exactly(green_rectangle)
|
@@ -111,24 +112,29 @@ if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
|
|
111
112
|
if Gem::Specification.find_all_by_name('pg').any?
|
112
113
|
context 'with postgresql' do
|
113
114
|
before :each do
|
114
|
-
ActiveRecord::Base.establish_connection(
|
115
|
+
ActiveRecord::Base.establish_connection(adapter: 'postgresql',
|
116
|
+
database: 'postgres',
|
117
|
+
schema_search_path: 'public')
|
115
118
|
ActiveRecord::Base.connection.drop_database('cancan_postgresql_spec')
|
116
|
-
ActiveRecord::Base.connection.create_database
|
117
|
-
|
119
|
+
ActiveRecord::Base.connection.create_database('cancan_postgresql_spec',
|
120
|
+
'encoding' => 'utf-8',
|
121
|
+
'adapter' => 'postgresql')
|
122
|
+
ActiveRecord::Base.establish_connection(adapter: 'postgresql',
|
123
|
+
database: 'cancan_postgresql_spec')
|
118
124
|
ActiveRecord::Migration.verbose = false
|
119
125
|
ActiveRecord::Schema.define do
|
120
126
|
create_table(:parents) do |t|
|
121
|
-
t.timestamps :
|
127
|
+
t.timestamps null: false
|
122
128
|
end
|
123
129
|
|
124
130
|
create_table(:children) do |t|
|
125
|
-
t.timestamps :
|
131
|
+
t.timestamps null: false
|
126
132
|
t.integer :parent_id
|
127
133
|
end
|
128
134
|
end
|
129
135
|
|
130
136
|
class Parent < ActiveRecord::Base
|
131
|
-
has_many :children,
|
137
|
+
has_many :children, -> { order(id: :desc) }
|
132
138
|
end
|
133
139
|
|
134
140
|
class Child < ActiveRecord::Base
|
@@ -138,13 +144,13 @@ if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
|
|
138
144
|
(@ability = double).extend(CanCan::Ability)
|
139
145
|
end
|
140
146
|
|
141
|
-
it
|
142
|
-
@ability.can :read, Parent, :
|
143
|
-
@ability.can :read, Parent, :
|
147
|
+
it 'allows overlapping conditions in SQL and merge with hash conditions' do
|
148
|
+
@ability.can :read, Parent, children: { parent_id: 1 }
|
149
|
+
@ability.can :read, Parent, children: { parent_id: 1 }
|
144
150
|
|
145
151
|
parent = Parent.create!
|
146
|
-
|
147
|
-
|
152
|
+
Child.create!(parent: parent, created_at: 1.hours.ago)
|
153
|
+
Child.create!(parent: parent, created_at: 2.hours.ago)
|
148
154
|
|
149
155
|
expect(Parent.accessible_by(@ability)).to eq([parent])
|
150
156
|
end
|
@@ -1,27 +1,26 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
4
4
|
|
5
5
|
describe CanCan::ModelAdapters::ActiveRecordAdapter do
|
6
|
-
|
7
6
|
before :each do
|
8
|
-
ActiveRecord::Base.establish_connection(:
|
7
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
9
8
|
ActiveRecord::Migration.verbose = false
|
10
9
|
ActiveRecord::Schema.define do
|
11
10
|
create_table(:categories) do |t|
|
12
11
|
t.string :name
|
13
12
|
t.boolean :visible
|
14
|
-
t.timestamps :
|
13
|
+
t.timestamps null: false
|
15
14
|
end
|
16
15
|
|
17
16
|
create_table(:projects) do |t|
|
18
17
|
t.string :name
|
19
|
-
t.timestamps :
|
18
|
+
t.timestamps null: false
|
20
19
|
end
|
21
20
|
|
22
21
|
create_table(:articles) do |t|
|
23
22
|
t.string :name
|
24
|
-
t.timestamps :
|
23
|
+
t.timestamps null: false
|
25
24
|
t.boolean :published
|
26
25
|
t.boolean :secret
|
27
26
|
t.integer :priority
|
@@ -32,17 +31,17 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
|
32
31
|
create_table(:comments) do |t|
|
33
32
|
t.boolean :spam
|
34
33
|
t.integer :article_id
|
35
|
-
t.timestamps :
|
34
|
+
t.timestamps null: false
|
36
35
|
end
|
37
36
|
|
38
37
|
create_table(:legacy_mentions) do |t|
|
39
38
|
t.integer :user_id
|
40
39
|
t.integer :article_id
|
41
|
-
t.timestamps :
|
40
|
+
t.timestamps null: false
|
42
41
|
end
|
43
42
|
|
44
43
|
create_table(:users) do |t|
|
45
|
-
t.timestamps :
|
44
|
+
t.timestamps null: false
|
46
45
|
end
|
47
46
|
end
|
48
47
|
|
@@ -57,7 +56,7 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
|
57
56
|
belongs_to :category
|
58
57
|
has_many :comments
|
59
58
|
has_many :mentions
|
60
|
-
has_many :mentioned_users, :
|
59
|
+
has_many :mentioned_users, through: :mentions, source: :user
|
61
60
|
belongs_to :user
|
62
61
|
end
|
63
62
|
|
@@ -80,259 +79,270 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
|
80
79
|
@comment_table = Comment.table_name
|
81
80
|
end
|
82
81
|
|
83
|
-
it
|
82
|
+
it 'is for only active record classes' do
|
84
83
|
if ActiveRecord.respond_to?(:version) &&
|
85
|
-
|
84
|
+
ActiveRecord.version > Gem::Version.new('4')
|
86
85
|
expect(CanCan::ModelAdapters::ActiveRecord4Adapter).to_not be_for_class(Object)
|
87
86
|
expect(CanCan::ModelAdapters::ActiveRecord4Adapter).to be_for_class(Article)
|
88
|
-
expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article))
|
87
|
+
expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article))
|
88
|
+
.to eq(CanCan::ModelAdapters::ActiveRecord4Adapter)
|
89
89
|
else
|
90
90
|
expect(CanCan::ModelAdapters::ActiveRecord3Adapter).to_not be_for_class(Object)
|
91
91
|
expect(CanCan::ModelAdapters::ActiveRecord3Adapter).to be_for_class(Article)
|
92
|
-
expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article))
|
92
|
+
expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article))
|
93
|
+
.to eq(CanCan::ModelAdapters::ActiveRecord3Adapter)
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
96
|
-
it
|
97
|
+
it 'finds record' do
|
97
98
|
article = Article.create!
|
98
99
|
adapter = CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article)
|
99
100
|
expect(adapter.find(Article, article.id)).to eq(article)
|
100
101
|
end
|
101
102
|
|
102
|
-
it
|
103
|
+
it 'does not fetch any records when no abilities are defined' do
|
103
104
|
Article.create!
|
104
105
|
expect(Article.accessible_by(@ability)).to be_empty
|
105
106
|
end
|
106
107
|
|
107
|
-
it
|
108
|
+
it 'fetches all articles when one can read all' do
|
108
109
|
@ability.can :read, Article
|
109
110
|
article = Article.create!
|
110
111
|
expect(Article.accessible_by(@ability)).to eq([article])
|
111
112
|
end
|
112
113
|
|
113
|
-
it
|
114
|
-
@ability.can :read, Article, :
|
115
|
-
article1 = Article.create!(:
|
116
|
-
|
114
|
+
it 'fetches only the articles that are published' do
|
115
|
+
@ability.can :read, Article, published: true
|
116
|
+
article1 = Article.create!(published: true)
|
117
|
+
Article.create!(published: false)
|
117
118
|
expect(Article.accessible_by(@ability)).to eq([article1])
|
118
119
|
end
|
119
120
|
|
120
|
-
it
|
121
|
-
@ability.can :read, Article, :
|
122
|
-
@ability.can :read, Article, :
|
123
|
-
article1 = Article.create!(:
|
124
|
-
article2 = Article.create!(:
|
125
|
-
article3 = Article.create!(:
|
126
|
-
|
121
|
+
it 'fetches any articles which are published or secret' do
|
122
|
+
@ability.can :read, Article, published: true
|
123
|
+
@ability.can :read, Article, secret: true
|
124
|
+
article1 = Article.create!(published: true, secret: false)
|
125
|
+
article2 = Article.create!(published: true, secret: true)
|
126
|
+
article3 = Article.create!(published: false, secret: true)
|
127
|
+
Article.create!(published: false, secret: false)
|
127
128
|
expect(Article.accessible_by(@ability)).to eq([article1, article2, article3])
|
128
129
|
end
|
129
130
|
|
130
|
-
it
|
131
|
+
it 'fetches any articles which we are cited in' do
|
131
132
|
user = User.create!
|
132
133
|
cited = Article.create!
|
133
|
-
|
134
|
+
Article.create!
|
134
135
|
cited.mentioned_users << user
|
135
|
-
@ability.can :read, Article,
|
136
|
-
@ability.can :read, Article,
|
136
|
+
@ability.can :read, Article, mentioned_users: { id: user.id }
|
137
|
+
@ability.can :read, Article, mentions: { user_id: user.id }
|
137
138
|
expect(Article.accessible_by(@ability)).to eq([cited])
|
138
139
|
end
|
139
140
|
|
140
|
-
it
|
141
|
-
@ability.can :read, Article, :
|
142
|
-
@ability.cannot :read, Article, :
|
143
|
-
article1 = Article.create!(:
|
144
|
-
|
145
|
-
|
146
|
-
|
141
|
+
it 'fetches only the articles that are published and not secret' do
|
142
|
+
@ability.can :read, Article, published: true
|
143
|
+
@ability.cannot :read, Article, secret: true
|
144
|
+
article1 = Article.create!(published: true, secret: false)
|
145
|
+
Article.create!(published: true, secret: true)
|
146
|
+
Article.create!(published: false, secret: true)
|
147
|
+
Article.create!(published: false, secret: false)
|
147
148
|
expect(Article.accessible_by(@ability)).to eq([article1])
|
148
149
|
end
|
149
150
|
|
150
|
-
it
|
151
|
-
@ability.can :read, Comment, :
|
152
|
-
comment1 = Comment.create!(:
|
153
|
-
|
151
|
+
it 'only reads comments for articles which are published' do
|
152
|
+
@ability.can :read, Comment, article: { published: true }
|
153
|
+
comment1 = Comment.create!(article: Article.create!(published: true))
|
154
|
+
Comment.create!(article: Article.create!(published: false))
|
154
155
|
expect(Comment.accessible_by(@ability)).to eq([comment1])
|
155
156
|
end
|
156
157
|
|
157
|
-
it
|
158
|
-
@ability.can :read, Article, :
|
159
|
-
@ability.can :read, Article, :
|
160
|
-
article1 = Article.create!(:
|
161
|
-
|
162
|
-
article3 = Article.create!(:
|
158
|
+
it 'should only read articles which are published or in visible categories' do
|
159
|
+
@ability.can :read, Article, category: { visible: true }
|
160
|
+
@ability.can :read, Article, published: true
|
161
|
+
article1 = Article.create!(published: true)
|
162
|
+
Article.create!(published: false)
|
163
|
+
article3 = Article.create!(published: false, category: Category.create!(visible: true))
|
163
164
|
expect(Article.accessible_by(@ability)).to eq([article1, article3])
|
164
165
|
end
|
165
166
|
|
166
|
-
it
|
167
|
-
@ability.can :read, Category, :
|
168
|
-
@ability.can :read, Article, :
|
167
|
+
it 'should only read categories once even if they have multiple articles' do
|
168
|
+
@ability.can :read, Category, articles: { published: true }
|
169
|
+
@ability.can :read, Article, published: true
|
169
170
|
category = Category.create!
|
170
|
-
Article.create!(:
|
171
|
-
Article.create!(:
|
171
|
+
Article.create!(published: true, category: category)
|
172
|
+
Article.create!(published: true, category: category)
|
172
173
|
expect(Category.accessible_by(@ability)).to eq([category])
|
173
174
|
end
|
174
175
|
|
175
|
-
it
|
176
|
-
@ability.can :read, Comment, :
|
177
|
-
comment1 = Comment.create!(:
|
178
|
-
|
176
|
+
it 'only reads comments for visible categories through articles' do
|
177
|
+
@ability.can :read, Comment, article: { category: { visible: true } }
|
178
|
+
comment1 = Comment.create!(article: Article.create!(category: Category.create!(visible: true)))
|
179
|
+
Comment.create!(article: Article.create!(category: Category.create!(visible: false)))
|
179
180
|
expect(Comment.accessible_by(@ability)).to eq([comment1])
|
180
181
|
end
|
181
182
|
|
182
|
-
it
|
183
|
-
@ability.can :read, Article, :
|
184
|
-
@ability.can :read, Article, [
|
185
|
-
article1 = Article.create!(:
|
186
|
-
article2 = Article.create!(:
|
187
|
-
article3 = Article.create!(:
|
188
|
-
|
183
|
+
it 'allows conditions in SQL and merge with hash conditions' do
|
184
|
+
@ability.can :read, Article, published: true
|
185
|
+
@ability.can :read, Article, ['secret=?', true]
|
186
|
+
article1 = Article.create!(published: true, secret: false)
|
187
|
+
article2 = Article.create!(published: true, secret: true)
|
188
|
+
article3 = Article.create!(published: false, secret: true)
|
189
|
+
Article.create!(published: false, secret: false)
|
189
190
|
expect(Article.accessible_by(@ability)).to eq([article1, article2, article3])
|
190
191
|
end
|
191
192
|
|
192
|
-
it
|
193
|
-
@ability.can :read, Article, Article.where(:
|
194
|
-
article1 = Article.create!(:
|
195
|
-
|
193
|
+
it 'allows a scope for conditions' do
|
194
|
+
@ability.can :read, Article, Article.where(secret: true)
|
195
|
+
article1 = Article.create!(secret: true)
|
196
|
+
Article.create!(secret: false)
|
196
197
|
expect(Article.accessible_by(@ability)).to eq([article1])
|
197
198
|
end
|
198
199
|
|
199
|
-
it
|
200
|
-
@ability.can :read, Article, Article.where(:
|
201
|
-
category1 = Category.create!(:
|
202
|
-
category2 = Category.create!(:
|
203
|
-
article1 = Article.create!(:
|
204
|
-
|
200
|
+
it 'fetches only associated records when using with a scope for conditions' do
|
201
|
+
@ability.can :read, Article, Article.where(secret: true)
|
202
|
+
category1 = Category.create!(visible: false)
|
203
|
+
category2 = Category.create!(visible: true)
|
204
|
+
article1 = Article.create!(secret: true, category: category1)
|
205
|
+
Article.create!(secret: true, category: category2)
|
205
206
|
expect(category1.articles.accessible_by(@ability)).to eq([article1])
|
206
207
|
end
|
207
208
|
|
208
|
-
it
|
209
|
-
@ability.can :read, Article, :
|
210
|
-
@ability.can :read, Article, Article.where(:
|
211
|
-
expect(
|
209
|
+
it 'raises an exception when trying to merge scope with other conditions' do
|
210
|
+
@ability.can :read, Article, published: true
|
211
|
+
@ability.can :read, Article, Article.where(secret: true)
|
212
|
+
expect(-> { Article.accessible_by(@ability) })
|
213
|
+
.to raise_error(CanCan::Error,
|
214
|
+
'Unable to merge an Active Record scope with other conditions. '\
|
215
|
+
'Instead use a hash or SQL for read Article ability.')
|
212
216
|
end
|
213
217
|
|
214
|
-
it
|
218
|
+
it 'does not allow to fetch records when ability with just block present' do
|
215
219
|
@ability.can :read, Article do
|
216
220
|
false
|
217
221
|
end
|
218
|
-
expect(
|
222
|
+
expect(-> { Article.accessible_by(@ability) }).to raise_error(CanCan::Error)
|
219
223
|
end
|
220
224
|
|
221
|
-
it
|
222
|
-
@ability.can :read, Comment, :
|
223
|
-
:
|
224
|
-
:
|
225
|
+
it 'should support more than one deeply nested conditions' do
|
226
|
+
@ability.can :read, Comment, article: {
|
227
|
+
category: {
|
228
|
+
name: 'foo', visible: true
|
225
229
|
}
|
226
230
|
}
|
227
231
|
expect { Comment.accessible_by(@ability) }.to_not raise_error
|
228
232
|
end
|
229
233
|
|
230
|
-
it
|
231
|
-
@ability.can :read, Article, [
|
232
|
-
expect(
|
234
|
+
it 'does not allow to check ability on object against SQL conditions without block' do
|
235
|
+
@ability.can :read, Article, ['secret=?', true]
|
236
|
+
expect(-> { @ability.can? :read, Article.new }).to raise_error(CanCan::Error)
|
233
237
|
end
|
234
238
|
|
235
|
-
it
|
239
|
+
it 'has false conditions if no abilities match' do
|
236
240
|
expect(@ability.model_adapter(Article, :read).conditions).to eq("'t'='f'")
|
237
241
|
end
|
238
242
|
|
239
|
-
it
|
243
|
+
it 'returns false conditions for cannot clause' do
|
240
244
|
@ability.cannot :read, Article
|
241
245
|
expect(@ability.model_adapter(Article, :read).conditions).to eq("'t'='f'")
|
242
246
|
end
|
243
247
|
|
244
|
-
it
|
248
|
+
it 'returns SQL for single `can` definition in front of default `cannot` condition' do
|
245
249
|
@ability.cannot :read, Article
|
246
|
-
@ability.can :read, Article, :
|
247
|
-
expect(@ability.model_adapter(Article, :read).conditions)
|
250
|
+
@ability.can :read, Article, published: false, secret: true
|
251
|
+
expect(@ability.model_adapter(Article, :read).conditions)
|
252
|
+
.to orderlessly_match(%("#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't'))
|
248
253
|
end
|
249
254
|
|
250
|
-
it
|
255
|
+
it 'returns true condition for single `can` definition in front of default `can` condition' do
|
251
256
|
@ability.can :read, Article
|
252
|
-
@ability.can :read, Article, :
|
257
|
+
@ability.can :read, Article, published: false, secret: true
|
253
258
|
expect(@ability.model_adapter(Article, :read).conditions).to eq("'t'='t'")
|
254
259
|
end
|
255
260
|
|
256
|
-
it
|
261
|
+
it 'returns `false condition` for single `cannot` definition in front of default `cannot` condition' do
|
257
262
|
@ability.cannot :read, Article
|
258
|
-
@ability.cannot :read, Article, :
|
263
|
+
@ability.cannot :read, Article, published: false, secret: true
|
259
264
|
expect(@ability.model_adapter(Article, :read).conditions).to eq("'t'='f'")
|
260
265
|
end
|
261
266
|
|
262
|
-
it
|
267
|
+
it 'returns `not (sql)` for single `cannot` definition in front of default `can` condition' do
|
263
268
|
@ability.can :read, Article
|
264
|
-
@ability.cannot :read, Article, :
|
265
|
-
expect(@ability.model_adapter(Article, :read).conditions)
|
269
|
+
@ability.cannot :read, Article, published: false, secret: true
|
270
|
+
expect(@ability.model_adapter(Article, :read).conditions)
|
271
|
+
.to orderlessly_match(%["not (#{@article_table}"."published" = 'f' AND "#{@article_table}"."secret" = 't')])
|
266
272
|
end
|
267
273
|
|
268
|
-
it
|
274
|
+
it 'returns appropriate sql conditions in complex case' do
|
269
275
|
@ability.can :read, Article
|
270
|
-
@ability.can :manage, Article, :
|
271
|
-
@ability.can :update, Article, :
|
272
|
-
@ability.cannot :update, Article, :
|
273
|
-
expect(@ability.model_adapter(Article, :update).conditions)
|
274
|
-
|
276
|
+
@ability.can :manage, Article, id: 1
|
277
|
+
@ability.can :update, Article, published: true
|
278
|
+
@ability.cannot :update, Article, secret: true
|
279
|
+
expect(@ability.model_adapter(Article, :update).conditions)
|
280
|
+
.to eq(%[not ("#{@article_table}"."secret" = 't') ] +
|
281
|
+
%[AND (("#{@article_table}"."published" = 't') ] +
|
282
|
+
%[OR ("#{@article_table}"."id" = 1))])
|
283
|
+
expect(@ability.model_adapter(Article, :manage).conditions).to eq(id: 1)
|
275
284
|
expect(@ability.model_adapter(Article, :read).conditions).to eq("'t'='t'")
|
276
285
|
end
|
277
286
|
|
278
|
-
it
|
279
|
-
@ability.can :read, Comment, :
|
280
|
-
expect(@ability.model_adapter(Comment, :read).conditions).to eq(
|
287
|
+
it 'returns appropriate sql conditions in complex case with nested joins' do
|
288
|
+
@ability.can :read, Comment, article: { category: { visible: true } }
|
289
|
+
expect(@ability.model_adapter(Comment, :read).conditions).to eq(Category.table_name.to_sym => { visible: true })
|
281
290
|
end
|
282
291
|
|
283
|
-
it
|
284
|
-
@ability.can :read, Comment, :
|
285
|
-
expect(@ability.model_adapter(Comment, :read).conditions)
|
292
|
+
it 'returns appropriate sql conditions in complex case with nested joins of different depth' do
|
293
|
+
@ability.can :read, Comment, article: { published: true, category: { visible: true } }
|
294
|
+
expect(@ability.model_adapter(Comment, :read).conditions)
|
295
|
+
.to eq(Article.table_name.to_sym => { published: true }, Category.table_name.to_sym => { visible: true })
|
286
296
|
end
|
287
297
|
|
288
|
-
it
|
289
|
-
@ability.can :read, Article, :
|
298
|
+
it 'does not forget conditions when calling with SQL string' do
|
299
|
+
@ability.can :read, Article, published: true
|
290
300
|
@ability.can :read, Article, ['secret=?', false]
|
291
301
|
adapter = @ability.model_adapter(Article, :read)
|
292
302
|
2.times do
|
293
|
-
expect(adapter.conditions).to eq(%
|
303
|
+
expect(adapter.conditions).to eq(%[(secret='f') OR ("#{@article_table}"."published" = 't')])
|
294
304
|
end
|
295
305
|
end
|
296
306
|
|
297
|
-
it
|
307
|
+
it 'has nil joins if no rules' do
|
298
308
|
expect(@ability.model_adapter(Article, :read).joins).to be_nil
|
299
309
|
end
|
300
310
|
|
301
|
-
it
|
302
|
-
@ability.can :read, Article, :
|
303
|
-
@ability.can :read, Article, :
|
311
|
+
it 'has nil joins if no nested hashes specified in conditions' do
|
312
|
+
@ability.can :read, Article, published: false
|
313
|
+
@ability.can :read, Article, secret: true
|
304
314
|
expect(@ability.model_adapter(Article, :read).joins).to be_nil
|
305
315
|
end
|
306
316
|
|
307
|
-
it
|
308
|
-
@ability.can :read, Article, :
|
309
|
-
@ability.can :read, Article, :
|
317
|
+
it 'merges separate joins into a single array' do
|
318
|
+
@ability.can :read, Article, project: { blocked: false }
|
319
|
+
@ability.can :read, Article, company: { admin: true }
|
310
320
|
expect(@ability.model_adapter(Article, :read).joins.inspect).to orderlessly_match([:company, :project].inspect)
|
311
321
|
end
|
312
322
|
|
313
|
-
it
|
314
|
-
@ability.can :read, Article, :
|
315
|
-
@ability.can :read, Article, :
|
323
|
+
it 'merges same joins into a single array' do
|
324
|
+
@ability.can :read, Article, project: { blocked: false }
|
325
|
+
@ability.can :read, Article, project: { admin: true }
|
316
326
|
expect(@ability.model_adapter(Article, :read).joins).to eq([:project])
|
317
327
|
end
|
318
328
|
|
319
|
-
it
|
320
|
-
@ability.can :read, Article, :
|
321
|
-
@ability.can :read, Article, :
|
322
|
-
expect(@ability.model_adapter(Article, :read).joins).to eq([{:
|
329
|
+
it 'merges nested and non-nested joins' do
|
330
|
+
@ability.can :read, Article, project: { blocked: false }
|
331
|
+
@ability.can :read, Article, project: { comments: { spam: true } }
|
332
|
+
expect(@ability.model_adapter(Article, :read).joins).to eq([{ project: [:comments] }])
|
323
333
|
end
|
324
334
|
|
325
|
-
it
|
335
|
+
it 'merges :all conditions with other conditions' do
|
326
336
|
user = User.create!
|
327
|
-
article = Article.create!(:
|
337
|
+
article = Article.create!(user: user)
|
328
338
|
ability = Ability.new(user)
|
329
339
|
ability.can :manage, :all
|
330
|
-
ability.can :manage, Article, :
|
340
|
+
ability.can :manage, Article, user_id: user.id
|
331
341
|
expect(Article.accessible_by(ability)).to eq([article])
|
332
342
|
end
|
333
343
|
|
334
344
|
it 'should not execute a scope when checking ability on the class' do
|
335
|
-
relation = Article.where(:
|
345
|
+
relation = Article.where(secret: true)
|
336
346
|
@ability.can :read, Article, relation do |article|
|
337
347
|
article.secret == true
|
338
348
|
end
|
@@ -342,17 +352,17 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
|
342
352
|
expect { @ability.can? :read, Article }.not_to raise_error
|
343
353
|
end
|
344
354
|
|
345
|
-
context
|
355
|
+
context 'with namespaced models' do
|
346
356
|
before :each do
|
347
357
|
ActiveRecord::Schema.define do
|
348
|
-
create_table(
|
349
|
-
t.timestamps :
|
358
|
+
create_table(:table_xes) do |t|
|
359
|
+
t.timestamps null: false
|
350
360
|
end
|
351
361
|
|
352
|
-
create_table(
|
362
|
+
create_table(:table_zs) do |t|
|
353
363
|
t.integer :table_x_id
|
354
364
|
t.integer :user_id
|
355
|
-
t.timestamps :
|
365
|
+
t.timestamps null: false
|
356
366
|
end
|
357
367
|
end
|
358
368
|
|
@@ -369,14 +379,14 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
|
369
379
|
end
|
370
380
|
end
|
371
381
|
|
372
|
-
it
|
382
|
+
it 'fetches all namespace::table_x when one is related by table_y' do
|
373
383
|
user = User.create!
|
374
384
|
|
375
385
|
ability = Ability.new(user)
|
376
|
-
ability.can :read, Namespace::TableX, :
|
386
|
+
ability.can :read, Namespace::TableX, table_zs: { user_id: user.id }
|
377
387
|
|
378
388
|
table_x = Namespace::TableX.create!
|
379
|
-
|
389
|
+
table_x.table_zs.create(user: user)
|
380
390
|
expect(Namespace::TableX.accessible_by(ability)).to eq([table_x])
|
381
391
|
end
|
382
392
|
end
|
@@ -384,7 +394,7 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
|
384
394
|
context 'when conditions are non iterable ranges' do
|
385
395
|
before :each do
|
386
396
|
ActiveRecord::Schema.define do
|
387
|
-
create_table(
|
397
|
+
create_table(:courses) do |t|
|
388
398
|
t.datetime :start_at
|
389
399
|
end
|
390
400
|
end
|
@@ -394,9 +404,9 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
|
|
394
404
|
end
|
395
405
|
|
396
406
|
it 'fetches only the valid records' do
|
397
|
-
@ability.can :read, Course, :
|
398
|
-
Course.create!(:
|
399
|
-
valid_course = Course.create!(:
|
407
|
+
@ability.can :read, Course, start_at: 1.day.ago..1.day.from_now
|
408
|
+
Course.create!(start_at: 10.days.ago)
|
409
|
+
valid_course = Course.create!(start_at: Time.now)
|
400
410
|
|
401
411
|
expect(Course.accessible_by(@ability)).to eq([valid_course])
|
402
412
|
end
|