mongoid_ability 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile +7 -0
- data/README.md +55 -23
- data/lib/cancancan/model_adapters/mongoid_adapter.rb +156 -0
- data/lib/cancancan/model_additions.rb +30 -0
- data/lib/mongoid_ability.rb +12 -12
- data/lib/mongoid_ability/ability.rb +67 -26
- data/lib/mongoid_ability/find_lock.rb +71 -0
- data/lib/mongoid_ability/lock.rb +25 -16
- data/lib/mongoid_ability/locks_decorator.rb +45 -0
- data/lib/mongoid_ability/owner.rb +23 -3
- data/lib/mongoid_ability/subject.rb +10 -22
- data/lib/mongoid_ability/version.rb +1 -1
- data/test/cancancan/model_adapters/mongoid_adapter_options_test.rb +102 -0
- data/test/cancancan/model_adapters/mongoid_adapter_test.rb +207 -0
- data/test/mongoid_ability/ability_basic_benchmark.rb +30 -0
- data/test/mongoid_ability/ability_basic_test.rb +44 -13
- data/test/mongoid_ability/ability_marshal_test.rb +17 -0
- data/test/mongoid_ability/ability_options_test.rb +93 -0
- data/test/mongoid_ability/ability_test.rb +87 -106
- data/test/mongoid_ability/find_lock_test.rb +67 -0
- data/test/mongoid_ability/lock_test.rb +32 -40
- data/test/mongoid_ability/owner_locks_test.rb +14 -21
- data/test/mongoid_ability/owner_test.rb +4 -14
- data/test/mongoid_ability/subject_test.rb +32 -58
- data/test/support/test_classes/my_lock.rb +8 -13
- data/test/support/test_classes/my_owner.rb +13 -15
- data/test/support/test_classes/my_role.rb +9 -11
- data/test/support/test_classes/my_subject.rb +16 -9
- data/test/test_helper.rb +12 -2
- metadata +18 -25
- data/lib/mongoid_ability/accessible_query_builder.rb +0 -64
- data/lib/mongoid_ability/resolve_default_locks.rb +0 -17
- data/lib/mongoid_ability/resolve_inherited_locks.rb +0 -35
- data/lib/mongoid_ability/resolve_locks.rb +0 -12
- data/lib/mongoid_ability/resolve_owner_locks.rb +0 -35
- data/lib/mongoid_ability/resolver.rb +0 -24
- data/lib/mongoid_ability/values_for_accessible_query.rb +0 -74
- data/test/mongoid_ability/ability_syntactic_sugar_test.rb +0 -32
- data/test/mongoid_ability/accessible_query_builder_test.rb +0 -119
- data/test/mongoid_ability/can_options_test.rb +0 -17
- data/test/mongoid_ability/resolve_default_locks_test.rb +0 -41
- data/test/mongoid_ability/resolve_inherited_locks_test.rb +0 -50
- data/test/mongoid_ability/resolve_owner_locks_test.rb +0 -56
- data/test/mongoid_ability/resolver_test.rb +0 -23
- data/test/mongoid_ability/subject_accessible_by_test.rb +0 -147
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dae67db7ec93cdbf840238c3dd2b482646550ea5
|
4
|
+
data.tar.gz: 9585efb9e63dbbee879f2865d378e9dcf81081a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e92310170b4733d744668f02b59595f5ffc0a1b10b8aab414ff1db137a0fb417d0392f370b0cc341895503fea19bebd89799d8d7744c989ddc9d23aef946e06c
|
7
|
+
data.tar.gz: f02421d9e891f91af19df44dc0a289beef01719e5fff3a1ef116a341f6ae10515bd4fc34fbddbc139ba03056e945fdf8a187e96c859203adc975c77539edbd62
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# 2.0.0
|
2
|
+
|
3
|
+
* Full rewrite, which more closely follows the `CanCanCan` conventions: instead of custom algorithm for resolving permissions, the `Lock` documents are now converted to standard `CanCanCan` rules. Similarly the `.acessible_by` criteria are now handled by standard model adapter (`MongoidAdapter`), to be extracted to separate gem. Therefore this gem can benefit for potential future performance improvements of `CanCanCan`. Lastly, the `ability` objects are now cacheable, therefore the conversion of `Lock` documents to `CanCanCan` rule objects does not need to be performed on every request.
|
4
|
+
|
1
5
|
# 1.0.0
|
2
6
|
|
3
7
|
* conforms to '[MONGOID-4418](https://jira.mongodb.org/browse/MONGOID-4418) Don't allow PersistenceContext method as field names' by renaming the `Lock` field `:options` to `:opts` (but aliasing it `as: :options`). As a result the `Mongoid::Ability` API stays unchanged, however in some cases it might be necessary to migrate the values from the `:options` fields to the new `:opts`.
|
data/Gemfile
CHANGED
@@ -1,3 +1,10 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
+
git_source(:github) do |repo_name|
|
4
|
+
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
5
|
+
"git@github.com:#{repo_name}.git"
|
6
|
+
end
|
7
|
+
|
3
8
|
gemspec
|
9
|
+
|
10
|
+
# gem 'cancancan-mongoid', github: 'tomasc/cancancan-mongoid', branch: 'master'
|
data/README.md
CHANGED
@@ -50,24 +50,11 @@ This class defines a permission itself using the following fields:
|
|
50
50
|
|
51
51
|
These fields define what subject (respectively subject type, when referring to a class) the lock applies to, which action it is defined for (for example `:read`), and whether the outcome is positive or negative.
|
52
52
|
|
53
|
-
For more specific behavior, it is possible to override the `#calculated_outcome` method (should, for example, the permission depend on some additional factors). The `#calculated_outcome` method receives options that are passed when checking the permissions using for example `can? :read, MyClass, { option_1: 1 }`
|
54
|
-
|
55
|
-
```ruby
|
56
|
-
def calculated_outcome options={}
|
57
|
-
# custom behaviour
|
58
|
-
# return true/false
|
59
|
-
end
|
60
|
-
```
|
61
|
-
|
62
|
-
If you wish to check the state of a lock directly, please use the convenience methods `#open?` and `#closed?`. These take into account the `#calculated_outcome`. Using the `:outcome` field directly is discouraged as it just returns the boolean attribute.
|
63
|
-
|
64
|
-
The lock class can be further subclassed in order to customise its behavior, for example per action.
|
65
|
-
|
66
53
|
### Subject
|
67
54
|
|
68
55
|
All subjects (classes which permissions you want to control) will include the `MongoidAbility::Subject` module.
|
69
56
|
|
70
|
-
Each action and its default outcome
|
57
|
+
Each action and its default outcome needs to be defined using the `.default_lock` macro.
|
71
58
|
|
72
59
|
```ruby
|
73
60
|
class MySubject
|
@@ -130,6 +117,60 @@ current_user.can?(:read, resource, options)
|
|
130
117
|
other_user.can?(:read, ResourceClass, options)
|
131
118
|
```
|
132
119
|
|
120
|
+
Ability can be easily obtained as:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
current_user.ability
|
124
|
+
```
|
125
|
+
|
126
|
+
### Caching
|
127
|
+
|
128
|
+
The ability object is fully cache-able, which means it is possible to save some precious time on every request (instead of always converting the Lock documents to CanCan rules):
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class ActionController::Base
|
132
|
+
def current_ability
|
133
|
+
@current_ability ||= Rails.cache.fetch([current_user.cache_key, 'ability'].join('/')) do
|
134
|
+
MongoidAbility::Ability.new(current_user)
|
135
|
+
end.tap do |ability|
|
136
|
+
ability.owner ||= current_user
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
And on the owner:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
def ability
|
146
|
+
@ability ||= Rails.cache.fetch([cache_key, 'ability'].join('/')) do
|
147
|
+
MongoidAbility::Ability.new(self)
|
148
|
+
end.tap do |ability|
|
149
|
+
ability.owner ||= self
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
Of course this assumes the user's `cache_key` updates when any of its locks (or locks stored on its roles) change.
|
155
|
+
|
156
|
+
Note the owner has to be assigned after fetching the ability from cache.
|
157
|
+
|
158
|
+
### Decoration
|
159
|
+
|
160
|
+
To be able to check permissions on decorated objects (for example via the Draper gem) subclass the Ability class as follows:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
class MyAbility < MongoidAbility::Ability
|
164
|
+
def can?(action, subject, *extra_args)
|
165
|
+
while subject.is_a?(Draper::Decorator)
|
166
|
+
subject = subject.model
|
167
|
+
end
|
168
|
+
|
169
|
+
super(action, subject, *extra_args)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
133
174
|
### CanCanCan
|
134
175
|
|
135
176
|
The default `:current_ability` defined by [CanCanCan](https://github.com/CanCanCommunity/cancancan) will be automatically overriden by the `Ability` class provided by this gem.
|
@@ -140,15 +181,6 @@ The default `:current_ability` defined by [CanCanCan](https://github.com/CanCanC
|
|
140
181
|
2. Define permissions using lock objects embedded (or associated to) either in user or role.
|
141
182
|
3. Use standard [CanCanCan](https://github.com/CanCanCommunity/cancancan) helpers (`.authorize!`, `#can?`, `#cannot?`) to authorize the current user.
|
142
183
|
|
143
|
-
## How it works?
|
144
|
-
|
145
|
-
The ability class in this gem looks up and calculates the outcome in the following order:
|
146
|
-
|
147
|
-
1. User locks, defined for `:subject_id`, then `:subject_type` (then its superclasses), then defined in the subject class itself (via the `.default_lock` macro) and its superclasses.
|
148
|
-
2. Role locks have the same look up chain as the user locks. The role permissions are optimistic, meaning that in case a user has multiple roles, and the roles have locks with conflicting outcomes, the ability favors the positive one.
|
149
|
-
|
150
|
-
See the test suite for more details.
|
151
|
-
|
152
184
|
## Contributing
|
153
185
|
|
154
186
|
1. Fork it ( https://github.com/tomasc/mongoid_ability/fork )
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module CanCan
|
2
|
+
module ModelAdapters
|
3
|
+
class MongoidAdapter < AbstractAdapter
|
4
|
+
def self.for_class?(model_class)
|
5
|
+
model_class <= Mongoid::Document
|
6
|
+
end
|
7
|
+
|
8
|
+
# Used to determine if this model adapter will override the matching behavior for a hash of conditions.
|
9
|
+
# If this returns true then matches_conditions_hash? will be called. See Rule#matches_conditions_hash
|
10
|
+
def self.override_conditions_hash_matching?(_subject, _conditions)
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
# Override if override_conditions_hash_matching? returns true
|
15
|
+
def self.matches_conditions_hash?(_subject, _conditions)
|
16
|
+
raise NotImplemented, 'This model adapter does not support matching on a conditions hash.'
|
17
|
+
end
|
18
|
+
|
19
|
+
# Used to determine if this model adapter will override the matching behavior for a specific condition.
|
20
|
+
# If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
|
21
|
+
def self.override_condition_matching?(_subject, _name, _value)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override if override_condition_matching? returns true
|
26
|
+
def self.matches_condition?(subject, name, value)
|
27
|
+
attribute = subject.send(name)
|
28
|
+
|
29
|
+
case value
|
30
|
+
when Hash then hash_condition_match?(attribute, value)
|
31
|
+
when Range then value.cover?(attribute)
|
32
|
+
when Regexp then value.match(attribute)
|
33
|
+
when Array then value.include?(attribute)
|
34
|
+
when Enumerable then value.include?(attribute)
|
35
|
+
else attribute == value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(model_class, rules, options = {})
|
40
|
+
@model_class = model_class
|
41
|
+
@rules = rules
|
42
|
+
@options = options
|
43
|
+
end
|
44
|
+
|
45
|
+
def subject_types
|
46
|
+
@subject_types ||= begin
|
47
|
+
root_cls = @model_class.root_class
|
48
|
+
[root_cls, *root_cls.descendants].compact
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def open_subject_types
|
53
|
+
@open_subject_types ||= begin
|
54
|
+
subject_types.inject([]) do |res, cls|
|
55
|
+
subject_type_rules_for(cls).each do |rule|
|
56
|
+
cls_list = [cls, *cls.descendants].compact
|
57
|
+
rule.base_behavior ? res += cls_list : res -= cls_list
|
58
|
+
end
|
59
|
+
res.uniq
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def closed_subject_types
|
65
|
+
@closed_subject_types ||= begin
|
66
|
+
subject_types - open_subject_types
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def open_conditions
|
71
|
+
@open_conditions ||= begin
|
72
|
+
condition_rules.select(&:base_behavior).each_with_object([]) do |rule, res|
|
73
|
+
rule.conditions.each do |key, value|
|
74
|
+
key = id_key if %i[id _id].include?(key.to_sym)
|
75
|
+
res << case value
|
76
|
+
when Array then { key => { '$in' => value } }
|
77
|
+
else { key => value }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def closed_conditions
|
85
|
+
@closed_conditions ||= begin
|
86
|
+
condition_rules.reject(&:base_behavior).each_with_object([]) do |rule, res|
|
87
|
+
rule.conditions.each do |key, value|
|
88
|
+
key = id_key if %i[id _id].include?(key.to_sym)
|
89
|
+
res << case value
|
90
|
+
when Regexp then { key => { '$not' => value } }
|
91
|
+
when Array then { key => { '$nin' => value } }
|
92
|
+
else { key => { '$ne' => value } }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def subject_type_conditions
|
100
|
+
return unless open_subject_types.present?
|
101
|
+
{ :"#{type_key}".nin => closed_subject_types.map(&:to_s) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def has_any_conditions?
|
105
|
+
subject_type_conditions.present? ||
|
106
|
+
open_conditions.present? ||
|
107
|
+
closed_conditions.present?
|
108
|
+
end
|
109
|
+
|
110
|
+
def database_records
|
111
|
+
return @model_class.none unless has_any_conditions?
|
112
|
+
|
113
|
+
or_conditions = { '$or' => ([subject_type_conditions, *open_conditions]).compact }
|
114
|
+
or_conditions = nil if or_conditions['$or'].empty?
|
115
|
+
|
116
|
+
and_conditions = { '$and' => [or_conditions, *closed_conditions].compact }
|
117
|
+
and_conditions = nil if and_conditions['$and'].empty?
|
118
|
+
|
119
|
+
@model_class.where(and_conditions)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def subject_type_rules_for(subject_type)
|
125
|
+
subject_type_rules.select do |rule|
|
126
|
+
rule.subjects.include?(subject_type)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def subject_type_rules
|
131
|
+
@rules.reject { |rule| rule.conditions.present? }
|
132
|
+
end
|
133
|
+
|
134
|
+
def condition_rules
|
135
|
+
@rules.select { |rule| rule.conditions.present? }
|
136
|
+
end
|
137
|
+
|
138
|
+
def prefix
|
139
|
+
@options.fetch(:prefix, nil)
|
140
|
+
end
|
141
|
+
|
142
|
+
def id_key
|
143
|
+
@id_key ||= [prefix, '_id'].reject(&:blank?).join.to_sym
|
144
|
+
end
|
145
|
+
|
146
|
+
def type_key
|
147
|
+
@type_key ||= [prefix, '_type'].reject(&:blank?).join.to_sym
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# simplest way to add `accessible_by` to all Mongoid Documents
|
154
|
+
module Mongoid::Document::ClassMethods
|
155
|
+
include CanCan::ModelAdditions::ClassMethods
|
156
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CanCan
|
2
|
+
# This module adds the accessible_by class method to a model. It is included in the model adapters.
|
3
|
+
module ModelAdditions
|
4
|
+
module ClassMethods
|
5
|
+
# Returns a scope which fetches only the records that the passed ability
|
6
|
+
# can perform a given action on. The action defaults to :index. This
|
7
|
+
# is usually called from a controller and passed the +current_ability+.
|
8
|
+
#
|
9
|
+
# @articles = Article.accessible_by(current_ability)
|
10
|
+
#
|
11
|
+
# Here only the articles which the user is able to read will be returned.
|
12
|
+
# If the user does not have permission to read any articles then an empty
|
13
|
+
# result is returned. Since this is a scope it can be combined with any
|
14
|
+
# other scopes or pagination.
|
15
|
+
#
|
16
|
+
# An alternative action can optionally be passed as a second argument.
|
17
|
+
#
|
18
|
+
# @articles = Article.accessible_by(current_ability, :update)
|
19
|
+
#
|
20
|
+
# Here only the articles which the user can update are returned.
|
21
|
+
def accessible_by(ability, action = :index, options = {})
|
22
|
+
ability.model_adapter(self, action, options).database_records
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.included(base)
|
27
|
+
base.extend ClassMethods
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/mongoid_ability.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
require
|
1
|
+
require 'cancancan'
|
2
|
+
require 'mongoid'
|
2
3
|
|
3
|
-
require
|
4
|
+
require 'cancancan/model_adapters/mongoid_adapter'
|
5
|
+
require 'cancancan/model_additions'
|
4
6
|
|
5
|
-
require
|
6
|
-
require "mongoid_ability/owner"
|
7
|
-
require "mongoid_ability/subject"
|
7
|
+
require 'mongoid_ability/version'
|
8
8
|
|
9
|
-
require
|
10
|
-
require "mongoid_ability/resolve_locks"
|
11
|
-
require "mongoid_ability/resolve_default_locks"
|
12
|
-
require "mongoid_ability/resolve_inherited_locks"
|
13
|
-
require "mongoid_ability/resolve_owner_locks"
|
9
|
+
require 'mongoid_ability/ability'
|
14
10
|
|
15
|
-
require
|
16
|
-
require
|
11
|
+
require 'mongoid_ability/lock'
|
12
|
+
require 'mongoid_ability/owner'
|
13
|
+
require 'mongoid_ability/subject'
|
14
|
+
|
15
|
+
require 'mongoid_ability/locks_decorator'
|
16
|
+
require 'mongoid_ability/find_lock'
|
17
17
|
|
18
18
|
# ---------------------------------------------------------------------
|
19
19
|
|
@@ -4,47 +4,88 @@ module MongoidAbility
|
|
4
4
|
class Ability
|
5
5
|
include CanCan::Ability
|
6
6
|
|
7
|
-
|
7
|
+
attr_accessor :owner
|
8
|
+
|
9
|
+
def marshal_dump
|
10
|
+
@rules
|
11
|
+
end
|
12
|
+
|
13
|
+
def marshal_load(array)
|
14
|
+
Array(array).each do |rule|
|
15
|
+
add_rule(rule)
|
16
|
+
end
|
17
|
+
end
|
8
18
|
|
9
19
|
def self.subject_classes
|
10
|
-
Object.descendants.select
|
20
|
+
Object.descendants.select do |cls|
|
21
|
+
cls.included_modules.include?(MongoidAbility::Subject)
|
22
|
+
end
|
11
23
|
end
|
12
24
|
|
13
25
|
def self.subject_root_classes
|
14
|
-
subject_classes.reject
|
26
|
+
subject_classes.reject do |cls|
|
27
|
+
cls.superclass.included_modules.include?(MongoidAbility::Subject)
|
28
|
+
end
|
15
29
|
end
|
16
30
|
|
17
31
|
def initialize(owner)
|
18
32
|
@owner = owner
|
19
33
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
34
|
+
inherited_locks = owner.respond_to?(owner.class.inherit_from_relation_name) ? owner.inherit_from_relation.flat_map(&:locks_relation) : []
|
35
|
+
inherited_locks = LocksDecorator.new(inherited_locks)
|
36
|
+
|
37
|
+
owner_locks = owner.respond_to?(owner.class.locks_relation_name) ? owner.locks_relation : []
|
38
|
+
|
39
|
+
self.class.subject_root_classes.each do |cls|
|
40
|
+
cls_list = [cls] + cls.descendants
|
41
|
+
cls_list.each do |subcls|
|
42
|
+
# if 2 of the same, prefer open
|
43
|
+
locks = subcls.default_locks.for_subject_type(subcls).group_by(&:group_key_for_calc).flat_map do |_, locks|
|
44
|
+
locks.detect(&:open?) || locks.first
|
45
|
+
end
|
46
|
+
|
47
|
+
# if 2 of the same, prefer open
|
48
|
+
locks += inherited_locks.for_subject_type(subcls).group_by(&:group_key_for_calc).flat_map do |_, locks|
|
49
|
+
locks.detect(&:open?) || locks.first
|
50
|
+
end
|
51
|
+
|
52
|
+
# if 2 of the same, prefer open
|
53
|
+
locks += owner_locks.for_subject_type(subcls).group_by(&:group_key_for_calc).flat_map do |_, locks|
|
54
|
+
locks.detect(&:open?) || locks.first
|
55
|
+
end
|
56
|
+
|
57
|
+
selected_locks = locks.group_by(&:group_key_for_calc).flat_map do |_, locks|
|
58
|
+
# prefer last one, i.e. the one closest to owner
|
59
|
+
locks.last
|
60
|
+
end
|
61
|
+
|
62
|
+
selected_locks.sort(&Lock.sort).each do |lock|
|
63
|
+
apply_lock_rule(lock)
|
64
|
+
end
|
24
65
|
end
|
25
66
|
end
|
26
67
|
end
|
27
68
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
69
|
+
def model_adapter(model_class, action, options = {})
|
70
|
+
adapter_class = CanCan::ModelAdapters::AbstractAdapter.adapter_class(model_class)
|
71
|
+
# include all rules that apply for descendants as well
|
72
|
+
# so the adapter can exclude include subclasses from critieria
|
73
|
+
rules = ([model_class] + model_class.descendants).inject([]) do |res, cls|
|
74
|
+
res += relevant_rules_for_query(action, cls)
|
75
|
+
res.uniq
|
47
76
|
end
|
77
|
+
adapter_class.new(model_class, rules, options)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def apply_lock_rule(lock)
|
83
|
+
ability_type = lock.outcome ? :can : :cannot
|
84
|
+
cls = lock.subject_class
|
85
|
+
options = lock.options
|
86
|
+
options = options.merge(id: lock.subject_id) if lock.id_lock?
|
87
|
+
action = lock.action
|
88
|
+
send ability_type, action, cls, options
|
48
89
|
end
|
49
90
|
end
|
50
91
|
end
|