mongoid_ability 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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