mongoid_ability 0.3.12 → 0.4.3

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