mongoid_ability 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2419970ae62d412e5779447e3b68ad88999e65fc
4
- data.tar.gz: 2850d0cc5ce00717c5aa9ee21af855a50f01edbe
3
+ metadata.gz: f894c51e60acf940ca8b92ec9f7402df3b852bf9
4
+ data.tar.gz: d923b2dfee15aee43d0aab8535adbd4ad44ff6f2
5
5
  SHA512:
6
- metadata.gz: 55f72d123b8ba7dc2dd788c2403a1616b8b73516cc96a36d4a7600294675d548390dec31fdb1274cf334306aba42e1ddc419d67b551eb26c5ff43cf48e81f239
7
- data.tar.gz: b16ce12d9d7ed6a604c3b2b4774018f9de5e1e193b5ccc7c0cf4bcc0fa9f00ca896cd59e32cff37020d6ebe803140742b6caef3d82c17f22cee986e9e3c25d4b
6
+ metadata.gz: 5a004c82655f15430cbb9421a3180a35d72375687f5301bb0fffd8b373055b87dd15ee9e187db4cd321ef7bede86c525f7a600030d088fdb31b019c654c51098
7
+ data.tar.gz: d8336fcfbb6a4f1ca8ede3e970dae45406b9cd575c83a78e5c48fda2ef02d6274ab06d218839ebe13b8a5c7ba71a121e40e2b2875ae074b5ee8d5f63a1d080c2
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
- cache: bundler
3
2
  script: 'bundle exec rake'
3
+ sudo: false
4
4
  rvm:
5
5
  - 2.1.2
6
6
  services:
@@ -11,4 +11,4 @@ notifications:
11
11
  recipients:
12
12
  - tomas.celizna@gmail.com
13
13
  on_failure: change
14
- on_success: never
14
+ on_success: never
data/README.md CHANGED
@@ -81,13 +81,6 @@ end
81
81
 
82
82
  The subject classes can be subclassed. Subclasses inherit the default locks (unless they override them), the resulting outcome being correctly calculated bottom-up the superclass chain.
83
83
 
84
- The subject also acquires a convenience `Mongoid::Criteria` named `.accessible_by`. This criteria can be used to query for subject based on the user's ability:
85
-
86
- ```ruby
87
- ability = MongoidAbility::Ability.new(current_user)
88
- MySubject.accessible_by(ability, :read, options={})
89
- ```
90
-
91
84
  ### Owner
92
85
 
93
86
  This `Ability` class supports two levels of inheritance (for example User and its Roles). The locks can be either embedded (via `.embeds_many`) or associated (via `.has_many`). Make sure to include the `as: :owner` option.
@@ -1,16 +1,35 @@
1
- # FIXME: this is extremely slow and not suitable for use, yet
2
-
3
1
  module MongoidAbility
4
2
  class AccessibleQueryBuilder < Struct.new(:base_class, :ability, :action, :options)
5
-
6
- def self.call *args
3
+ def self.call(*args)
7
4
  new(*args).call
8
5
  end
9
6
 
10
7
  # =====================================================================
11
8
 
9
+ # TODO: cleanup
12
10
  def call
13
- base_class_and_descendants.inject(base_criteria) { |criteria, cls| criteria.merge!(criteria_for_class(cls)) }
11
+ closed_classes = [] # [cls]
12
+ open_ids = [] # [cls, id]
13
+ closed_ids = [] # [id]
14
+
15
+ base_class_and_descendants.each do |cls|
16
+ closed_classes << cls.to_s if ability.cannot?(action, cls, options)
17
+
18
+ id_locks(cls).each do |lock|
19
+ if ability.can?(action, cls.new(_id: lock.subject_id), options)
20
+ open_ids << [cls.to_s, lock.subject_id]
21
+ else
22
+ closed_ids << lock.subject_id
23
+ end
24
+ end
25
+ end
26
+
27
+ closed_classes_condition = { :_type.nin => closed_classes }
28
+ open_ids_condition = { :_type.in => open_ids.map(&:first), :_id.in => open_ids.map(&:last) }
29
+ closed_ids_condition = { :_id.nin => closed_ids }
30
+ or_conditions = [ closed_classes_condition, open_ids_condition ].reject(&:blank?)
31
+
32
+ base_criteria.where( :$and => [ { :$or => or_conditions }, closed_ids_condition ])
14
33
  end
15
34
 
16
35
  private # =============================================================
@@ -25,16 +44,12 @@ module MongoidAbility
25
44
  @base_class_superclass ||= (base_class.ancestors_with_default_locks.last || base_class)
26
45
  end
27
46
 
28
- def base_class_descendants
29
- @base_class_descendants ||= ObjectSpace.each_object(Class).select{ |cls| cls < base_class_superclass }
47
+ def default_lock(_cls, action)
48
+ base_class_superclass.default_locks.detect { |l| l.action.to_s == action.to_s }
30
49
  end
31
50
 
32
51
  def base_class_and_descendants
33
- @base_class_and_descendants ||= [base_class].concat(base_class_descendants)
34
- end
35
-
36
- def hereditary?
37
- base_class_and_descendants.count > 1
52
+ @base_class_and_descendants ||= [base_class].concat(base_class.descendants)
38
53
  end
39
54
 
40
55
  # ---------------------------------------------------------------------
@@ -50,79 +65,21 @@ module MongoidAbility
50
65
 
51
66
  # ---------------------------------------------------------------------
52
67
 
53
- def owner_id_locks_for_subject_type cls
68
+ def id_locks(cls)
69
+ (Array(owner_id_locks_for_subject_type(cls)) + Array(inherited_from_relation_ids_locks_for_subject_type(cls))).flatten
70
+ end
71
+
72
+ def owner_id_locks_for_subject_type(cls)
54
73
  @owner_id_locks_for_subject_type ||= {}
55
74
  @owner_id_locks_for_subject_type[cls] ||= owner.locks_relation.id_locks.for_action(action).for_subject_type(cls.to_s)
56
75
  end
57
76
 
58
- def inherited_from_relation_ids_locks_for_subject_type cls
77
+ def inherited_from_relation_ids_locks_for_subject_type(cls)
59
78
  return [] unless inherited_from_relation
60
79
  @inherited_from_relation_ids_locks_for_subject_type ||= {}
61
- @inherited_from_relation_ids_locks_for_subject_type[cls] ||= inherited_from_relation.collect { |o|
80
+ @inherited_from_relation_ids_locks_for_subject_type[cls] ||= inherited_from_relation.collect do |o|
62
81
  o.locks_relation.id_locks.for_action(action).for_subject_type(cls.to_s)
63
- }.flatten
64
- end
65
-
66
- # ---------------------------------------------------------------------
67
-
68
- def role_has_open_id_lock? cls, subject_id
69
- @role_has_open_id_lock ||= {}
70
- @role_has_open_id_lock["#{cls}_#{subject_id}"] ||= begin
71
- inherited_from_relation_ids_locks_for_subject_type(cls).
72
- select{ |l| l.open?(options) }.
73
- map(&:subject_id).
74
- include?(subject_id)
75
- end
76
- end
77
-
78
- def owner_has_open_id_lock? cls, subject_id
79
- @owner_has_open_id_lock ||= {}
80
- @owner_has_open_id_lock["#{cls}_#{subject_id}"] ||= begin
81
- owner_id_locks_for_subject_type(cls).
82
- select{ |l| l.open?(options) }.
83
- map(&:subject_id).
84
- include?(subject_id)
85
- end
82
+ end.flatten
86
83
  end
87
-
88
- # ---------------------------------------------------------------------
89
-
90
- def criteria_for_class cls
91
- @criteria_for_class ||= {}
92
- @criteria_for_class[cls] ||= ability.can?(action, cls, options) ? exclude_criteria(cls) : include_criteria(cls)
93
- end
94
-
95
- def exclude_criteria cls
96
- @exclude_criteria ||= {}
97
- @exclude_criteria[cls] ||= begin
98
- id_locks = inherited_from_relation_ids_locks_for_subject_type(cls).select{ |l| l.closed?(options) }
99
- id_locks = id_locks.reject{ |lock| role_has_open_id_lock?(cls, lock.subject_id) }
100
- id_locks = id_locks.reject{ |lock| owner_has_open_id_lock?(cls, lock.subject_id) }
101
- id_locks += owner_id_locks_for_subject_type(cls).select{ |l| l.closed?(options) }
102
-
103
- excluded_ids = id_locks.map(&:subject_id).flatten
104
-
105
- conditions = { :_id.nin => excluded_ids }
106
- conditions = conditions.merge(_type: cls.to_s) if hereditary?
107
-
108
- base_criteria.or(conditions)
109
- end
110
- end
111
-
112
- def include_criteria cls
113
- @include_criteria ||= {}
114
- @include_criteria[cls] ||= begin
115
- id_locks = inherited_from_relation_ids_locks_for_subject_type(cls).select{ |l| l.open?(options) }
116
- id_locks += owner_id_locks_for_subject_type(cls).select{ |l| l.open?(options) }
117
-
118
- included_ids = id_locks.map(&:subject_id).flatten
119
-
120
- conditions = { :_id.in => included_ids }
121
- conditions = conditions.merge(_type: cls.to_s) if hereditary?
122
-
123
- base_criteria.or(conditions)
124
- end
125
- end
126
-
127
84
  end
128
85
  end
@@ -2,7 +2,7 @@ require 'mongoid'
2
2
 
3
3
  module MongoidAbility
4
4
  module Lock
5
- def self.included base
5
+ def self.included(base)
6
6
  base.extend ClassMethods
7
7
  base.class_eval do
8
8
  field :action, type: Symbol, default: :read
@@ -12,14 +12,14 @@ module MongoidAbility
12
12
  belongs_to :subject, polymorphic: true, touch: true
13
13
 
14
14
  # TODO: validate that action is defined on subject or its superclasses
15
- validates :action, presence: true, uniqueness: { scope: [ :subject_type, :subject_id, :outcome ] }
15
+ validates :action, presence: true, uniqueness: { scope: [:subject_type, :subject_id, :outcome] }
16
16
  validates :outcome, presence: true
17
17
 
18
- scope :for_action, -> action { where(action: action.to_sym) }
18
+ scope :for_action, -> (action) { where(action: action.to_sym) }
19
19
 
20
- scope :for_subject_type, -> subject_type { where(subject_type: subject_type.to_s) }
21
- scope :for_subject_id, -> subject_id { where(subject_id: subject_id.presence) }
22
- scope :for_subject, -> subject { where(subject_type: subject.class.model_name, subject_id: subject.id) }
20
+ scope :for_subject_type, -> (subject_type) { where(subject_type: subject_type.to_s) }
21
+ scope :for_subject_id, -> (subject_id) { where(subject_id: subject_id.presence) }
22
+ scope :for_subject, -> (subject) { where(subject_type: subject.class.model_name, subject_id: subject.id) }
23
23
 
24
24
  scope :class_locks, -> { where(subject_id: nil) }
25
25
  scope :id_locks, -> { ne(subject_id: nil) }
@@ -28,52 +28,50 @@ module MongoidAbility
28
28
 
29
29
  # =====================================================================
30
30
 
31
- # NOTE: override for more complicated results
32
- def calculated_outcome options={}
33
- outcome
34
- end
35
-
36
- # NOTE: override for more complicated results
37
- def conditions
38
- res = { _type: subject_type }
39
- res = res.merge(_id: subject_id) if subject_id.present?
40
- res = { '$not' => res } if calculated_outcome == false
41
- res
42
- end
43
-
44
- # calculates outcome as if this lock is not present
45
- def inherited_outcome options=default_options
46
- return calculated_outcome(options) unless owner.present?
47
- cloned_owner = owner.clone
48
- cloned_owner.locks_relation = cloned_owner.locks_relation - [self]
49
- MongoidAbility::Ability.new(cloned_owner).can? action, (subject.present? ? subject : subject_class), options
50
- end
31
+ concerning :LockType do
32
+ def class_lock?
33
+ !id_lock?
34
+ end
51
35
 
52
- # this is used when calculating inherited outcome
53
- def default_options
54
- {}
36
+ def id_lock?
37
+ subject_id.present?
38
+ end
55
39
  end
56
40
 
57
- # ---------------------------------------------------------------------
41
+ concerning :Outcome do
42
+ # NOTE: override for more complicated results
43
+ def calculated_outcome(_options = {})
44
+ outcome
45
+ end
58
46
 
59
- def subject_class
60
- subject_type.constantize
61
- end
47
+ def open?(options = {})
48
+ calculated_outcome(options) == true
49
+ end
62
50
 
63
- def open? options={}
64
- calculated_outcome(options) == true
51
+ def closed?(options = {})
52
+ !open?(options)
53
+ end
65
54
  end
66
55
 
67
- def closed? options={}
68
- !open?(options)
69
- end
56
+ concerning :InheritedOutcome do
57
+ # calculates outcome as if this lock is not present
58
+ def inherited_outcome(options = default_options)
59
+ return calculated_outcome(options) unless owner.present?
60
+ cloned_owner = owner.clone
61
+ cloned_owner.locks_relation = cloned_owner.locks_relation - [self]
62
+ MongoidAbility::Ability.new(cloned_owner).can? action, (subject.present? ? subject : subject_class), options
63
+ end
70
64
 
71
- def class_lock?
72
- !id_lock?
65
+ # used when calculating inherited outcome
66
+ def default_options
67
+ {}
68
+ end
73
69
  end
74
70
 
75
- def id_lock?
76
- subject_id.present?
71
+ concerning :Subject do
72
+ def subject_class
73
+ subject_type.constantize
74
+ end
77
75
  end
78
76
  end
79
77
  end
@@ -1,6 +1,5 @@
1
1
  module MongoidAbility
2
2
  class ResolveOwnerLocks < ResolveLocks
3
-
4
3
  def call
5
4
  # FIXME: this is not a very nice fix
6
5
  return unless owner.respond_to?(:locks_relation)
@@ -12,17 +11,16 @@ module MongoidAbility
12
11
  # return outcome if owner defines lock for id
13
12
  if subject.present?
14
13
  id_locks = locks_for_subject_type.id_locks.for_subject_id(subject_id)
15
- return false if id_locks.any?{ |l| l.closed?(options) }
16
- return true if id_locks.any?{ |l| l.open?(options) }
14
+ return false if id_locks.any? { |l| l.closed?(options) }
15
+ return true if id_locks.any? { |l| l.open?(options) }
17
16
  end
18
17
 
19
18
  # return outcome if owner defines lock for subject_type
20
19
  class_locks = locks_for_subject_type.class_locks
21
- return false if class_locks.class_locks.any?{ |l| l.closed?(options) }
22
- return true if class_locks.class_locks.any?{ |l| l.open?(options) }
20
+ return false if class_locks.class_locks.any? { |l| l.closed?(options) }
21
+ return true if class_locks.class_locks.any? { |l| l.open?(options) }
23
22
 
24
23
  nil
25
24
  end
26
-
27
25
  end
28
26
  end
@@ -1,7 +1,6 @@
1
1
  module MongoidAbility
2
2
  module Subject
3
-
4
- def self.included base
3
+ def self.included(base)
5
4
  base.extend ClassMethods
6
5
  base.class_eval do
7
6
  end
@@ -12,21 +11,15 @@ module MongoidAbility
12
11
  @default_locks ||= []
13
12
  end
14
13
 
15
- def default_locks= locks
14
+ def default_locks=(locks)
16
15
  @default_locks = locks
17
16
  end
18
17
 
19
- def default_lock lock_cls, action, outcome, options={}
20
- # unless is_root_class?
21
- # unless root_class.has_default_lock_for_action?(action)
22
- # raise StandardError, "action is not defined on root class (#{root_class})"
23
- # end
24
- # end
25
-
26
- lock = lock_cls.new( subject_type: self.to_s, action: action, outcome: outcome, options: options )
18
+ def default_lock(lock_cls, action, outcome, options = {})
19
+ lock = lock_cls.new(subject_type: to_s, action: action, outcome: outcome, options: options)
27
20
 
28
21
  # remove any existing locks
29
- if existing_lock = default_locks.detect{ |l| l.action == lock.action }
22
+ if existing_lock = default_locks.detect { |l| l.action == lock.action }
30
23
  default_locks.delete(existing_lock)
31
24
  end
32
25
 
@@ -54,20 +47,19 @@ module MongoidAbility
54
47
 
55
48
  # ---------------------------------------------------------------------
56
49
 
57
- def default_lock_for_action action
58
- default_locks.detect{ |lock| lock.action == action.to_sym }
50
+ def default_lock_for_action(action)
51
+ default_locks.detect { |lock| lock.action == action.to_sym }
59
52
  end
60
53
 
61
- def has_default_lock_for_action? action
54
+ def has_default_lock_for_action?(action)
62
55
  default_lock_for_action(action).present?
63
56
  end
64
57
 
65
58
  # ---------------------------------------------------------------------
66
59
 
67
- def accessible_by ability, action=:read, options={}
60
+ def accessible_by(ability, action = :read, options = {})
68
61
  AccessibleQueryBuilder.call(self, ability, action, options)
69
62
  end
70
63
  end
71
-
72
64
  end
73
65
  end
@@ -1,3 +1,3 @@
1
1
  module MongoidAbility
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency "cancancan", "~> 1.9"
22
22
  spec.add_dependency "mongoid", "~> 5.0"
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "bundler"
25
25
  spec.add_development_dependency "coveralls"
26
26
  spec.add_development_dependency "database_cleaner", ">= 1.5.1"
27
27
  spec.add_development_dependency "guard"
@@ -1,24 +1,21 @@
1
- require "test_helper"
1
+ require 'test_helper'
2
2
 
3
3
  module MongoidAbility
4
4
  describe AccessibleQueryBuilder do
5
-
6
5
  let(:base_class) { MySubject }
7
-
8
6
  let(:owner) { MyOwner.new }
9
7
  let(:ability) { Ability.new(owner) }
10
-
11
8
  let(:action) { :read }
12
-
13
- subject { AccessibleQueryBuilder.call(base_class, ability, action) }
9
+ let(:options) { Hash.new }
14
10
 
15
11
  before do
16
- MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
12
+ MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: true) ]
17
13
  end
18
14
 
15
+ subject { AccessibleQueryBuilder.call(base_class, ability, action, options) }
16
+
19
17
  it 'returns Mongoid::Criteria' do
20
18
  subject.must_be_kind_of Mongoid::Criteria
21
19
  end
22
-
23
20
  end
24
21
  end
@@ -2,14 +2,14 @@ require 'test_helper'
2
2
 
3
3
  module MongoidAbility
4
4
  describe Lock do
5
-
6
5
  subject { MyLock.new }
7
6
  let(:my_subject) { MySubject.new }
8
7
  let(:inherited_lock) { MyLock1.new }
8
+
9
9
  # ---------------------------------------------------------------------
10
10
 
11
11
  before do
12
- MySubject.default_locks = [ MyLock.new(action: :read, outcome: true), MyLock.new(action: :update, outcome: false) ]
12
+ MySubject.default_locks = [MyLock.new(action: :read, outcome: true), MyLock.new(action: :update, outcome: false)]
13
13
  end
14
14
 
15
15
  # ---------------------------------------------------------------------
@@ -27,13 +27,16 @@ module MongoidAbility
27
27
  subject.must_respond_to :open?
28
28
  subject.open?.must_equal false
29
29
  end
30
+
30
31
  it '#closed?' do
31
32
  subject.must_respond_to :closed?
32
33
  subject.closed?.must_equal true
33
34
  end
35
+
34
36
  it '#class_lock?' do
35
37
  subject.must_respond_to :class_lock?
36
38
  end
39
+
37
40
  it '#id_lock?' do
38
41
  subject.must_respond_to :id_lock?
39
42
  end
@@ -74,48 +77,9 @@ module MongoidAbility
74
77
  end
75
78
 
76
79
  it 'returns calculated_outcome for default locks' do
77
- lock = MySubject.default_locks.detect{ |l| l.action == :read }
80
+ lock = MySubject.default_locks.detect { |l| l.action == :read }
78
81
  lock.inherited_outcome.must_equal true
79
82
  end
80
83
  end
81
-
82
- # ---------------------------------------------------------------------
83
-
84
- describe '#criteria' do
85
- let(:open_subject_type_lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: true) }
86
- let(:closed_subject_type_lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
87
-
88
- let(:open_subject_lock) { MyLock.new(subject: my_subject, action: :read, outcome: true) }
89
- let(:closed_subject_lock) { MyLock.new(subject: my_subject, action: :read, outcome: false) }
90
-
91
- it 'returns conditions Hash' do
92
- open_subject_type_lock.conditions.must_be_kind_of Hash
93
- closed_subject_type_lock.conditions.must_be_kind_of Hash
94
-
95
- open_subject_lock.conditions.must_be_kind_of Hash
96
- closed_subject_lock.conditions.must_be_kind_of Hash
97
- end
98
-
99
- describe 'when open' do
100
- it 'includes subject_type' do
101
- open_subject_type_lock.conditions.must_equal({ _type: open_subject_type_lock.subject_type })
102
- end
103
-
104
- it 'includes id' do
105
- open_subject_lock.conditions.must_equal({ _type: open_subject_type_lock.subject_type, _id: open_subject_lock.subject_id })
106
- end
107
- end
108
-
109
- describe 'when closed' do
110
- it 'excludes subject_type' do
111
- closed_subject_type_lock.conditions.must_equal({ '$not' => { _type: open_subject_type_lock.subject_type }})
112
- end
113
-
114
- it 'includes id' do
115
- closed_subject_lock.conditions.must_equal({ '$not' => { _type: open_subject_type_lock.subject_type, _id: open_subject_lock.subject_id }})
116
- end
117
- end
118
- end
119
-
120
84
  end
121
85
  end
@@ -13,13 +13,13 @@ module MongoidAbility
13
13
  # ---------------------------------------------------------------------
14
14
 
15
15
  before do
16
- MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: true) ]
16
+ MySubject.default_locks = [MyLock.new(subject_type: MySubject, action: :read, outcome: true)]
17
17
  end
18
18
 
19
19
  # ---------------------------------------------------------------------
20
20
 
21
21
  describe 'when lock for subject' do
22
- before { owner.my_locks = [ subject_lock ] }
22
+ before { owner.my_locks = [subject_lock] }
23
23
 
24
24
  it 'applies it' do
25
25
  ability.can?(:read, subject.class).must_equal true
@@ -30,13 +30,12 @@ module MongoidAbility
30
30
  # ---------------------------------------------------------------------
31
31
 
32
32
  describe 'when lock for subject type' do
33
- before { owner.my_locks = [ subject_type_lock ] }
33
+ before { owner.my_locks = [subject_type_lock] }
34
34
 
35
35
  it 'applies it' do
36
36
  ability.can?(:read, subject.class).must_equal false
37
37
  ability.can?(:read, subject).must_equal false
38
38
  end
39
39
  end
40
-
41
40
  end
42
41
  end
@@ -41,6 +41,5 @@ module MongoidAbility
41
41
  owner.has_lock?(other_lock).must_equal false
42
42
  end
43
43
  end
44
-
45
44
  end
46
45
  end
@@ -2,134 +2,138 @@ require 'test_helper'
2
2
 
3
3
  module MongoidAbility
4
4
  describe '.accessible_by' do
5
+ let(:my_subject) { MySubject.create! }
6
+ let(:my_subject1) { MySubject1.create! }
7
+ let(:my_subject2) { MySubject2.create! }
5
8
 
6
9
  let(:role_1) { MyRole.new }
7
10
  let(:role_2) { MyRole.new }
8
- let(:owner) { MyOwner.new(my_roles: [ role_1, role_2 ]) }
11
+ let(:owner) { MyOwner.new(my_roles: [role_1, role_2]) }
9
12
  let(:ability) { Ability.new(owner) }
10
13
 
11
- before do
12
- MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: true) ]
13
- MySubject1.default_locks = []
14
+ # =====================================================================
15
+
16
+ describe 'default open locks' do
17
+ before do
18
+ # NOTE: we might need to use the .default_lock macro in case we propagate down directly
19
+ MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :update, outcome: true) ]
20
+ MySubject1.default_locks = []
21
+ MySubject2.default_locks = []
22
+
23
+ my_subject
24
+ my_subject1
25
+ my_subject2
26
+ end
27
+
28
+ it 'propagates from superclass to all subclasses' do
29
+ MySubject.accessible_by(ability, :update).to_a.must_include my_subject
30
+ MySubject.accessible_by(ability, :update).to_a.must_include my_subject1
31
+ MySubject.accessible_by(ability, :update).to_a.must_include my_subject2
14
32
 
15
- @my_subject = MySubject.create!
16
- @my_subject_1 = MySubject1.create!
33
+ MySubject1.accessible_by(ability, :update).to_a.wont_include my_subject
34
+ MySubject1.accessible_by(ability, :update).to_a.must_include my_subject1
35
+ MySubject1.accessible_by(ability, :update).to_a.must_include my_subject2
36
+
37
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject
38
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject1
39
+ MySubject2.accessible_by(ability, :update).to_a.must_include my_subject2
40
+ end
17
41
  end
18
42
 
19
- # =====================================================================
43
+ describe 'default closed locks' do
44
+ before do
45
+ # NOTE: we might need to use the .default_lock macro in case we propagate down directly
46
+ MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :update, outcome: false) ]
47
+ MySubject1.default_locks = []
48
+ MySubject2.default_locks = []
20
49
 
21
- describe 'default locks' do
22
- describe 'when open' do
23
- it 'returns everything' do
24
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject
25
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject_1
26
- end
50
+ my_subject
51
+ my_subject1
52
+ my_subject2
27
53
  end
28
54
 
29
- describe 'when closed' do
30
- before do
31
- MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
32
- MySubject1.default_locks = []
33
- end
55
+ it 'propagates from superclass to all subclasses' do
56
+ MySubject.accessible_by(ability, :update).to_a.wont_include my_subject
57
+ MySubject.accessible_by(ability, :update).to_a.wont_include my_subject1
58
+ MySubject.accessible_by(ability, :update).to_a.wont_include my_subject2
34
59
 
35
- it 'returns nothing' do
36
- MySubject.accessible_by(ability, :read).to_a.wont_include @my_subject
37
- MySubject.accessible_by(ability, :read).to_a.wont_include @my_subject_1
38
- end
60
+ MySubject1.accessible_by(ability, :update).to_a.wont_include my_subject
61
+ MySubject1.accessible_by(ability, :update).to_a.wont_include my_subject1
62
+ MySubject1.accessible_by(ability, :update).to_a.wont_include my_subject2
63
+
64
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject
65
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject1
66
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject2
39
67
  end
40
68
  end
41
69
 
42
- # ---------------------------------------------------------------------
70
+ describe 'default combined locks' do
71
+ before do
72
+ # NOTE: we might need to use the .default_lock macro in case we propagate down directly
73
+ MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :update, outcome: false) ]
74
+ MySubject1.default_locks = [ MyLock.new(subject_type: MySubject, action: :update, outcome: true) ]
75
+ MySubject2.default_locks = [ MyLock.new(subject_type: MySubject, action: :update, outcome: false) ]
43
76
 
44
- describe 'subject_type lock' do
45
- describe 'on roles' do
46
- it 'overrides default lock' do
47
- role_1.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
48
- MySubject.accessible_by(ability, :read).to_a.must_be :empty?
49
- end
50
-
51
- it 'takes the most permissive of roles' do
52
- role_1.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
53
- role_2.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: true) ]
54
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject
55
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject_1
56
- end
77
+ my_subject
78
+ my_subject1
79
+ my_subject2
57
80
  end
58
81
 
59
- describe 'on user' do
60
- it 'overrides default lock' do
61
- owner.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
62
- MySubject.accessible_by(ability, :read).to_a.must_be :empty?
63
- end
64
-
65
- it 'overrides role locks' do
66
- role_1.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
67
- owner.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: true) ]
68
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject
69
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject_1
70
- end
82
+ it 'propagates from superclass to all subclasses' do
83
+ MySubject.accessible_by(ability, :update).to_a.wont_include my_subject
84
+ MySubject.accessible_by(ability, :update).to_a.must_include my_subject1
85
+ MySubject.accessible_by(ability, :update).to_a.wont_include my_subject2
86
+
87
+ MySubject1.accessible_by(ability, :update).to_a.wont_include my_subject
88
+ MySubject1.accessible_by(ability, :update).to_a.must_include my_subject1
89
+ MySubject1.accessible_by(ability, :update).to_a.wont_include my_subject2
90
+
91
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject
92
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject1
93
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject2
71
94
  end
72
95
  end
73
96
 
74
97
  # ---------------------------------------------------------------------
75
98
 
76
- describe 'subject_id lock' do
77
- describe 'on roles' do
78
- it 'overrides default lock' do
79
- role_1.my_locks = [ MyLock.new(subject: @my_subject, action: :read, outcome: false) ]
80
- MySubject1.accessible_by(ability, :read).to_a.wont_include @my_subject
81
- end
82
-
83
- it 'overrides default negative lock' do
84
- MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
85
- role_1.my_locks = [ MyLock.new(subject: @my_subject, action: :read, outcome: true) ]
86
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject
87
- end
88
-
89
- it 'overrides subject_type lock' do
90
- role_1.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
91
- role_2.my_locks = [ MyLock.new(subject: @my_subject, action: :read, outcome: true) ]
92
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject
93
- end
94
-
95
- it 'takes the most permissive of roles' do
96
- role_1.my_locks = [ MyLock.new(subject: @my_subject, action: :read, outcome: true) ]
97
- role_2.my_locks = [ MyLock.new(subject: @my_subject, action: :read, outcome: false) ]
98
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject
99
- end
100
-
101
- describe 'for subclasses' do
102
- it 'overrides default negative lock' do
103
- MySubject.default_locks = [ MyLock.new(subject_type: MySubject1, action: :read, outcome: false) ]
104
- role_1.my_locks = [ MyLock.new(subject: @my_subject_1, action: :read, outcome: true) ]
105
- MySubject.accessible_by(ability, :read).to_a.wont_include @my_subject
106
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject_1
107
- end
108
- end
99
+ describe 'closed id locks' do
100
+ let(:role_1) { MyRole.new(my_locks: [ MyLock.new(subject: my_subject, action: :update, outcome: false) ]) }
101
+
102
+ before do
103
+ MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :update, outcome: true) ]
104
+ MySubject1.default_locks = []
105
+ MySubject2.default_locks = []
106
+
107
+ my_subject
108
+ my_subject1
109
+ my_subject2
109
110
  end
110
111
 
111
- describe 'on user' do
112
- it 'overrides default lock' do
113
- owner.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
114
- MySubject.accessible_by(ability, :read).to_a.must_be :empty?
115
- end
116
-
117
- it 'overrides subject_type lock' do
118
- owner.my_locks = [
119
- MyLock.new(subject_type: MySubject, action: :read, outcome: false),
120
- MyLock.new(subject_type: MySubject1, action: :read, outcome: true)
121
- ]
122
- MySubject.accessible_by(ability, :read).to_a.wont_include @my_subject
123
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject_1
124
- end
125
-
126
- it 'overrides role locks' do
127
- role_1.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: false) ]
128
- owner.my_locks = [ MyLock.new(subject_type: MySubject, action: :read, outcome: true) ]
129
- MySubject.accessible_by(ability, :read).to_a.must_include @my_subject
130
- end
112
+ it 'applies id locks' do
113
+ MySubject.accessible_by(ability, :update).to_a.wont_include my_subject
114
+ MySubject.accessible_by(ability, :update).to_a.must_include my_subject1
115
+ MySubject.accessible_by(ability, :update).to_a.must_include my_subject2
131
116
  end
132
117
  end
133
118
 
119
+ describe 'open id locks' do
120
+ let(:role_1) { MyRole.new(my_locks: [ MyLock.new(subject: my_subject1, action: :update, outcome: true) ]) }
121
+
122
+ before do
123
+ MySubject.default_locks = [ MyLock.new(subject_type: MySubject, action: :update, outcome: false) ]
124
+ MySubject1.default_locks = []
125
+ MySubject2.default_locks = []
126
+
127
+ my_subject
128
+ my_subject1
129
+ my_subject2
130
+ end
131
+
132
+ it 'applies id locks' do
133
+ MySubject.accessible_by(ability, :update).to_a.wont_include my_subject
134
+ MySubject1.accessible_by(ability, :update).to_a.must_include my_subject1
135
+ MySubject2.accessible_by(ability, :update).to_a.wont_include my_subject2
136
+ end
137
+ end
134
138
  end
135
139
  end
@@ -2,7 +2,6 @@ require 'test_helper'
2
2
 
3
3
  module MongoidAbility
4
4
  describe Subject do
5
-
6
5
  describe '.default_lock' do
7
6
  before do
8
7
  MySubject.default_locks = []
@@ -20,17 +19,6 @@ module MongoidAbility
20
19
  end
21
20
  end
22
21
 
23
- # describe 'when lock not defined on superclass' do
24
- # before do
25
- # MySubject.default_locks = []
26
- # MySubject1.default_locks = []
27
- # end
28
- #
29
- # it 'must raise error' do
30
- # -> { MySubject1.default_lock MyLock, :test, true }.must_raise StandardError
31
- # end
32
- # end
33
-
34
22
  describe 'prevents conflicts' do
35
23
  before do
36
24
  MySubject.default_locks = []
@@ -39,15 +27,15 @@ module MongoidAbility
39
27
  end
40
28
 
41
29
  it 'does not allow multiple locks for same action' do
42
- MySubject.default_locks.select{ |l| l.action == :read }.count.must_equal 1
30
+ MySubject.default_locks.count { |l| l.action == :read }.must_equal 1
43
31
  end
44
32
 
45
33
  it 'replace existing locks with new attributes' do
46
- MySubject.default_locks.detect{ |l| l.action == :read }.outcome.must_equal false
34
+ MySubject.default_locks.detect { |l| l.action == :read }.outcome.must_equal false
47
35
  end
48
36
 
49
37
  it 'replaces existing locks with new one' do
50
- MySubject.default_locks.detect{ |l| l.action == :read }.class.must_equal MyLock1
38
+ MySubject.default_locks.detect { |l| l.action == :read }.class.must_equal MyLock1
51
39
  end
52
40
  end
53
41
 
@@ -57,11 +45,10 @@ module MongoidAbility
57
45
  it { MySubject2.is_root_class?.must_equal false }
58
46
  end
59
47
 
60
- describe ".root_class" do
48
+ describe '.root_class' do
61
49
  it { MySubject.root_class.must_equal MySubject }
62
50
  it { MySubject1.root_class.must_equal MySubject }
63
51
  it { MySubject2.root_class.must_equal MySubject }
64
52
  end
65
-
66
53
  end
67
54
  end
@@ -0,0 +1,4 @@
1
+ module MongoidAbility
2
+ module Expectations
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module MongoidAbility
2
+ class MyLock
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include MongoidAbility::Lock
6
+
7
+ embedded_in :owner, polymorphic: true, touch: true
8
+ end
9
+
10
+ class MyLock1 < MyLock
11
+ def calculated_outcome(opts = {})
12
+ opts.fetch(:override, outcome)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module MongoidAbility
2
+ class MyOwner
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include MongoidAbility::Owner
6
+
7
+ embeds_many :my_locks, class_name: 'MongoidAbility::MyLock', as: :owner
8
+ has_and_belongs_to_many :my_roles
9
+
10
+ def self.locks_relation_name
11
+ :my_locks
12
+ end
13
+
14
+ def self.inherit_from_relation_name
15
+ :my_roles
16
+ end
17
+ end
18
+
19
+ class MyOwner1 < MyOwner
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ module MongoidAbility
2
+ class MyRole
3
+ include Mongoid::Document
4
+ include MongoidAbility::Owner
5
+
6
+ embeds_many :my_locks, class_name: 'MongoidAbility::MyLock', as: :owner
7
+ has_and_belongs_to_many :my_owners
8
+
9
+ def self.locks_relation_name
10
+ :my_locks
11
+ end
12
+ end
13
+
14
+ class MyRole1 < MyRole
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module MongoidAbility
2
+ class MySubject
3
+ include Mongoid::Document
4
+ include MongoidAbility::Subject
5
+
6
+ default_lock MyLock, :read, true
7
+ default_lock MyLock1, :update, false
8
+ end
9
+
10
+ class MySubject1 < MySubject
11
+ default_lock MyLock, :read, false
12
+ end
13
+
14
+ class MySubject2 < MySubject1
15
+ end
16
+ end
@@ -9,8 +9,8 @@ require 'mongoid_ability'
9
9
 
10
10
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
11
 
12
- if ENV["CI"]
13
- require "coveralls"
12
+ if ENV['CI']
13
+ require 'coveralls'
14
14
  Coveralls.wear!
15
15
  end
16
16
 
@@ -28,3 +28,7 @@ class MiniTest::Spec
28
28
  before(:each) { DatabaseCleaner.start }
29
29
  after(:each) { DatabaseCleaner.clean }
30
30
  end
31
+
32
+ class Object
33
+ include MongoidAbility::Expectations
34
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid_ability
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomáš Celizna
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-10 00:00:00.000000000 Z
11
+ date: 2016-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cancancan
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.6'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '1.6'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: coveralls
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -177,7 +177,11 @@ files:
177
177
  - test/mongoid_ability/resolve_owner_locks_test.rb
178
178
  - test/mongoid_ability/subject_accessible_by_test.rb
179
179
  - test/mongoid_ability/subject_test.rb
180
- - test/support/test_classes.rb
180
+ - test/support/expectations.rb
181
+ - test/support/test_classes/my_lock.rb
182
+ - test/support/test_classes/my_owner.rb
183
+ - test/support/test_classes/my_role.rb
184
+ - test/support/test_classes/my_subject.rb
181
185
  - test/test_helper.rb
182
186
  homepage: https://github.com/tomasc/mongoid_ability
183
187
  licenses:
@@ -199,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
203
  version: '0'
200
204
  requirements: []
201
205
  rubyforge_project:
202
- rubygems_version: 2.4.8
206
+ rubygems_version: 2.4.6
203
207
  signing_key:
204
208
  specification_version: 4
205
209
  summary: Custom Ability class that allows CanCanCan authorization library store permissions
@@ -218,5 +222,9 @@ test_files:
218
222
  - test/mongoid_ability/resolve_owner_locks_test.rb
219
223
  - test/mongoid_ability/subject_accessible_by_test.rb
220
224
  - test/mongoid_ability/subject_test.rb
221
- - test/support/test_classes.rb
225
+ - test/support/expectations.rb
226
+ - test/support/test_classes/my_lock.rb
227
+ - test/support/test_classes/my_owner.rb
228
+ - test/support/test_classes/my_role.rb
229
+ - test/support/test_classes/my_subject.rb
222
230
  - test/test_helper.rb
@@ -1,161 +0,0 @@
1
- module MongoidAbility
2
- class MyLock
3
- include Mongoid::Document
4
- include MongoidAbility::Lock
5
- embedded_in :owner, polymorphic: true
6
- end
7
-
8
- class MyLock1 < MyLock
9
- def calculated_outcome opts={}
10
- opts.fetch(:override, outcome)
11
- end
12
- end
13
-
14
- # ---------------------------------------------------------------------
15
-
16
- class MySubject
17
- include Mongoid::Document
18
- include MongoidAbility::Subject
19
-
20
- default_lock MyLock, :read, true
21
- default_lock MyLock1, :update, false
22
- end
23
-
24
- class MySubject1 < MySubject
25
- default_lock MyLock, :read, false
26
- end
27
-
28
- class MySubject2 < MySubject1
29
- end
30
-
31
- # ---------------------------------------------------------------------
32
-
33
- class MyOwner
34
- include Mongoid::Document
35
- include MongoidAbility::Owner
36
-
37
- embeds_many :my_locks, class_name: 'MongoidAbility::MyLock', as: :owner
38
- has_and_belongs_to_many :my_roles
39
-
40
- def self.locks_relation_name
41
- :my_locks
42
- end
43
-
44
- def self.inherit_from_relation_name
45
- :my_roles
46
- end
47
- end
48
-
49
- class MyOwner1 < MyOwner
50
- end
51
-
52
- # ---------------------------------------------------------------------
53
-
54
- class MyRole
55
- include Mongoid::Document
56
- include MongoidAbility::Owner
57
-
58
- embeds_many :my_locks, class_name: 'MongoidAbility::MyLock', as: :owner
59
- has_and_belongs_to_many :my_owners
60
-
61
- def self.locks_relation_name
62
- :my_locks
63
- end
64
- end
65
-
66
- class MyRole1 < MyRole
67
- end
68
- end
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
- # class TestLock
77
- # include Mongoid::Document
78
- # include MongoidAbility::Lock
79
- #
80
- # embedded_in :owner, polymorphic: true
81
- # end
82
- #
83
- # class TestLockSub < TestLock
84
- # end
85
- #
86
- # # ---------------------------------------------------------------------
87
- #
88
- # class TestOwnerSuper
89
- # include Mongoid::Document
90
- # include MongoidAbility::Owner
91
- #
92
- # embeds_many :test_locks, class_name: 'TestLock', as: :owner
93
- # end
94
- #
95
- # class TestOwner < TestOwnerSuper
96
- # end
97
- #
98
- # # ---------------------------------------------------------------------
99
- #
100
- # class SubjectTest
101
- # include Mongoid::Document
102
- # include MongoidAbility::Subject
103
- #
104
- # default_lock :read, true
105
- # end
106
- #
107
- # class SubjectTestOne < SubjectTest
108
- # end
109
- #
110
- # class SubjectTestTwo < SubjectTest
111
- # end
112
- #
113
- # class SubjectSingleTest
114
- # include Mongoid::Document
115
- # include MongoidAbility::Subject
116
- #
117
- # default_lock :read, true
118
- # end
119
- #
120
- # # ---------------------------------------------------------------------
121
- #
122
- # class TestAbilityResolverSubject
123
- # include Mongoid::Document
124
- # include MongoidAbility::Subject
125
- #
126
- # default_lock :read, true
127
- # end
128
- #
129
- # class TestAbilitySubjectSuper2
130
- # include Mongoid::Document
131
- # include MongoidAbility::Subject
132
- #
133
- # default_lock :read, false
134
- # default_lock :update, true
135
- # end
136
- #
137
- # class TestAbilitySubjectSuper1 < TestAbilitySubjectSuper2
138
- # end
139
- #
140
- # class TestAbilitySubject < TestAbilitySubjectSuper1
141
- # end
142
- #
143
- # # ---------------------------------------------------------------------
144
- #
145
- # class TestRole
146
- # include Mongoid::Document
147
- # include MongoidAbility::Owner
148
- #
149
- # field :name, type: String
150
- #
151
- # embeds_many :test_locks, class_name: 'TestLock', as: :owner
152
- # has_and_belongs_to_many :users, class_name: 'TestUser'
153
- # end
154
- #
155
- # class TestUser
156
- # include Mongoid::Document
157
- # include MongoidAbility::Owner
158
- #
159
- # embeds_many :test_locks, class_name: 'TestLock', as: :owner
160
- # has_and_belongs_to_many :roles, class_name: 'TestRole'
161
- # end