mongoid_ability 1.0.0 → 2.0.0

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.
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