mongoid_ability 0.3.12 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43d40158fbfa09bdf0f359c67b2479fb2854018e
4
- data.tar.gz: 5991679a87775917e3f9823048a4b6c56f13fc1c
3
+ metadata.gz: 7fc3874b06359df0fba37050d4d67178930055ff
4
+ data.tar.gz: d6b58a71fc1cea44d3777e8199f7a9c7d6c20cf4
5
5
  SHA512:
6
- metadata.gz: c991cc35b5c519d08cfc1bb49b8df8d41648a55d3cbb13f82dcea1691fcbc500218374cc6d78609d07c69578cd4d1ba6d66fbbc4c16ed64b6eb59965b169424a
7
- data.tar.gz: c983cf65e6e9d8e3276e6833173e866e857e1f7745426242d4285178118f6252cda3466ce6779540c91a65787eb04999f5bedb0a389217ce6466d4aef9b9854d
6
+ metadata.gz: 2b5a18e5d2662ba5e5edfb7cac7e7a0295eb78ad35affa6b879e009985978454dff6c0e142feacf0e2bccdd85142f3714f177a9bb4dfdfaa15a7ba89eb6faf34
7
+ data.tar.gz: a465b49e5904ee8b06fbed035b6a95a439fbb9fc3e5c79f4769de2b59a05702c986e3e64d0a94c60de73aa1fd71e9659f88cf283f7e3aa3a016199f6a3618b7d
data/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # 0.4.3
2
+
3
+ * stub `default_locks` in tests to avoid brittle tests
4
+ * fix the ability syntactic sugar to support both proc & direct call
5
+
6
+ # 0.4.2
7
+
8
+ * **yanked**
9
+ * improved clarity in class naming
10
+
11
+ # 0.4.1
12
+
13
+ * **yanked**
14
+ * `Resolver` classes now accept `subject_id` (instead of `subject`) for greater flexibility
15
+
16
+ # 0.4.0
17
+
18
+ * **yanked**
19
+ * the `Resolver` classes refactored to return locks (instead of just an outcome)
@@ -6,6 +6,7 @@ require "mongoid_ability/lock"
6
6
  require "mongoid_ability/owner"
7
7
  require "mongoid_ability/subject"
8
8
 
9
+ require "mongoid_ability/resolver"
9
10
  require "mongoid_ability/resolve_locks"
10
11
  require "mongoid_ability/resolve_default_locks"
11
12
  require "mongoid_ability/resolve_inherited_locks"
@@ -14,39 +14,37 @@ module MongoidAbility
14
14
  subject_classes.reject { |cls| cls.superclass.included_modules.include?(MongoidAbility::Subject) }
15
15
  end
16
16
 
17
- # =====================================================================
18
-
19
17
  def initialize(owner)
20
18
  @owner = owner
21
19
 
22
- can do |action, subject_type, subject, options|
23
- subject_class = subject_type.to_s.constantize
24
- outcome = nil
25
- options ||= {}
26
-
27
- subject_class.self_and_ancestors_with_default_locks.each do |cls|
28
- outcome = ResolveInheritedLocks.call(owner, action, cls, subject, options)
29
- break unless outcome.nil?
20
+ can do |action, subject_type, subject, options = {}|
21
+ subject_id = subject ? subject.id : nil
22
+ if lock = ResolveLocks.call(owner, action, subject_type, subject_id, options)
23
+ lock.calculated_outcome(options)
30
24
  end
31
-
32
- outcome
33
25
  end
34
26
  end
35
27
 
36
- # ---------------------------------------------------------------------
37
-
38
28
  # lambda for easy permission checking:
39
29
  # .select(&current_ability.can_read)
40
30
  # .select(&current_ability.can_update)
41
31
  # .select(&current_ability.can_destroy)
42
32
  # etc.
43
- #
44
- # TODO: allow to pass options .select(&current_ability.can_read(my_option: false))
45
- #
46
33
  def method_missing(name, *args)
47
34
  return super unless name.to_s =~ /\A(can|cannot)_/
48
- return unless action = name.to_s.gsub(/\A(can|cannot)_/, '').to_sym
49
- name =~ /can_/ ? ->(doc) { can? action, doc } : ->(doc) { cannot? action, doc }
35
+ return unless action = name.to_s.scan(/\A(can|cannot)_(\w+)/).flatten.last.to_sym
36
+
37
+ if args.empty? || args.first.is_a?(Hash)
38
+ case name
39
+ when /can_/ then -> (doc) { can?(action, doc, *args) }
40
+ else -> (doc) { cannot?(action, doc, *args) }
41
+ end
42
+ else
43
+ case name
44
+ when /can_/ then can?(action, *args)
45
+ else cannot?(action, *args)
46
+ end
47
+ end
50
48
  end
51
49
  end
52
50
  end
@@ -15,16 +15,17 @@ module MongoidAbility
15
15
  validates :action, presence: true, uniqueness: { scope: [:subject_type, :subject_id, :outcome] }
16
16
  validates :outcome, presence: true
17
17
 
18
- scope :for_action, -> (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) }
20
+ scope :for_subject_type, ->(subject_type) { where(subject_type: subject_type.to_s) }
21
+ scope :for_subject_types, ->(subject_types) { criteria.in(subject_type: subject_types) }
21
22
 
22
- scope :for_subject_id, -> (subject_id) {
23
+ scope :for_subject_id, ->(subject_id) {
23
24
  return where(subject_id: nil) unless subject_id.present?
24
25
  where(subject_id: BSON::ObjectId.from_string(subject_id))
25
26
  }
26
27
 
27
- scope :for_subject, -> (subject) {
28
+ scope :for_subject, ->(subject) {
28
29
  return where(subject_id: nil) unless subject.present?
29
30
  where(subject_type: subject.class.model_name, subject_id: subject.id)
30
31
  }
@@ -34,8 +35,6 @@ module MongoidAbility
34
35
  end
35
36
  end
36
37
 
37
- # =====================================================================
38
-
39
38
  concerning :LockType do
40
39
  def class_lock?
41
40
  !id_lock?
@@ -1,6 +1,6 @@
1
1
  module MongoidAbility
2
2
  module Owner
3
- def self.included base
3
+ def self.included(base)
4
4
  base.extend ClassMethods
5
5
  base.class_eval do
6
6
  delegate :can?, :cannot?, to: :ability
@@ -8,8 +8,6 @@ module MongoidAbility
8
8
  end
9
9
  end
10
10
 
11
- # ---------------------------------------------------------------------
12
-
13
11
  module ClassMethods
14
12
  def locks_relation_name
15
13
  :locks
@@ -20,32 +18,28 @@ module MongoidAbility
20
18
  end
21
19
  end
22
20
 
23
- # ---------------------------------------------------------------------
24
-
25
21
  def locks_relation
26
- self.send(self.class.locks_relation_name)
22
+ send(self.class.locks_relation_name)
27
23
  end
28
24
 
29
- def locks_relation= val
30
- self.send "#{self.class.locks_relation_name}=", val
25
+ def locks_relation=(val)
26
+ send "#{self.class.locks_relation_name}=", val
31
27
  end
32
28
 
33
29
  def inherit_from_relation
34
- self.send(self.class.inherit_from_relation_name)
30
+ send(self.class.inherit_from_relation_name)
35
31
  end
36
32
 
37
33
  def ability
38
34
  @ability ||= MongoidAbility::Ability.new(self)
39
35
  end
40
36
 
41
- # ---------------------------------------------------------------------
42
-
43
37
  def has_lock?(lock)
44
38
  @has_lock ||= {}
45
39
  @has_lock[lock] ||= locks_relation.where(action: lock.action, subject_type: lock.subject_type, subject_id: lock.subject_id.presence).cache.exists?
46
40
  end
47
41
 
48
- private # =============================================================
42
+ private
49
43
 
50
44
  def cleanup_locks
51
45
  locks_relation.select(&:open?).each do |lock|
@@ -1,11 +1,14 @@
1
1
  module MongoidAbility
2
- class ResolveDefaultLocks < ResolveLocks
2
+ class ResolveDefaultLocks < Resolver
3
3
  def call
4
- return false if default_locks.any? { |l| l.closed?(options) }
5
- return true if default_locks.any? { |l| l.open?(options) }
4
+ closed_lock = default_locks.detect { |l| l.closed?(options) }
5
+ return closed_lock if closed_lock
6
+
7
+ open_lock = default_locks.detect { |l| l.open?(options) }
8
+ return open_lock if open_lock
6
9
  end
7
10
 
8
- private # =============================================================
11
+ private
9
12
 
10
13
  def default_locks
11
14
  subject_class.default_locks.select { |l| l.action.to_s == action.to_s }
@@ -1,30 +1,35 @@
1
1
  module MongoidAbility
2
- class ResolveInheritedLocks < ResolveLocks
2
+ class ResolveInheritedLocks < Resolver
3
3
  def call
4
- uo = user_outcome
5
- return uo unless uo.nil?
4
+ owner_lock = resolved_owner_lock
5
+ return owner_lock if owner_lock
6
6
 
7
7
  if owner.respond_to?(owner.class.inherit_from_relation_name) && !owner.inherit_from_relation.nil?
8
- io = owner.inherit_from_relation.collect { |inherited_owner| inherited_owner_outcome(inherited_owner) }.compact
9
- return io.any? { |o| o == true } unless io.empty?
8
+ resolved_inherited_owner_locks = owner.inherit_from_relation.map { |inherited_owner| resolved_inherited_owner_lock(inherited_owner) }.compact
9
+
10
+ open_lock = resolved_inherited_owner_locks.detect { |l| l.open?(options) }
11
+ return open_lock if open_lock
12
+
13
+ closed_lock = resolved_inherited_owner_locks.detect { |l| l.closed?(options) }
14
+ return closed_lock if closed_lock
10
15
  end
11
16
 
12
- default_outcome
17
+ resolved_default_lock
13
18
  end
14
19
 
15
- private # =============================================================
20
+ private
16
21
 
17
- def user_outcome
18
- @user_outcome ||= ResolveOwnerLocks.call(owner, action, subject_class, subject, options)
22
+ def resolved_owner_lock
23
+ @resolved_owner_lock ||= ResolveOwnerLocks.call(owner, action, subject_class, subject_id, options)
19
24
  end
20
25
 
21
- def inherited_owner_outcome(inherited_owner)
22
- @inherited_owner_outcome ||= {}
23
- @inherited_owner_outcome[inherited_owner] ||= ResolveOwnerLocks.call(inherited_owner, action, subject_class, subject, options)
26
+ def resolved_inherited_owner_lock(inherited_owner)
27
+ @resolved_inherited_owner_lock ||= {}
28
+ @resolved_inherited_owner_lock[inherited_owner] ||= ResolveOwnerLocks.call(inherited_owner, action, subject_class, subject_id, options)
24
29
  end
25
30
 
26
- def default_outcome
27
- @default_outcome ||= ResolveDefaultLocks.call(nil, action, subject_class, nil, options)
31
+ def resolved_default_lock
32
+ @resolved_default_lock ||= ResolveDefaultLocks.call(nil, action, subject_class, nil, options)
28
33
  end
29
34
  end
30
35
  end
@@ -1,32 +1,12 @@
1
1
  module MongoidAbility
2
- class ResolveLocks < Struct.new(:owner, :action, :subject_type, :subject, :options)
3
- attr_reader(
4
- :subject_class,
5
- :subject_id
6
- )
7
-
8
- def self.call(*args)
9
- new(*args).call
10
- end
11
-
12
- # =====================================================================
13
-
14
- def initialize(*args)
15
- super(*args)
16
-
17
- @subject_class = subject_type.to_s.constantize
18
- @subject_id = subject.id if subject.present?
19
-
20
- fail StandardError, "#{subject_type} class does not have default locks" unless @subject_class.respond_to?(:default_locks)
21
- fail StandardError, "#{subject_type} class does not have default lock for :#{action} action" unless @subject_class.self_and_ancestors_with_default_locks.any? do |cls|
22
- cls.default_locks.any? { |l| l.action == action }
23
- end
24
- end
25
-
26
- # =====================================================================
27
-
2
+ class ResolveLocks < Resolver
28
3
  def call
29
- fail NotImplementedError
4
+ lock = nil
5
+ subject_class.self_and_ancestors_with_default_locks.each do |cls|
6
+ lock = ResolveInheritedLocks.call(owner, action, cls.to_s, subject_id, options)
7
+ break unless lock.nil?
8
+ end
9
+ lock
30
10
  end
31
11
  end
32
12
  end
@@ -1,7 +1,7 @@
1
1
  # OPTIMIZE: this seems quite expensive
2
2
 
3
3
  module MongoidAbility
4
- class ResolveOwnerLocks < ResolveLocks
4
+ class ResolveOwnerLocks < Resolver
5
5
  def call
6
6
  return unless owner.respond_to?(:locks_relation)
7
7
 
@@ -9,17 +9,25 @@ module MongoidAbility
9
9
 
10
10
  return unless locks_for_subject_type.exists?
11
11
 
12
- # return outcome if owner defines lock for id
12
+ # return lock if owner defines lock for id
13
13
  if subject_id.present?
14
14
  id_locks = locks_for_subject_type.id_locks.for_subject_id(subject_id).cache
15
- return false if id_locks.any? { |l| l.closed?(options) }
16
- return true if id_locks.any? { |l| l.open?(options) }
15
+
16
+ closed_lock = id_locks.detect { |l| l.closed?(options) }
17
+ return closed_lock if closed_lock
18
+
19
+ open_lock = id_locks.detect { |l| l.open?(options) }
20
+ return open_lock if open_lock
17
21
  end
18
22
 
19
- # return outcome if owner defines lock for subject_type
23
+ # return lock if owner defines lock for subject_type
20
24
  class_locks = locks_for_subject_type.class_locks.cache
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) }
25
+
26
+ closed_lock = class_locks.class_locks.detect { |l| l.closed?(options) }
27
+ return closed_lock if closed_lock
28
+
29
+ open_lock = class_locks.class_locks.detect { |l| l.open?(options) }
30
+ return open_lock if open_lock
23
31
 
24
32
  nil
25
33
  end
@@ -0,0 +1,24 @@
1
+ module MongoidAbility
2
+ class Resolver < Struct.new(:owner, :action, :subject_type, :subject_id, :options)
3
+ attr_reader :subject_class
4
+
5
+ def self.call(*args)
6
+ new(*args).call
7
+ end
8
+
9
+ def initialize(owner, action, subject_type, subject_id = nil, options = {})
10
+ super(owner, action, subject_type, subject_id, options)
11
+
12
+ @subject_class = subject_type.to_s.constantize
13
+
14
+ raise StandardError, "#{subject_type} class does not have default locks" unless @subject_class.respond_to?(:default_locks)
15
+ raise StandardError, "#{subject_type} class does not have default lock for :#{action} action" unless @subject_class.self_and_ancestors_with_default_locks.any? do |cls|
16
+ cls.default_locks.any? { |l| l.action == action }
17
+ end
18
+ end
19
+
20
+ def call
21
+ raise NotImplementedError
22
+ end
23
+ end
24
+ end
@@ -4,6 +4,10 @@ module MongoidAbility
4
4
  new(*args).call
5
5
  end
6
6
 
7
+ def initialize(base_class, ability, action, options = {})
8
+ super(base_class, ability, action, options)
9
+ end
10
+
7
11
  # =====================================================================
8
12
 
9
13
  def call
@@ -1,3 +1,3 @@
1
1
  module MongoidAbility
2
- VERSION = '0.3.12'.freeze
2
+ VERSION = '0.4.3'.freeze
3
3
  end
@@ -0,0 +1,29 @@
1
+ require 'test_helper'
2
+
3
+ module MongoidAbility
4
+ describe 'basic ability test' do
5
+ let(:read_lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
6
+ let(:owner) { MyRole.new(my_locks: [read_lock]) }
7
+ let(:ability) { Ability.new(owner) }
8
+
9
+ let(:default_locks) { [MyLock.new(action: :read, outcome: true)] }
10
+
11
+ it 'owner can?' do
12
+ MySubject.stub :default_locks, default_locks do
13
+ ability.can?(:read, MySubject).must_equal false
14
+ end
15
+ end
16
+
17
+ it 'owner cannot?' do
18
+ MySubject.stub :default_locks, default_locks do
19
+ ability.cannot?(:read, MySubject).must_equal true
20
+ end
21
+ end
22
+
23
+ it 'is accessible by' do
24
+ MySubject.stub :default_locks, default_locks do
25
+ MySubject.accessible_by(ability, :read).must_be_kind_of Mongoid::Criteria
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+
3
+ module MongoidAbility
4
+ describe 'syntactic sugar' do
5
+ let(:read_lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
6
+ let(:owner) { MyRole.new(my_locks: [read_lock]) }
7
+ let(:ability) { Ability.new(owner) }
8
+ let(:options) { { x: 1 } }
9
+
10
+ let(:default_locks) { [MyLock.new(action: :read, outcome: true)] }
11
+
12
+ it 'owner can?' do
13
+ MySubject.stub :default_locks, default_locks do
14
+ [MySubject].select(&ability.can_read(options)).must_equal []
15
+ [MySubject].select(&ability.can_read?(options)).must_equal []
16
+
17
+ ability.can_read(MySubject, options).must_equal false
18
+ ability.can_read?(MySubject, options).must_equal false
19
+ end
20
+ end
21
+
22
+ it 'owner cannot?' do
23
+ MySubject.stub :default_locks, default_locks do
24
+ [MySubject].select(&ability.cannot_read(options)).must_equal [MySubject]
25
+ [MySubject].select(&ability.cannot_read?(options)).must_equal [MySubject]
26
+
27
+ ability.cannot_read(MySubject, options).must_equal true
28
+ ability.cannot_read?(MySubject, options).must_equal true
29
+ end
30
+ end
31
+ end
32
+ end