mongoid_ability 0.3.12 → 0.4.3
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 +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
|