mongoid_ability 0.0.1
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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +22 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +154 -0
- data/Rakefile +9 -0
- data/lib/mongoid_ability.rb +17 -0
- data/lib/mongoid_ability/ability.rb +29 -0
- data/lib/mongoid_ability/ability_resolver.rb +87 -0
- data/lib/mongoid_ability/lock.rb +64 -0
- data/lib/mongoid_ability/owner.rb +59 -0
- data/lib/mongoid_ability/subject.rb +80 -0
- data/lib/mongoid_ability/version.rb +3 -0
- data/mongoid_ability.gemspec +31 -0
- data/test/mongoid_ability/ability_resolver_test.rb +208 -0
- data/test/mongoid_ability/ability_test.rb +91 -0
- data/test/mongoid_ability/lock_test.rb +57 -0
- data/test/mongoid_ability/owner_test.rb +53 -0
- data/test/mongoid_ability/subject_test.rb +102 -0
- data/test/test_helper.rb +122 -0
- metadata +200 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module MongoidAbility
|
|
2
|
+
module Owner
|
|
3
|
+
|
|
4
|
+
def self.included base
|
|
5
|
+
base.extend ClassMethods
|
|
6
|
+
base.class_eval do
|
|
7
|
+
delegate :can?, :cannot?, to: :ability
|
|
8
|
+
|
|
9
|
+
before_save :cleanup_locks
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# =====================================================================
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
# override if needed
|
|
17
|
+
# return for example :my_locks
|
|
18
|
+
def locks_relation_name
|
|
19
|
+
@locks_relation_name ||= relations.detect{ |name, meta| meta.class_name == lock_class_name }.first.to_sym
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# override if your relation is named differently
|
|
23
|
+
def roles_relation_name
|
|
24
|
+
:roles
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# override if needed
|
|
28
|
+
# return for example 'MyLock'
|
|
29
|
+
def lock_class_name
|
|
30
|
+
@lock_class_name ||= Object.subclasses.detect{ |cls| cls < MongoidAbility::Lock }.name
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# =====================================================================
|
|
35
|
+
|
|
36
|
+
def locks_relation
|
|
37
|
+
self.send(self.class.locks_relation_name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def roles_relation
|
|
41
|
+
self.send(self.class.roles_relation_name)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
def ability
|
|
47
|
+
@ability ||= MongoidAbility::Ability.new(self)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private # =============================================================
|
|
51
|
+
|
|
52
|
+
def cleanup_locks
|
|
53
|
+
locks_relation.select(&:open?).each do |lock|
|
|
54
|
+
lock.destroy if locks_relation.where(action: lock.action, subject_type: lock.subject_type, subject_id: lock.subject_id).any?(&:closed?)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module MongoidAbility
|
|
2
|
+
module Subject
|
|
3
|
+
|
|
4
|
+
def self.included base
|
|
5
|
+
base.extend ClassMethods
|
|
6
|
+
base.class_eval do
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# =====================================================================
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def default_locks
|
|
14
|
+
@default_locks ||= []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def default_locks= val
|
|
18
|
+
@default_locks = val
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def default_lock action, outcome
|
|
22
|
+
default_locks << lock_class_name.constantize.new(subject_type: self, action: action, outcome: outcome)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
# override if needed
|
|
28
|
+
# return for example 'MyLock'
|
|
29
|
+
def lock_class_name
|
|
30
|
+
Object.subclasses.detect{ |cls| cls < MongoidAbility::Lock }.name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
def self_and_ancestors_with_default_locks
|
|
36
|
+
self.ancestors.select{ |a| a.is_a?(Class) && a.respond_to?(:default_locks) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ancestors_with_default_locks
|
|
40
|
+
self_and_ancestors_with_default_locks - [self]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
def accessible_by ability, action=:read
|
|
46
|
+
criteria = Mongoid::Criteria.new(self)
|
|
47
|
+
|
|
48
|
+
return criteria unless ability.user.present?
|
|
49
|
+
|
|
50
|
+
id_locks = [
|
|
51
|
+
ability.user,
|
|
52
|
+
ability.user.roles_relation
|
|
53
|
+
].flatten.collect { |owner|
|
|
54
|
+
owner.locks_relation.for_subject_type(self.to_s).id_locks.for_action(action).to_a
|
|
55
|
+
}.flatten
|
|
56
|
+
|
|
57
|
+
if ability.can?(action, self)
|
|
58
|
+
criteria.nin({
|
|
59
|
+
_id: id_locks.map(&:subject_id).select do |subject_id|
|
|
60
|
+
subject = self.new
|
|
61
|
+
subject.id = subject_id
|
|
62
|
+
ability.cannot?(action, subject)
|
|
63
|
+
end
|
|
64
|
+
})
|
|
65
|
+
else
|
|
66
|
+
criteria.in({
|
|
67
|
+
_id: id_locks.map(&:subject_id).select do |subject_id|
|
|
68
|
+
subject = self.new
|
|
69
|
+
subject.id = subject_id
|
|
70
|
+
ability.can?(action, subject)
|
|
71
|
+
end
|
|
72
|
+
})
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# =====================================================================
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'mongoid_ability/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "mongoid_ability"
|
|
8
|
+
spec.version = MongoidAbility::VERSION
|
|
9
|
+
spec.authors = ["Tomas Celizna"]
|
|
10
|
+
spec.email = ["tomas.celizna@gmail.com"]
|
|
11
|
+
spec.summary = %q{Custom Ability class that allows CanCanCan authorization library store permissions in MongoDB via the Mongoid gem.}
|
|
12
|
+
spec.description = %q{Custom Ability class that allows CanCanCan authorization library store permissions in MongoDB via the Mongoid gem.}
|
|
13
|
+
spec.homepage = "https://github.com/tomasc/mongoid_ability"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_dependency "cancancan", "~> 1.9"
|
|
22
|
+
spec.add_dependency "mongoid", "~> 4.0"
|
|
23
|
+
|
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
|
25
|
+
spec.add_development_dependency "coveralls"
|
|
26
|
+
spec.add_development_dependency "database_cleaner"
|
|
27
|
+
spec.add_development_dependency "guard"
|
|
28
|
+
spec.add_development_dependency "guard-minitest"
|
|
29
|
+
spec.add_development_dependency "minitest"
|
|
30
|
+
spec.add_development_dependency "rake"
|
|
31
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
module MongoidAbility
|
|
4
|
+
describe AbilityResolver do
|
|
5
|
+
|
|
6
|
+
let(:user) { TestUser.new }
|
|
7
|
+
let(:role_editor) { TestRole.new(name: 'Editor') }
|
|
8
|
+
let(:role_sysop) { TestRole.new(name: 'SysOp') }
|
|
9
|
+
|
|
10
|
+
let(:ability_resolver_subject) { TestAbilityResolverSubject.new }
|
|
11
|
+
|
|
12
|
+
subject { AbilityResolver.new(user, :read, TestAbilityResolverSubject.to_s) }
|
|
13
|
+
let(:subject_with_id) { AbilityResolver.new(user, :read, TestAbilityResolverSubject.to_s, ability_resolver_subject) }
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
describe 'errors' do
|
|
18
|
+
it 'raises NameError for invalid subject_type' do
|
|
19
|
+
-> { ar = AbilityResolver.new(user, :read, 'Foo') }.must_raise NameError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'raises StandardError when subject_type does not have default_locks' do
|
|
23
|
+
-> { ar = AbilityResolver.new(user, :read, Object.to_s) }.must_raise StandardError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'raises StandardError when subject_type class or its ancestors does not have default_lock' do
|
|
27
|
+
TestAbilityResolverSubject.stub(:default_locks, []) do
|
|
28
|
+
-> { ar = AbilityResolver.new(user, :read, TestAbilityResolverSubject.to_s) }.must_raise StandardError
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
describe '#outcome' do
|
|
36
|
+
describe 'when locks on both user and its roles' do
|
|
37
|
+
before do
|
|
38
|
+
user.roles = [
|
|
39
|
+
role_sysop.tap { |r| r.test_locks = [
|
|
40
|
+
TestLock.new(action: :read, outcome: true, subject_type: TestAbilityResolverSubject.to_s)
|
|
41
|
+
]}
|
|
42
|
+
]
|
|
43
|
+
user.test_locks = [
|
|
44
|
+
TestLock.new(action: :read, outcome: false, subject_type: TestAbilityResolverSubject.to_s)
|
|
45
|
+
]
|
|
46
|
+
end
|
|
47
|
+
it 'prefers users locks' do
|
|
48
|
+
subject.outcome.must_equal false
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
describe 'when locks on roles and on class' do
|
|
52
|
+
before do
|
|
53
|
+
user.roles = [
|
|
54
|
+
role_sysop.tap { |r| r.test_locks = [
|
|
55
|
+
TestLock.new(action: :read, outcome: false, subject_type: TestAbilityResolverSubject.to_s)
|
|
56
|
+
]}
|
|
57
|
+
]
|
|
58
|
+
end
|
|
59
|
+
it 'prefers role locks' do
|
|
60
|
+
subject.outcome.must_equal false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
describe '#user_outcome' do
|
|
68
|
+
describe 'no locks' do
|
|
69
|
+
it 'returns nil if no locks for subject_type and action exists' do
|
|
70
|
+
subject.user_outcome.must_be_nil
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe 'id locks' do
|
|
75
|
+
it 'returns outcome' do
|
|
76
|
+
user.test_locks = [
|
|
77
|
+
TestLock.new(action: :read, subject: ability_resolver_subject, outcome: true)
|
|
78
|
+
]
|
|
79
|
+
subject_with_id.user_outcome.must_equal true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'prefers negative outcome' do
|
|
83
|
+
user.test_locks = [
|
|
84
|
+
TestLock.new(action: :read, subject: ability_resolver_subject, outcome: true),
|
|
85
|
+
TestLock.new(action: :read, subject: ability_resolver_subject, outcome: false)
|
|
86
|
+
]
|
|
87
|
+
subject_with_id.user_outcome.must_equal false
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe 'class locks' do
|
|
92
|
+
it 'returns outcome' do
|
|
93
|
+
user.test_locks = [
|
|
94
|
+
TestLock.new(action: :read, subject_type: TestAbilityResolverSubject.to_s, outcome: true)
|
|
95
|
+
]
|
|
96
|
+
subject.user_outcome.must_equal true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'prefers negative outcome' do
|
|
100
|
+
user.test_locks = [
|
|
101
|
+
TestLock.new(action: :read, subject_type: TestAbilityResolverSubject.to_s, outcome: true),
|
|
102
|
+
TestLock.new(action: :read, subject_type: TestAbilityResolverSubject.to_s, outcome: false)
|
|
103
|
+
]
|
|
104
|
+
subject.user_outcome.must_equal false
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
describe '#roles_outcome' do
|
|
112
|
+
describe 'no locks' do
|
|
113
|
+
it 'returns nil if no locks for subject_type exist in any of user roles' do
|
|
114
|
+
subject.roles_outcome.must_be_nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe 'id locks' do
|
|
119
|
+
it 'returns outcome' do
|
|
120
|
+
user.roles = [
|
|
121
|
+
role_sysop.tap{ |r| r.test_locks = [
|
|
122
|
+
TestLock.new(subject: ability_resolver_subject, action: :read, outcome: true)
|
|
123
|
+
]},
|
|
124
|
+
role_editor
|
|
125
|
+
]
|
|
126
|
+
subject_with_id.roles_outcome.must_equal true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'prefers negative outcome across one role' do
|
|
130
|
+
user.roles = [
|
|
131
|
+
role_sysop.tap{ |r| r.test_locks = [
|
|
132
|
+
TestLock.new(subject: ability_resolver_subject, action: :read, outcome: true),
|
|
133
|
+
TestLock.new(subject: ability_resolver_subject, action: :read, outcome: false)
|
|
134
|
+
]},
|
|
135
|
+
role_editor
|
|
136
|
+
]
|
|
137
|
+
subject_with_id.roles_outcome.must_equal false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'prefers positive outcome across multiple roles' do
|
|
141
|
+
user.roles = [
|
|
142
|
+
role_sysop.tap{ |r| r.test_locks = [
|
|
143
|
+
TestLock.new(subject: ability_resolver_subject, action: :read, outcome: true)
|
|
144
|
+
]},
|
|
145
|
+
role_editor.tap{ |r| r.test_locks = [
|
|
146
|
+
TestLock.new(subject: ability_resolver_subject, action: :read, outcome: false)
|
|
147
|
+
]}
|
|
148
|
+
]
|
|
149
|
+
subject_with_id.roles_outcome.must_equal true
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
describe 'class locks' do
|
|
154
|
+
it 'returns outcome' do
|
|
155
|
+
user.roles = [
|
|
156
|
+
role_sysop.tap{ |r| r.test_locks = [
|
|
157
|
+
TestLock.new(subject_type: TestAbilityResolverSubject.to_s, action: :read, outcome: true)
|
|
158
|
+
]},
|
|
159
|
+
role_editor
|
|
160
|
+
]
|
|
161
|
+
subject.roles_outcome.must_equal true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it 'prefers negative outcome across one role' do
|
|
165
|
+
user.roles = [
|
|
166
|
+
role_sysop.tap{ |r| r.test_locks = [
|
|
167
|
+
TestLock.new(subject_type: TestAbilityResolverSubject.to_s, action: :read, outcome: true),
|
|
168
|
+
TestLock.new(subject_type: TestAbilityResolverSubject.to_s, action: :read, outcome: false)
|
|
169
|
+
]},
|
|
170
|
+
role_editor
|
|
171
|
+
]
|
|
172
|
+
subject.roles_outcome.must_equal false
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'prefers positive outcome across multiple roles' do
|
|
176
|
+
user.roles = [
|
|
177
|
+
role_sysop.tap{ |r| r.test_locks = [
|
|
178
|
+
TestLock.new(subject_type: TestAbilityResolverSubject.to_s, action: :read, outcome: true)
|
|
179
|
+
]},
|
|
180
|
+
role_editor.tap{ |r| r.test_locks = [
|
|
181
|
+
TestLock.new(subject_type: TestAbilityResolverSubject.to_s, action: :read, outcome: false)
|
|
182
|
+
]}
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
subject.roles_outcome.must_equal true
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
describe '#class_outcome' do
|
|
193
|
+
it 'returns outcome' do
|
|
194
|
+
subject.class_outcome.must_equal true
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it 'prefers negative outcome across same class' do
|
|
198
|
+
TestAbilityResolverSubject.stub(:default_locks, [
|
|
199
|
+
TestLock.new(subject_type: TestAbilityResolverSubject.to_s, action: :read, outcome: false),
|
|
200
|
+
TestLock.new(subject_type: TestAbilityResolverSubject.to_s, action: :read, outcome: true)
|
|
201
|
+
]) do
|
|
202
|
+
subject.class_outcome.must_equal false
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
module MongoidAbility
|
|
4
|
+
describe Ability do
|
|
5
|
+
|
|
6
|
+
let(:user) { TestUser.new }
|
|
7
|
+
let(:ability) { Ability.new(user) }
|
|
8
|
+
|
|
9
|
+
# ---------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
describe 'user' do
|
|
12
|
+
it 'exposes user' do
|
|
13
|
+
ability.user.must_equal user
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
describe 'default locks' do
|
|
20
|
+
it 'propagates from superclass to all subclasses' do
|
|
21
|
+
ability.can?(:update, TestAbilitySubjectSuper1).must_equal true
|
|
22
|
+
ability.can?(:update, TestAbilitySubject).must_equal true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe 'when defined for all superclasses' do
|
|
26
|
+
it 'propagates default locks to subclasses' do
|
|
27
|
+
ability.can?(:read, TestAbilitySubjectSuper2).must_equal false
|
|
28
|
+
TestAbilitySubjectSuper1.stub(:default_locks, [
|
|
29
|
+
TestLock.new(subject_type: TestAbilitySubjectSuper1.to_s, action: :read, outcome: false)
|
|
30
|
+
]) do
|
|
31
|
+
ability.can?(:read, TestAbilitySubjectSuper1).must_equal false
|
|
32
|
+
end
|
|
33
|
+
TestAbilitySubject.stub(:default_locks, [
|
|
34
|
+
TestLock.new(subject_type: TestAbilitySubject.to_s, action: :read, outcome: true)
|
|
35
|
+
]) do
|
|
36
|
+
ability.can?(:read, TestAbilitySubject).must_equal true
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe 'when defined for some superclasses' do
|
|
42
|
+
it 'propagates default locks to subclasses' do
|
|
43
|
+
ability.can?(:read, TestAbilitySubjectSuper2).must_equal false
|
|
44
|
+
ability.can?(:read, TestAbilitySubjectSuper1).must_equal false
|
|
45
|
+
TestAbilitySubject.stub(:default_locks, [
|
|
46
|
+
TestLock.new(subject_type: TestAbilitySubjectSuper1.to_s, action: :read, outcome: true)
|
|
47
|
+
]) do
|
|
48
|
+
ability.can?(:read, TestAbilitySubject).must_equal true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
describe 'user locks' do
|
|
57
|
+
describe 'when defined for superclass' do
|
|
58
|
+
before do
|
|
59
|
+
user.tap do |u|
|
|
60
|
+
u.test_locks = [TestLock.new(subject_type: TestAbilitySubjectSuper2.to_s, action: :read, outcome: true)]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
it 'applies the superclass lock' do
|
|
64
|
+
ability.can?(:read, TestAbilitySubject).must_equal true
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
describe 'role locks' do
|
|
72
|
+
describe 'when defined for superclass' do
|
|
73
|
+
before do
|
|
74
|
+
user.tap do |u|
|
|
75
|
+
u.roles = [
|
|
76
|
+
TestRole.new(test_locks: [
|
|
77
|
+
TestLock.new(subject_type: TestAbilitySubjectSuper2.to_s, action: :read, outcome: true)
|
|
78
|
+
])
|
|
79
|
+
]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
it 'applies the superclass lock' do
|
|
83
|
+
ability.can?(:read, TestAbilitySubject).must_equal true
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
end
|
|
91
|
+
end
|