mongoid_ability 0.3.12 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/lib/mongoid_ability.rb +1 -0
- data/lib/mongoid_ability/ability.rb +17 -19
- data/lib/mongoid_ability/lock.rb +5 -6
- data/lib/mongoid_ability/owner.rb +6 -12
- data/lib/mongoid_ability/resolve_default_locks.rb +7 -4
- data/lib/mongoid_ability/resolve_inherited_locks.rb +19 -14
- data/lib/mongoid_ability/resolve_locks.rb +7 -27
- data/lib/mongoid_ability/resolve_owner_locks.rb +15 -7
- data/lib/mongoid_ability/resolver.rb +24 -0
- data/lib/mongoid_ability/values_for_accessible_query.rb +4 -0
- data/lib/mongoid_ability/version.rb +1 -1
- data/test/mongoid_ability/ability_basic_test.rb +29 -0
- data/test/mongoid_ability/ability_syntactic_sugar_test.rb +32 -0
- data/test/mongoid_ability/ability_test.rb +83 -39
- data/test/mongoid_ability/accessible_query_builder_test.rb +56 -14
- data/test/mongoid_ability/can_options_test.rb +5 -5
- data/test/mongoid_ability/lock_test.rb +27 -11
- data/test/mongoid_ability/owner_locks_test.rb +9 -13
- data/test/mongoid_ability/resolve_default_locks_test.rb +24 -10
- data/test/mongoid_ability/resolve_inherited_locks_test.rb +20 -19
- data/test/mongoid_ability/resolve_owner_locks_test.rb +27 -21
- data/test/mongoid_ability/{resolve_locks_test.rb → resolver_test.rb} +4 -6
- data/test/mongoid_ability/subject_accessible_by_test.rb +97 -98
- data/test/mongoid_ability/subject_test.rb +57 -26
- data/test/support/test_classes/my_subject.rb +0 -4
- metadata +10 -6
- data/test/mongoid_ability/ability_role_test.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fc3874b06359df0fba37050d4d67178930055ff
|
4
|
+
data.tar.gz: d6b58a71fc1cea44d3777e8199f7a9c7d6c20cf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b5a18e5d2662ba5e5edfb7cac7e7a0295eb78ad35affa6b879e009985978454dff6c0e142feacf0e2bccdd85142f3714f177a9bb4dfdfaa15a7ba89eb6faf34
|
7
|
+
data.tar.gz: a465b49e5904ee8b06fbed035b6a95a439fbb9fc3e5c79f4769de2b59a05702c986e3e64d0a94c60de73aa1fd71e9659f88cf283f7e3aa3a016199f6a3618b7d
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# 0.4.3
|
2
|
+
|
3
|
+
* stub `default_locks` in tests to avoid brittle tests
|
4
|
+
* fix the ability syntactic sugar to support both proc & direct call
|
5
|
+
|
6
|
+
# 0.4.2
|
7
|
+
|
8
|
+
* **yanked**
|
9
|
+
* improved clarity in class naming
|
10
|
+
|
11
|
+
# 0.4.1
|
12
|
+
|
13
|
+
* **yanked**
|
14
|
+
* `Resolver` classes now accept `subject_id` (instead of `subject`) for greater flexibility
|
15
|
+
|
16
|
+
# 0.4.0
|
17
|
+
|
18
|
+
* **yanked**
|
19
|
+
* the `Resolver` classes refactored to return locks (instead of just an outcome)
|
data/lib/mongoid_ability.rb
CHANGED
@@ -6,6 +6,7 @@ require "mongoid_ability/lock"
|
|
6
6
|
require "mongoid_ability/owner"
|
7
7
|
require "mongoid_ability/subject"
|
8
8
|
|
9
|
+
require "mongoid_ability/resolver"
|
9
10
|
require "mongoid_ability/resolve_locks"
|
10
11
|
require "mongoid_ability/resolve_default_locks"
|
11
12
|
require "mongoid_ability/resolve_inherited_locks"
|
@@ -14,39 +14,37 @@ module MongoidAbility
|
|
14
14
|
subject_classes.reject { |cls| cls.superclass.included_modules.include?(MongoidAbility::Subject) }
|
15
15
|
end
|
16
16
|
|
17
|
-
# =====================================================================
|
18
|
-
|
19
17
|
def initialize(owner)
|
20
18
|
@owner = owner
|
21
19
|
|
22
|
-
can do |action, subject_type, subject, options|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
subject_class.self_and_ancestors_with_default_locks.each do |cls|
|
28
|
-
outcome = ResolveInheritedLocks.call(owner, action, cls, subject, options)
|
29
|
-
break unless outcome.nil?
|
20
|
+
can do |action, subject_type, subject, options = {}|
|
21
|
+
subject_id = subject ? subject.id : nil
|
22
|
+
if lock = ResolveLocks.call(owner, action, subject_type, subject_id, options)
|
23
|
+
lock.calculated_outcome(options)
|
30
24
|
end
|
31
|
-
|
32
|
-
outcome
|
33
25
|
end
|
34
26
|
end
|
35
27
|
|
36
|
-
# ---------------------------------------------------------------------
|
37
|
-
|
38
28
|
# lambda for easy permission checking:
|
39
29
|
# .select(¤t_ability.can_read)
|
40
30
|
# .select(¤t_ability.can_update)
|
41
31
|
# .select(¤t_ability.can_destroy)
|
42
32
|
# etc.
|
43
|
-
#
|
44
|
-
# TODO: allow to pass options .select(¤t_ability.can_read(my_option: false))
|
45
|
-
#
|
46
33
|
def method_missing(name, *args)
|
47
34
|
return super unless name.to_s =~ /\A(can|cannot)_/
|
48
|
-
return unless action = name.to_s.
|
49
|
-
|
35
|
+
return unless action = name.to_s.scan(/\A(can|cannot)_(\w+)/).flatten.last.to_sym
|
36
|
+
|
37
|
+
if args.empty? || args.first.is_a?(Hash)
|
38
|
+
case name
|
39
|
+
when /can_/ then -> (doc) { can?(action, doc, *args) }
|
40
|
+
else -> (doc) { cannot?(action, doc, *args) }
|
41
|
+
end
|
42
|
+
else
|
43
|
+
case name
|
44
|
+
when /can_/ then can?(action, *args)
|
45
|
+
else cannot?(action, *args)
|
46
|
+
end
|
47
|
+
end
|
50
48
|
end
|
51
49
|
end
|
52
50
|
end
|
data/lib/mongoid_ability/lock.rb
CHANGED
@@ -15,16 +15,17 @@ module MongoidAbility
|
|
15
15
|
validates :action, presence: true, uniqueness: { scope: [:subject_type, :subject_id, :outcome] }
|
16
16
|
validates :outcome, presence: true
|
17
17
|
|
18
|
-
scope :for_action, ->
|
18
|
+
scope :for_action, ->(action) { where(action: action.to_sym) }
|
19
19
|
|
20
|
-
scope :for_subject_type, ->
|
20
|
+
scope :for_subject_type, ->(subject_type) { where(subject_type: subject_type.to_s) }
|
21
|
+
scope :for_subject_types, ->(subject_types) { criteria.in(subject_type: subject_types) }
|
21
22
|
|
22
|
-
scope :for_subject_id, ->
|
23
|
+
scope :for_subject_id, ->(subject_id) {
|
23
24
|
return where(subject_id: nil) unless subject_id.present?
|
24
25
|
where(subject_id: BSON::ObjectId.from_string(subject_id))
|
25
26
|
}
|
26
27
|
|
27
|
-
scope :for_subject, ->
|
28
|
+
scope :for_subject, ->(subject) {
|
28
29
|
return where(subject_id: nil) unless subject.present?
|
29
30
|
where(subject_type: subject.class.model_name, subject_id: subject.id)
|
30
31
|
}
|
@@ -34,8 +35,6 @@ module MongoidAbility
|
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
|
-
# =====================================================================
|
38
|
-
|
39
38
|
concerning :LockType do
|
40
39
|
def class_lock?
|
41
40
|
!id_lock?
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module MongoidAbility
|
2
2
|
module Owner
|
3
|
-
def self.included
|
3
|
+
def self.included(base)
|
4
4
|
base.extend ClassMethods
|
5
5
|
base.class_eval do
|
6
6
|
delegate :can?, :cannot?, to: :ability
|
@@ -8,8 +8,6 @@ module MongoidAbility
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
# ---------------------------------------------------------------------
|
12
|
-
|
13
11
|
module ClassMethods
|
14
12
|
def locks_relation_name
|
15
13
|
:locks
|
@@ -20,32 +18,28 @@ module MongoidAbility
|
|
20
18
|
end
|
21
19
|
end
|
22
20
|
|
23
|
-
# ---------------------------------------------------------------------
|
24
|
-
|
25
21
|
def locks_relation
|
26
|
-
|
22
|
+
send(self.class.locks_relation_name)
|
27
23
|
end
|
28
24
|
|
29
|
-
def locks_relation=
|
30
|
-
|
25
|
+
def locks_relation=(val)
|
26
|
+
send "#{self.class.locks_relation_name}=", val
|
31
27
|
end
|
32
28
|
|
33
29
|
def inherit_from_relation
|
34
|
-
|
30
|
+
send(self.class.inherit_from_relation_name)
|
35
31
|
end
|
36
32
|
|
37
33
|
def ability
|
38
34
|
@ability ||= MongoidAbility::Ability.new(self)
|
39
35
|
end
|
40
36
|
|
41
|
-
# ---------------------------------------------------------------------
|
42
|
-
|
43
37
|
def has_lock?(lock)
|
44
38
|
@has_lock ||= {}
|
45
39
|
@has_lock[lock] ||= locks_relation.where(action: lock.action, subject_type: lock.subject_type, subject_id: lock.subject_id.presence).cache.exists?
|
46
40
|
end
|
47
41
|
|
48
|
-
private
|
42
|
+
private
|
49
43
|
|
50
44
|
def cleanup_locks
|
51
45
|
locks_relation.select(&:open?).each do |lock|
|
@@ -1,11 +1,14 @@
|
|
1
1
|
module MongoidAbility
|
2
|
-
class ResolveDefaultLocks <
|
2
|
+
class ResolveDefaultLocks < Resolver
|
3
3
|
def call
|
4
|
-
|
5
|
-
return
|
4
|
+
closed_lock = default_locks.detect { |l| l.closed?(options) }
|
5
|
+
return closed_lock if closed_lock
|
6
|
+
|
7
|
+
open_lock = default_locks.detect { |l| l.open?(options) }
|
8
|
+
return open_lock if open_lock
|
6
9
|
end
|
7
10
|
|
8
|
-
private
|
11
|
+
private
|
9
12
|
|
10
13
|
def default_locks
|
11
14
|
subject_class.default_locks.select { |l| l.action.to_s == action.to_s }
|
@@ -1,30 +1,35 @@
|
|
1
1
|
module MongoidAbility
|
2
|
-
class ResolveInheritedLocks <
|
2
|
+
class ResolveInheritedLocks < Resolver
|
3
3
|
def call
|
4
|
-
|
5
|
-
return
|
4
|
+
owner_lock = resolved_owner_lock
|
5
|
+
return owner_lock if owner_lock
|
6
6
|
|
7
7
|
if owner.respond_to?(owner.class.inherit_from_relation_name) && !owner.inherit_from_relation.nil?
|
8
|
-
|
9
|
-
|
8
|
+
resolved_inherited_owner_locks = owner.inherit_from_relation.map { |inherited_owner| resolved_inherited_owner_lock(inherited_owner) }.compact
|
9
|
+
|
10
|
+
open_lock = resolved_inherited_owner_locks.detect { |l| l.open?(options) }
|
11
|
+
return open_lock if open_lock
|
12
|
+
|
13
|
+
closed_lock = resolved_inherited_owner_locks.detect { |l| l.closed?(options) }
|
14
|
+
return closed_lock if closed_lock
|
10
15
|
end
|
11
16
|
|
12
|
-
|
17
|
+
resolved_default_lock
|
13
18
|
end
|
14
19
|
|
15
|
-
private
|
20
|
+
private
|
16
21
|
|
17
|
-
def
|
18
|
-
@
|
22
|
+
def resolved_owner_lock
|
23
|
+
@resolved_owner_lock ||= ResolveOwnerLocks.call(owner, action, subject_class, subject_id, options)
|
19
24
|
end
|
20
25
|
|
21
|
-
def
|
22
|
-
@
|
23
|
-
@
|
26
|
+
def resolved_inherited_owner_lock(inherited_owner)
|
27
|
+
@resolved_inherited_owner_lock ||= {}
|
28
|
+
@resolved_inherited_owner_lock[inherited_owner] ||= ResolveOwnerLocks.call(inherited_owner, action, subject_class, subject_id, options)
|
24
29
|
end
|
25
30
|
|
26
|
-
def
|
27
|
-
@
|
31
|
+
def resolved_default_lock
|
32
|
+
@resolved_default_lock ||= ResolveDefaultLocks.call(nil, action, subject_class, nil, options)
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
@@ -1,32 +1,12 @@
|
|
1
1
|
module MongoidAbility
|
2
|
-
class ResolveLocks <
|
3
|
-
attr_reader(
|
4
|
-
:subject_class,
|
5
|
-
:subject_id
|
6
|
-
)
|
7
|
-
|
8
|
-
def self.call(*args)
|
9
|
-
new(*args).call
|
10
|
-
end
|
11
|
-
|
12
|
-
# =====================================================================
|
13
|
-
|
14
|
-
def initialize(*args)
|
15
|
-
super(*args)
|
16
|
-
|
17
|
-
@subject_class = subject_type.to_s.constantize
|
18
|
-
@subject_id = subject.id if subject.present?
|
19
|
-
|
20
|
-
fail StandardError, "#{subject_type} class does not have default locks" unless @subject_class.respond_to?(:default_locks)
|
21
|
-
fail StandardError, "#{subject_type} class does not have default lock for :#{action} action" unless @subject_class.self_and_ancestors_with_default_locks.any? do |cls|
|
22
|
-
cls.default_locks.any? { |l| l.action == action }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# =====================================================================
|
27
|
-
|
2
|
+
class ResolveLocks < Resolver
|
28
3
|
def call
|
29
|
-
|
4
|
+
lock = nil
|
5
|
+
subject_class.self_and_ancestors_with_default_locks.each do |cls|
|
6
|
+
lock = ResolveInheritedLocks.call(owner, action, cls.to_s, subject_id, options)
|
7
|
+
break unless lock.nil?
|
8
|
+
end
|
9
|
+
lock
|
30
10
|
end
|
31
11
|
end
|
32
12
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# OPTIMIZE: this seems quite expensive
|
2
2
|
|
3
3
|
module MongoidAbility
|
4
|
-
class ResolveOwnerLocks <
|
4
|
+
class ResolveOwnerLocks < Resolver
|
5
5
|
def call
|
6
6
|
return unless owner.respond_to?(:locks_relation)
|
7
7
|
|
@@ -9,17 +9,25 @@ module MongoidAbility
|
|
9
9
|
|
10
10
|
return unless locks_for_subject_type.exists?
|
11
11
|
|
12
|
-
# return
|
12
|
+
# return lock if owner defines lock for id
|
13
13
|
if subject_id.present?
|
14
14
|
id_locks = locks_for_subject_type.id_locks.for_subject_id(subject_id).cache
|
15
|
-
|
16
|
-
|
15
|
+
|
16
|
+
closed_lock = id_locks.detect { |l| l.closed?(options) }
|
17
|
+
return closed_lock if closed_lock
|
18
|
+
|
19
|
+
open_lock = id_locks.detect { |l| l.open?(options) }
|
20
|
+
return open_lock if open_lock
|
17
21
|
end
|
18
22
|
|
19
|
-
# return
|
23
|
+
# return lock if owner defines lock for subject_type
|
20
24
|
class_locks = locks_for_subject_type.class_locks.cache
|
21
|
-
|
22
|
-
|
25
|
+
|
26
|
+
closed_lock = class_locks.class_locks.detect { |l| l.closed?(options) }
|
27
|
+
return closed_lock if closed_lock
|
28
|
+
|
29
|
+
open_lock = class_locks.class_locks.detect { |l| l.open?(options) }
|
30
|
+
return open_lock if open_lock
|
23
31
|
|
24
32
|
nil
|
25
33
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module MongoidAbility
|
2
|
+
class Resolver < Struct.new(:owner, :action, :subject_type, :subject_id, :options)
|
3
|
+
attr_reader :subject_class
|
4
|
+
|
5
|
+
def self.call(*args)
|
6
|
+
new(*args).call
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(owner, action, subject_type, subject_id = nil, options = {})
|
10
|
+
super(owner, action, subject_type, subject_id, options)
|
11
|
+
|
12
|
+
@subject_class = subject_type.to_s.constantize
|
13
|
+
|
14
|
+
raise StandardError, "#{subject_type} class does not have default locks" unless @subject_class.respond_to?(:default_locks)
|
15
|
+
raise StandardError, "#{subject_type} class does not have default lock for :#{action} action" unless @subject_class.self_and_ancestors_with_default_locks.any? do |cls|
|
16
|
+
cls.default_locks.any? { |l| l.action == action }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module MongoidAbility
|
4
|
+
describe 'basic ability test' do
|
5
|
+
let(:read_lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
|
6
|
+
let(:owner) { MyRole.new(my_locks: [read_lock]) }
|
7
|
+
let(:ability) { Ability.new(owner) }
|
8
|
+
|
9
|
+
let(:default_locks) { [MyLock.new(action: :read, outcome: true)] }
|
10
|
+
|
11
|
+
it 'owner can?' do
|
12
|
+
MySubject.stub :default_locks, default_locks do
|
13
|
+
ability.can?(:read, MySubject).must_equal false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'owner cannot?' do
|
18
|
+
MySubject.stub :default_locks, default_locks do
|
19
|
+
ability.cannot?(:read, MySubject).must_equal true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'is accessible by' do
|
24
|
+
MySubject.stub :default_locks, default_locks do
|
25
|
+
MySubject.accessible_by(ability, :read).must_be_kind_of Mongoid::Criteria
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module MongoidAbility
|
4
|
+
describe 'syntactic sugar' do
|
5
|
+
let(:read_lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
|
6
|
+
let(:owner) { MyRole.new(my_locks: [read_lock]) }
|
7
|
+
let(:ability) { Ability.new(owner) }
|
8
|
+
let(:options) { { x: 1 } }
|
9
|
+
|
10
|
+
let(:default_locks) { [MyLock.new(action: :read, outcome: true)] }
|
11
|
+
|
12
|
+
it 'owner can?' do
|
13
|
+
MySubject.stub :default_locks, default_locks do
|
14
|
+
[MySubject].select(&ability.can_read(options)).must_equal []
|
15
|
+
[MySubject].select(&ability.can_read?(options)).must_equal []
|
16
|
+
|
17
|
+
ability.can_read(MySubject, options).must_equal false
|
18
|
+
ability.can_read?(MySubject, options).must_equal false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'owner cannot?' do
|
23
|
+
MySubject.stub :default_locks, default_locks do
|
24
|
+
[MySubject].select(&ability.cannot_read(options)).must_equal [MySubject]
|
25
|
+
[MySubject].select(&ability.cannot_read?(options)).must_equal [MySubject]
|
26
|
+
|
27
|
+
ability.cannot_read(MySubject, options).must_equal true
|
28
|
+
ability.cannot_read?(MySubject, options).must_equal true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|