mongoid_ability 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile +7 -0
  4. data/README.md +55 -23
  5. data/lib/cancancan/model_adapters/mongoid_adapter.rb +156 -0
  6. data/lib/cancancan/model_additions.rb +30 -0
  7. data/lib/mongoid_ability.rb +12 -12
  8. data/lib/mongoid_ability/ability.rb +67 -26
  9. data/lib/mongoid_ability/find_lock.rb +71 -0
  10. data/lib/mongoid_ability/lock.rb +25 -16
  11. data/lib/mongoid_ability/locks_decorator.rb +45 -0
  12. data/lib/mongoid_ability/owner.rb +23 -3
  13. data/lib/mongoid_ability/subject.rb +10 -22
  14. data/lib/mongoid_ability/version.rb +1 -1
  15. data/test/cancancan/model_adapters/mongoid_adapter_options_test.rb +102 -0
  16. data/test/cancancan/model_adapters/mongoid_adapter_test.rb +207 -0
  17. data/test/mongoid_ability/ability_basic_benchmark.rb +30 -0
  18. data/test/mongoid_ability/ability_basic_test.rb +44 -13
  19. data/test/mongoid_ability/ability_marshal_test.rb +17 -0
  20. data/test/mongoid_ability/ability_options_test.rb +93 -0
  21. data/test/mongoid_ability/ability_test.rb +87 -106
  22. data/test/mongoid_ability/find_lock_test.rb +67 -0
  23. data/test/mongoid_ability/lock_test.rb +32 -40
  24. data/test/mongoid_ability/owner_locks_test.rb +14 -21
  25. data/test/mongoid_ability/owner_test.rb +4 -14
  26. data/test/mongoid_ability/subject_test.rb +32 -58
  27. data/test/support/test_classes/my_lock.rb +8 -13
  28. data/test/support/test_classes/my_owner.rb +13 -15
  29. data/test/support/test_classes/my_role.rb +9 -11
  30. data/test/support/test_classes/my_subject.rb +16 -9
  31. data/test/test_helper.rb +12 -2
  32. metadata +18 -25
  33. data/lib/mongoid_ability/accessible_query_builder.rb +0 -64
  34. data/lib/mongoid_ability/resolve_default_locks.rb +0 -17
  35. data/lib/mongoid_ability/resolve_inherited_locks.rb +0 -35
  36. data/lib/mongoid_ability/resolve_locks.rb +0 -12
  37. data/lib/mongoid_ability/resolve_owner_locks.rb +0 -35
  38. data/lib/mongoid_ability/resolver.rb +0 -24
  39. data/lib/mongoid_ability/values_for_accessible_query.rb +0 -74
  40. data/test/mongoid_ability/ability_syntactic_sugar_test.rb +0 -32
  41. data/test/mongoid_ability/accessible_query_builder_test.rb +0 -119
  42. data/test/mongoid_ability/can_options_test.rb +0 -17
  43. data/test/mongoid_ability/resolve_default_locks_test.rb +0 -41
  44. data/test/mongoid_ability/resolve_inherited_locks_test.rb +0 -50
  45. data/test/mongoid_ability/resolve_owner_locks_test.rb +0 -56
  46. data/test/mongoid_ability/resolver_test.rb +0 -23
  47. data/test/mongoid_ability/subject_accessible_by_test.rb +0 -147
@@ -0,0 +1,71 @@
1
+ module MongoidAbility
2
+ # finds first lock that controls specified params
3
+
4
+ class FindLock < Struct.new(:owner, :action, :subject_type, :subject_id, :options)
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.to_s, subject_id, options)
11
+ end
12
+
13
+ def call
14
+ lock = nil
15
+ subject_class.self_and_ancestors_with_default_locks.each do |cls|
16
+ break if lock = FindOwnedLock.call(owner, action, cls, subject_id, options)
17
+ break if lock = FindInheritedLock.call(owner, action, cls, subject_id, options)
18
+ break if lock = FindDefaultLock.call(owner, action, cls, subject_id, options)
19
+ end
20
+ lock
21
+ end
22
+
23
+ private
24
+
25
+ def subject_class
26
+ subject_type.constantize
27
+ end
28
+
29
+ # ---------------------------------------------------------------------
30
+
31
+ class FindDefaultLock < FindLock
32
+ def call
33
+ locks = subject_class.default_locks.for_action(action)
34
+ locks.compact.detect(&:closed?) || locks.compact.detect(&:open?)
35
+ end
36
+ end
37
+
38
+ class FindInheritedLock < FindLock
39
+ def call
40
+ return unless owner.respond_to?(owner.class.inherit_from_relation_name)
41
+ locks = LocksDecorator.new(
42
+ owner.inherit_from_relation
43
+ .flat_map { |inherited_owner| FindOwnedLock.call(inherited_owner, action, subject_type, subject_id, options) }
44
+ )
45
+
46
+ if subject_id.present?
47
+ lock = locks.for_subject_id(subject_id).detect(&:closed?) ||
48
+ locks.for_subject_id(subject_id).detect(&:open?)
49
+ return lock unless lock.nil?
50
+ end
51
+
52
+ locks.class_locks.detect(&:open?) || locks.class_locks.detect(&:closed?)
53
+ end
54
+ end
55
+
56
+ class FindOwnedLock < FindLock
57
+ def call
58
+ return unless owner.respond_to?(:locks_relation)
59
+ locks = owner.locks_relation.for_action(action).for_subject_type(subject_type)
60
+
61
+ if subject_id.present?
62
+ lock = locks.for_subject_id(subject_id).detect(&:closed?) ||
63
+ locks.for_subject_id(subject_id).detect(&:open?)
64
+ return lock unless lock.nil?
65
+ end
66
+
67
+ locks.class_locks.detect(&:closed?) || locks.class_locks.detect(&:open?)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -46,32 +46,24 @@ module MongoidAbility
46
46
  end
47
47
 
48
48
  concerning :Outcome do
49
- # NOTE: override for more complicated results
50
- def calculated_outcome(_options = {})
49
+ def open?
51
50
  outcome
52
51
  end
53
52
 
54
- def open?(options = {})
55
- calculated_outcome(options) == true
56
- end
57
-
58
- def closed?(options = {})
59
- !open?(options)
53
+ def closed?
54
+ !open?
60
55
  end
61
56
  end
62
57
 
58
+ # calculates outcome as if this lock is not present
63
59
  concerning :InheritedOutcome do
64
- # calculates outcome as if this lock is not present
65
- def inherited_outcome(options = default_options)
66
- return calculated_outcome(options) unless owner.present?
60
+ def inherited_outcome(options = {})
61
+ return outcome unless owner.present?
67
62
  cloned_owner = owner.clone
68
63
  cloned_owner.locks_relation = cloned_owner.locks_relation - [self]
69
- MongoidAbility::Ability.new(cloned_owner).can? action, (subject.present? ? subject : subject_class), options
70
- end
64
+ cloned_ability = MongoidAbility::Ability.new(cloned_owner)
71
65
 
72
- # used when calculating inherited outcome
73
- def default_options
74
- {}
66
+ cloned_ability.can?(action, (subject.present? ? subject : subject_class), options)
75
67
  end
76
68
  end
77
69
 
@@ -80,5 +72,22 @@ module MongoidAbility
80
72
  subject_type.constantize
81
73
  end
82
74
  end
75
+
76
+ concerning :Group do
77
+ def group_key_for_calc
78
+ [subject_type, subject_id, action, options]
79
+ end
80
+ end
81
+
82
+ concerning :Sort do
83
+ class_methods do
84
+ def sort
85
+ -> (a, b) {
86
+ [a.subject_type, a.subject_id.to_s, a.action, (a.outcome ? -1 : 1)] <=>
87
+ [b.subject_type, b.subject_id.to_s, b.action, (b.outcome ? -1 : 1)]
88
+ }
89
+ end
90
+ end
91
+ end
83
92
  end
84
93
  end
@@ -0,0 +1,45 @@
1
+ module MongoidAbility
2
+ # adds scope-like methods on top of an Array containing locks
3
+
4
+ class LocksDecorator < SimpleDelegator
5
+ def for_action(action)
6
+ compact.select do |lock|
7
+ lock.action == action.to_sym
8
+ end
9
+ end
10
+
11
+ def for_subject_type(subject_type)
12
+ compact.select do |lock|
13
+ lock.subject_type == subject_type.to_s
14
+ end
15
+ end
16
+
17
+ def for_subject_types(subject_types)
18
+ subject_types = Array(subject_types).map(&:to_s)
19
+ compact.select do |lock|
20
+ subject_types.include?(lock.subject_type)
21
+ end
22
+ end
23
+
24
+ def for_subject_id(subject_id)
25
+ compact.select do |lock|
26
+ lock.subject_id == BSON::ObjectId.from_string(subject_id)
27
+ end
28
+ end
29
+
30
+ def for_subject(subject)
31
+ compact.select do |lock|
32
+ lock.subject_type == subject.class.model_name &&
33
+ lock.subject_id == subject.id
34
+ end
35
+ end
36
+
37
+ def class_locks
38
+ compact.select(&:class_lock?)
39
+ end
40
+
41
+ def id_locks
42
+ compact.select(&:id_lock?)
43
+ end
44
+ end
45
+ end
@@ -3,7 +3,9 @@ module MongoidAbility
3
3
  def self.included(base)
4
4
  base.extend ClassMethods
5
5
  base.class_eval do
6
- delegate :can?, :cannot?, to: :ability
6
+ delegate :can?, :cannot?,
7
+ to: :ability
8
+
7
9
  before_save :cleanup_locks
8
10
  end
9
11
  end
@@ -19,14 +21,17 @@ module MongoidAbility
19
21
  end
20
22
 
21
23
  def locks_relation
24
+ return unless respond_to?(self.class.locks_relation_name)
22
25
  send(self.class.locks_relation_name)
23
26
  end
24
27
 
25
28
  def locks_relation=(val)
29
+ return unless respond_to?("#{self.class.locks_relation_name}=")
26
30
  send "#{self.class.locks_relation_name}=", val
27
31
  end
28
32
 
29
33
  def inherit_from_relation
34
+ return unless respond_to?(self.class.inherit_from_relation_name)
30
35
  send(self.class.inherit_from_relation_name)
31
36
  end
32
37
 
@@ -36,14 +41,29 @@ module MongoidAbility
36
41
 
37
42
  def has_lock?(lock)
38
43
  @has_lock ||= {}
39
- @has_lock[lock] ||= locks_relation.where(action: lock.action, subject_type: lock.subject_type, subject_id: lock.subject_id.presence).cache.exists?
44
+
45
+ return @has_lock[lock.id.to_s] if @has_lock.key?(lock.id.to_s)
46
+
47
+ @has_lock[lock.id.to_s] ||= begin
48
+ locks_relation.where(
49
+ subject_type: lock.subject_type,
50
+ subject_id: lock.subject_id.presence,
51
+ action: lock.action,
52
+ options: lock.options
53
+ ).exists?
54
+ end
40
55
  end
41
56
 
42
57
  private
43
58
 
44
59
  def cleanup_locks
45
60
  locks_relation.select(&:open?).each do |lock|
46
- lock.destroy if locks_relation.where(action: lock.action, subject_type: lock.subject_type, subject_id: lock.subject_id).any?(&:closed?)
61
+ lock.destroy if locks_relation.where(
62
+ subject_type: lock.subject_type,
63
+ subject_id: lock.subject_id.presence,
64
+ action: lock.action,
65
+ options: lock.options
66
+ ).any?(&:closed?)
47
67
  end
48
68
  end
49
69
  end
@@ -3,12 +3,18 @@ module MongoidAbility
3
3
  def self.included(base)
4
4
  base.extend ClassMethods
5
5
  base.class_eval do
6
+ # always set the _type field as it is used by :accessible_by queries
7
+ field :_type, type: String, default: model_name
6
8
  end
7
9
  end
8
10
 
9
11
  module ClassMethods
10
12
  def default_locks
11
- @default_locks ||= []
13
+ @default_locks ||= LocksDecorator.new([])
14
+ end
15
+
16
+ def reset_default_locks!
17
+ @default_locks = LocksDecorator.new([])
12
18
  end
13
19
 
14
20
  def default_locks=(locks)
@@ -19,16 +25,14 @@ module MongoidAbility
19
25
  lock = lock_cls.new(subject_type: to_s, action: action, outcome: outcome, options: options)
20
26
 
21
27
  # remove any existing locks
22
- if existing_lock = default_locks.detect { |l| l.action == lock.action }
28
+ if existing_lock = default_locks.detect { |l| l.action == lock.action && l.options == lock.options }
23
29
  default_locks.delete(existing_lock)
24
30
  end
25
31
 
26
32
  # add new lock
27
- default_locks.push lock
33
+ default_locks.push(lock)
28
34
  end
29
35
 
30
- # ---------------------------------------------------------------------
31
-
32
36
  def self_and_ancestors_with_default_locks
33
37
  ancestors.select { |a| a.is_a?(Class) && a.respond_to?(:default_locks) }
34
38
  end
@@ -45,24 +49,8 @@ module MongoidAbility
45
49
  self_and_ancestors_with_default_locks.last
46
50
  end
47
51
 
48
- # ---------------------------------------------------------------------
49
-
50
- def default_lock_for_action(action)
51
- default_locks.detect { |lock| lock.action == action.to_sym }
52
- end
53
-
54
52
  def has_default_lock_for_action?(action)
55
- default_lock_for_action(action).present?
56
- end
57
-
58
- # ---------------------------------------------------------------------
59
-
60
- def accessible_by(ability, action = :read, options = {})
61
- AccessibleQueryBuilder.call(self, ability, action, options)
62
- end
63
-
64
- def values_for_accessible_query(ability, action = :read, options = {})
65
- ValuesForAccessibleQuery.call(self, ability, action, options)
53
+ default_locks.for_action(action).present?
66
54
  end
67
55
  end
68
56
  end
@@ -1,3 +1,3 @@
1
1
  module MongoidAbility
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -0,0 +1,102 @@
1
+ require 'test_helper'
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ describe MongoidAdapter do
6
+ describe '.accessible_by' do
7
+ let(:owner) { MyOwner.new }
8
+ let(:ability) { MongoidAbility::Ability.new(owner) }
9
+ let(:subject1) { MySubject.new }
10
+
11
+ before do
12
+ subject1.save!
13
+ subject2.save!
14
+ end
15
+
16
+ describe 'Boolean' do
17
+ let(:subject2) { MySubject.new(override: true) }
18
+
19
+ describe 'positive' do
20
+ before(:all) { MySubject.default_lock MyLock, :read, true, override: true }
21
+
22
+ it { MySubject.accessible_by(ability).wont_include subject1 }
23
+ it { MySubject.accessible_by(ability).must_include subject2 }
24
+ end
25
+
26
+ describe 'negative' do
27
+ before(:all) do
28
+ MySubject.default_lock MyLock, :read, true
29
+ MySubject.default_lock MyLock, :read, false, override: true
30
+ end
31
+
32
+ it { MySubject.accessible_by(ability).must_include subject1 }
33
+ it { MySubject.accessible_by(ability).wont_include subject2 }
34
+ end
35
+ end
36
+
37
+ describe 'String' do
38
+ let(:subject2) { MySubject.new(str_val: "Jan Tschichold") }
39
+
40
+ describe 'positive' do
41
+ before(:all) { MySubject.default_lock MyLock, :read, true, str_val: 'Jan Tschichold' }
42
+
43
+ it { MySubject.accessible_by(ability).wont_include subject1 }
44
+ it { MySubject.accessible_by(ability).must_include subject2 }
45
+ end
46
+
47
+ describe 'negative' do
48
+ before(:all) do
49
+ MySubject.default_lock MyLock, :read, true
50
+ MySubject.default_lock MyLock, :read, false, str_val: 'Jan Tschichold'
51
+ end
52
+
53
+ it { MySubject.accessible_by(ability).must_include subject1 }
54
+ it { MySubject.accessible_by(ability).wont_include subject2 }
55
+ end
56
+ end
57
+
58
+ describe 'Regexp' do
59
+ let(:subject2) { MySubject.new(str_val: "Jan Tschichold") }
60
+
61
+ describe 'positive' do
62
+ before(:all) { MySubject.default_lock MyLock, :read, true, str_val: /tschichold/i }
63
+
64
+ it { MySubject.accessible_by(ability).wont_include subject1 }
65
+ it { MySubject.accessible_by(ability).must_include subject2 }
66
+ end
67
+
68
+ describe 'negative' do
69
+ before(:all) do
70
+ MySubject.default_lock MyLock, :read, true
71
+ MySubject.default_lock MyLock, :read, false, str_val: /tschichold/i
72
+ end
73
+
74
+ it { MySubject.accessible_by(ability).must_include subject1 }
75
+ it { MySubject.accessible_by(ability).wont_include subject2 }
76
+ end
77
+ end
78
+
79
+ describe 'Array' do
80
+ let(:subject2) { MySubject.new(str_val: "John") }
81
+
82
+ describe 'positive' do
83
+ before(:all) { MySubject.default_lock MyLock, :read, true, str_val: %w(John Paul George Ringo) }
84
+
85
+ it { MySubject.accessible_by(ability).wont_include subject1 }
86
+ it { MySubject.accessible_by(ability).must_include subject2 }
87
+ end
88
+
89
+ describe 'negative' do
90
+ before(:all) do
91
+ MySubject.default_lock MyLock, :read, true
92
+ MySubject.default_lock MyLock, :read, false, str_val: %w(John Paul George Ringo)
93
+ end
94
+
95
+ it { MySubject.accessible_by(ability).must_include subject1 }
96
+ it { MySubject.accessible_by(ability).wont_include subject2 }
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,207 @@
1
+ require 'test_helper'
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ describe MongoidAdapter do
6
+ describe '.accessible_by' do
7
+ let(:my_subject) { MySubject.new }
8
+ let(:my_subject1) { MySubject1.new }
9
+ let(:my_subject11) { MySubject11.new }
10
+ let(:my_subject2) { MySubject2.new }
11
+ let(:my_subject21) { MySubject21.new }
12
+
13
+ let(:role_1) { MyRole.new }
14
+ let(:role_2) { MyRole.new }
15
+ let(:owner) { MyOwner.new(my_roles: [role_1, role_2]) }
16
+ let(:ability) { MongoidAbility::Ability.new(owner) }
17
+
18
+ before do
19
+ my_subject.save!
20
+ my_subject1.save!
21
+ my_subject11.save!
22
+ my_subject2.save!
23
+ my_subject21.save!
24
+ end
25
+
26
+ describe 'subject type locks' do
27
+ describe 'default open locks' do
28
+ before { MySubject.default_lock MyLock, :read, true }
29
+
30
+ it { MySubject.accessible_by(ability, :read).to_a.must_include my_subject }
31
+ it { MySubject.accessible_by(ability, :read).to_a.must_include my_subject1 }
32
+ it { MySubject.accessible_by(ability, :read).to_a.must_include my_subject2 }
33
+
34
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include my_subject }
35
+ it { MySubject1.accessible_by(ability, :read).to_a.must_include my_subject1 }
36
+ it { MySubject1.accessible_by(ability, :read).to_a.must_include my_subject2 }
37
+
38
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include my_subject }
39
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include my_subject1 }
40
+ it { MySubject2.accessible_by(ability, :read).to_a.must_include my_subject2 }
41
+ end
42
+
43
+ describe 'default closed locks' do
44
+ before { MySubject.default_lock MyLock, :read, false }
45
+
46
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject }
47
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject1 }
48
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject2 }
49
+
50
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include my_subject }
51
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include my_subject1 }
52
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include my_subject2 }
53
+
54
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include my_subject }
55
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include my_subject1 }
56
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include my_subject2 }
57
+
58
+ it { MySubject.accessible_by(ability, :read).selector.must_equal({}) }
59
+ end
60
+
61
+ describe 'default combined locks' do
62
+ before(:all) do
63
+ MySubject.default_lock MyLock, :read, false
64
+ MySubject1.default_lock MyLock, :read, true
65
+ MySubject2.default_lock MyLock, :read, false
66
+ end
67
+
68
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject }
69
+ it { MySubject.accessible_by(ability, :read).to_a.must_include my_subject1 }
70
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject2 }
71
+
72
+ it { MySubject1.accessible_by(ability, :read).to_a.must_include my_subject1 }
73
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include my_subject2 }
74
+
75
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include my_subject2 }
76
+ end
77
+
78
+ describe 'combined locks' do
79
+ before(:all) do
80
+ MySubject.default_lock MyLock, :read, true
81
+ end
82
+
83
+ let(:lock) { MyLock.new(subject_type: MySubject1, action: :read, outcome: false) }
84
+ let(:role) { MyRole.new(my_locks: [lock]) }
85
+ let(:owner) { MyOwner.new(my_roles: [role]) }
86
+
87
+ it { MySubject.accessible_by(ability, :read).to_a.must_include my_subject }
88
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject1 }
89
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject2 }
90
+
91
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include my_subject1 }
92
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include my_subject2 }
93
+
94
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include my_subject2 }
95
+ end
96
+ end
97
+
98
+ describe 'conditions locks' do
99
+ describe 'subject locks' do
100
+ describe 'closed id locks' do
101
+ let(:lock) { MyLock.new(subject: my_subject, action: :read, outcome: false) }
102
+ let(:role_1) { MyRole.new(my_locks: [lock]) }
103
+
104
+ before(:all) do
105
+ MySubject.default_lock MyLock, :read, true
106
+ end
107
+
108
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject }
109
+ it { MySubject.accessible_by(ability, :read).to_a.must_include my_subject1 }
110
+ it { MySubject.accessible_by(ability, :read).to_a.must_include my_subject2 }
111
+ end
112
+
113
+ describe 'open id locks' do
114
+ let(:lock) { MyLock.new(subject: my_subject1, action: :read, outcome: true) }
115
+ let(:role_1) { MyRole.new(my_locks: [lock]) }
116
+
117
+ before(:all) do
118
+ MySubject.default_lock MyLock, :read, false
119
+ end
120
+
121
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include my_subject }
122
+ it { MySubject1.accessible_by(ability, :read).to_a.must_include my_subject1 }
123
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include my_subject2 }
124
+ end
125
+
126
+ describe 'closed types & open ids' do
127
+ let(:lock_1) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
128
+ let(:lock_2) { MyLock.new(subject: my_subject, action: :read, outcome: true) }
129
+ let(:lock_3) { MyLock.new(subject: my_subject1, action: :read, outcome: true) }
130
+
131
+ let(:owner) { MyOwner.new(my_locks: [lock_1, lock_2, lock_3]) }
132
+
133
+ it { MySubject.accessible_by(ability, :read).must_include my_subject }
134
+ it { MySubject.accessible_by(ability, :read).must_include my_subject1 }
135
+ end
136
+ end
137
+
138
+ describe 'arbitrary conditions' do
139
+ describe 'positive' do
140
+ let(:my_subject1) { MySubject1.new(override: true) }
141
+
142
+ before(:all) { MySubject.default_lock MyLock, :read, true, override: true }
143
+
144
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include(my_subject) }
145
+ it { MySubject.accessible_by(ability, :read).to_a.must_include(my_subject1) }
146
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include(my_subject2) }
147
+
148
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include(my_subject) }
149
+ it { MySubject1.accessible_by(ability, :read).to_a.must_include(my_subject1) }
150
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include(my_subject2) }
151
+
152
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include(my_subject) }
153
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include(my_subject1) }
154
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include(my_subject2) }
155
+ end
156
+
157
+ describe 'negative' do
158
+ let(:my_subject1) { MySubject1.new(override: true) }
159
+
160
+ before(:all) do
161
+ MySubject.default_lock MyLock, :read, true
162
+ MySubject.default_lock MyLock, :read, false, override: true
163
+ end
164
+
165
+ it { MySubject.accessible_by(ability, :read).to_a.must_include(my_subject) }
166
+ it { MySubject.accessible_by(ability, :read).to_a.wont_include(my_subject1) }
167
+ it { MySubject.accessible_by(ability, :read).to_a.must_include(my_subject2) }
168
+
169
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include(my_subject) }
170
+ it { MySubject1.accessible_by(ability, :read).to_a.wont_include(my_subject1) }
171
+ it { MySubject1.accessible_by(ability, :read).to_a.must_include(my_subject2) }
172
+
173
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include(my_subject) }
174
+ it { MySubject2.accessible_by(ability, :read).to_a.wont_include(my_subject1) }
175
+ it { MySubject2.accessible_by(ability, :read).to_a.must_include(my_subject2) }
176
+ end
177
+ end
178
+ end
179
+
180
+ describe 'prefix' do
181
+ let(:lock_1) { MyLock.new(subject: my_subject1, action: :read, outcome: true) }
182
+ let(:lock_2) { MyLock.new(subject: my_subject2, action: :read, outcome: false) }
183
+ let(:role_1) { MyRole.new(my_locks: [lock_1, lock_2]) }
184
+
185
+ let(:prefix) { :subject }
186
+ let(:selector) { MySubject.accessible_by(ability, :read, prefix: prefix).selector }
187
+
188
+ before(:all) do
189
+ MySubject.default_lock MyLock, :read, true
190
+ end
191
+
192
+ it 'allows to pass prefix' do
193
+ selector.must_equal(
194
+ '$and' => [
195
+ { '$or' => [
196
+ { 'subject_type' => { '$nin' => [] } },
197
+ { 'subject_id' => my_subject1.id }
198
+ ] },
199
+ { 'subject_id' => { '$ne' => my_subject2.id } }
200
+ ]
201
+ )
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end