simple_cancan 0.1.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 +7 -0
- data/lib/simple_cancan.rb +5 -0
- data/lib/simple_cancan/ability.rb +260 -0
- data/lib/simple_cancan/controller_additions.rb +91 -0
- data/lib/simple_cancan/exceptions.rb +50 -0
- data/lib/simple_cancan/rule.rb +147 -0
- data/lib/simple_cancan/version.rb +3 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 081a175eb3429fa16be74dee2c7a57b5b5d6bf91
|
4
|
+
data.tar.gz: 86dc6d52dd8e69ced01b2c142a864d7dccbf16c8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83e050bc2577d86e994c2d1a294224bb6a0971faf5a772aef0d7efcaf14a8a9c2a4c42cedcd081fb2cdf506779d600bcc487ff24427bb9adba997684103f1f3f
|
7
|
+
data.tar.gz: 9640b98e04a5c430118807289294e5e58a321568eef48541193530d10959828b45486661d95175efb0f10d0ab220de1e62521e200933e691c38414991500ff88
|
@@ -0,0 +1,260 @@
|
|
1
|
+
module SimpleCancan
|
2
|
+
|
3
|
+
module Ability
|
4
|
+
|
5
|
+
def can?(action, subject, *extra_args)
|
6
|
+
match = relevant_rules_for_match(action, subject).detect do |rule|
|
7
|
+
rule.matches_conditions?(action, subject, extra_args)
|
8
|
+
end
|
9
|
+
match ? match.base_behavior : false
|
10
|
+
end
|
11
|
+
|
12
|
+
# Convenience method which works the same as "can?" but returns the opposite value.
|
13
|
+
#
|
14
|
+
# cannot? :destroy, @project
|
15
|
+
#
|
16
|
+
def cannot?(*args)
|
17
|
+
!can?(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Defines which abilities are allowed using two arguments. The first one is the action
|
21
|
+
# you're setting the permission for, the second one is the class of object you're setting it on.
|
22
|
+
#
|
23
|
+
# can :update, Article
|
24
|
+
#
|
25
|
+
# You can pass an array for either of these parameters to match any one.
|
26
|
+
# Here the user has the ability to update or destroy both articles and comments.
|
27
|
+
#
|
28
|
+
# can [:update, :destroy], [Article, Comment]
|
29
|
+
#
|
30
|
+
# You can pass :all to match any object and :manage to match any action. Here are some examples.
|
31
|
+
#
|
32
|
+
# can :manage, :all
|
33
|
+
# can :update, :all
|
34
|
+
# can :manage, Project
|
35
|
+
#
|
36
|
+
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
|
37
|
+
#
|
38
|
+
# can :read, Project, :active => true, :user_id => user.id
|
39
|
+
#
|
40
|
+
# See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions
|
41
|
+
# are also used for initial attributes when building a record in ControllerAdditions#load_resource.
|
42
|
+
#
|
43
|
+
# If the conditions hash does not give you enough control over defining abilities, you can use a block
|
44
|
+
# along with any Ruby code you want.
|
45
|
+
#
|
46
|
+
# can :update, Project do |project|
|
47
|
+
# project.groups.include?(user.group)
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# If the block returns true then the user has that :update ability for that project, otherwise he
|
51
|
+
# will be denied access. The downside to using a block is that it cannot be used to generate
|
52
|
+
# conditions for database queries.
|
53
|
+
#
|
54
|
+
# You can pass custom objects into this "can" method, this is usually done with a symbol
|
55
|
+
# and is useful if a class isn't available to define permissions on.
|
56
|
+
#
|
57
|
+
# can :read, :stats
|
58
|
+
# can? :read, :stats # => true
|
59
|
+
#
|
60
|
+
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
|
61
|
+
#
|
62
|
+
# can :update, Project, :priority => 3
|
63
|
+
# can? :update, Project # => true
|
64
|
+
#
|
65
|
+
# If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
|
66
|
+
# block will always be executed. This allows you to override the full behavior if the permissions are
|
67
|
+
# defined in an external source such as the database.
|
68
|
+
#
|
69
|
+
# can do |action, object_class, object|
|
70
|
+
# # check the database and return true/false
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
def can(action = nil, subject = nil, conditions = nil, &block)
|
74
|
+
rules << Rule.new(true, action, subject, conditions, block)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Defines an ability which cannot be done. Accepts the same arguments as "can".
|
78
|
+
#
|
79
|
+
# can :read, :all
|
80
|
+
# cannot :read, Comment
|
81
|
+
#
|
82
|
+
# A block can be passed just like "can", however if the logic is complex it is recommended
|
83
|
+
# to use the "can" method.
|
84
|
+
#
|
85
|
+
# cannot :read, Product do |product|
|
86
|
+
# product.invisible?
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
def cannot(action = nil, subject = nil, conditions = nil, &block)
|
90
|
+
rules << Rule.new(false, action, subject, conditions, block)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Alias one or more actions into another one.
|
94
|
+
#
|
95
|
+
# alias_action :update, :destroy, :to => :modify
|
96
|
+
# can :modify, Comment
|
97
|
+
#
|
98
|
+
# Then :modify permission will apply to both :update and :destroy requests.
|
99
|
+
#
|
100
|
+
# can? :update, Comment # => true
|
101
|
+
# can? :destroy, Comment # => true
|
102
|
+
#
|
103
|
+
# This only works in one direction. Passing the aliased action into the "can?" call
|
104
|
+
# will not work because aliases are meant to generate more generic actions.
|
105
|
+
#
|
106
|
+
# alias_action :update, :destroy, :to => :modify
|
107
|
+
# can :update, Comment
|
108
|
+
# can? :modify, Comment # => false
|
109
|
+
#
|
110
|
+
# Unless that exact alias is used.
|
111
|
+
#
|
112
|
+
# can :modify, Comment
|
113
|
+
# can? :modify, Comment # => true
|
114
|
+
#
|
115
|
+
# The following aliases are added by default for conveniently mapping common controller actions.
|
116
|
+
#
|
117
|
+
# alias_action :index, :show, :to => :read
|
118
|
+
# alias_action :new, :to => :create
|
119
|
+
# alias_action :edit, :to => :update
|
120
|
+
#
|
121
|
+
# This way one can use params[:action] in the controller to determine the permission.
|
122
|
+
def alias_action(*args)
|
123
|
+
target = args.pop[:to]
|
124
|
+
validate_target(target)
|
125
|
+
aliased_actions[target] ||= []
|
126
|
+
aliased_actions[target] += args
|
127
|
+
end
|
128
|
+
|
129
|
+
# User shouldn't specify targets with names of real actions or it will cause Seg fault
|
130
|
+
def validate_target(target)
|
131
|
+
raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
|
135
|
+
def aliased_actions
|
136
|
+
@aliased_actions ||= default_alias_actions
|
137
|
+
end
|
138
|
+
|
139
|
+
# Removes previously aliased actions including the defaults.
|
140
|
+
def clear_aliased_actions
|
141
|
+
@aliased_actions = {}
|
142
|
+
end
|
143
|
+
|
144
|
+
def model_adapter(model_class, action)
|
145
|
+
adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
|
146
|
+
adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
|
147
|
+
end
|
148
|
+
|
149
|
+
# See ControllerAdditions#authorize! for documentation.
|
150
|
+
def authorize!(action, subject, *args)
|
151
|
+
message = nil
|
152
|
+
if args.last.kind_of?(Hash) && args.last.has_key?(:message)
|
153
|
+
message = args.pop[:message]
|
154
|
+
end
|
155
|
+
if cannot?(action, subject, *args)
|
156
|
+
message ||= unauthorized_message(action, subject)
|
157
|
+
raise AccessDenied.new(message, action, subject)
|
158
|
+
end
|
159
|
+
subject
|
160
|
+
end
|
161
|
+
|
162
|
+
def unauthorized_message(action, subject)
|
163
|
+
keys = unauthorized_message_keys(action, subject)
|
164
|
+
variables = {:action => action.to_s}
|
165
|
+
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
|
166
|
+
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
|
167
|
+
message.blank? ? nil : message
|
168
|
+
end
|
169
|
+
|
170
|
+
def attributes_for(action, subject)
|
171
|
+
attributes = {}
|
172
|
+
relevant_rules(action, subject).map do |rule|
|
173
|
+
attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
|
174
|
+
end
|
175
|
+
attributes
|
176
|
+
end
|
177
|
+
|
178
|
+
def has_block?(action, subject)
|
179
|
+
relevant_rules(action, subject).any?(&:only_block?)
|
180
|
+
end
|
181
|
+
|
182
|
+
def has_raw_sql?(action, subject)
|
183
|
+
relevant_rules(action, subject).any?(&:only_raw_sql?)
|
184
|
+
end
|
185
|
+
|
186
|
+
def merge(ability)
|
187
|
+
ability.send(:rules).each do |rule|
|
188
|
+
rules << rule.dup
|
189
|
+
end
|
190
|
+
self
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def unauthorized_message_keys(action, subject)
|
196
|
+
subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
|
197
|
+
[subject, :all].map do |try_subject|
|
198
|
+
[aliases_for_action(action), :manage].flatten.map do |try_action|
|
199
|
+
:"#{try_action}.#{try_subject}"
|
200
|
+
end
|
201
|
+
end.flatten
|
202
|
+
end
|
203
|
+
|
204
|
+
# Accepts an array of actions and returns an array of actions which match.
|
205
|
+
# This should be called before "matches?" and other checking methods since they
|
206
|
+
# rely on the actions to be expanded.
|
207
|
+
def expand_actions(actions)
|
208
|
+
actions.map do |action|
|
209
|
+
aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action
|
210
|
+
end.flatten
|
211
|
+
end
|
212
|
+
|
213
|
+
# Given an action, it will try to find all of the actions which are aliased to it.
|
214
|
+
# This does the opposite kind of lookup as expand_actions.
|
215
|
+
def aliases_for_action(action)
|
216
|
+
results = [action]
|
217
|
+
aliased_actions.each do |aliased_action, actions|
|
218
|
+
results += aliases_for_action(aliased_action) if actions.include? action
|
219
|
+
end
|
220
|
+
results
|
221
|
+
end
|
222
|
+
|
223
|
+
def rules
|
224
|
+
@rules ||= []
|
225
|
+
end
|
226
|
+
|
227
|
+
# Returns an array of Rule instances which match the action and subject
|
228
|
+
# This does not take into consideration any hash conditions or block statements
|
229
|
+
def relevant_rules(action, subject)
|
230
|
+
rules.reverse.select do |rule|
|
231
|
+
rule.expanded_actions = expand_actions(rule.actions)
|
232
|
+
rule.relevant? action, subject
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def relevant_rules_for_match(action, subject)
|
237
|
+
relevant_rules(action, subject).each do |rule|
|
238
|
+
if rule.only_raw_sql?
|
239
|
+
raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def relevant_rules_for_query(action, subject)
|
245
|
+
relevant_rules(action, subject).each do |rule|
|
246
|
+
if rule.only_block?
|
247
|
+
raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def default_alias_actions
|
253
|
+
{
|
254
|
+
:read => [:index, :show],
|
255
|
+
:create => [:new],
|
256
|
+
:update => [:edit],
|
257
|
+
}
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module SimpleCancan
|
2
|
+
|
3
|
+
module ControllerAdditions
|
4
|
+
module ClassMethods
|
5
|
+
def load_and_authorize_resource(*args)
|
6
|
+
cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def load_resource(*args)
|
10
|
+
cancan_resource_class.add_before_filter(self, :load_resource, *args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def authorize_resource(*args)
|
14
|
+
cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def skip_load_and_authorize_resource(*args)
|
18
|
+
skip_load_resource(*args)
|
19
|
+
skip_authorize_resource(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def skip_load_resource(*args)
|
23
|
+
options = args.extract_options!
|
24
|
+
name = args.first
|
25
|
+
cancan_skipper[:load][name] = options
|
26
|
+
end
|
27
|
+
|
28
|
+
def skip_authorize_resource(*args)
|
29
|
+
options = args.extract_options!
|
30
|
+
name = args.first
|
31
|
+
cancan_skipper[:authorize][name] = options
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_authorization(options = {})
|
35
|
+
self.after_filter(options.slice(:only, :except)) do |controller|
|
36
|
+
next if controller.instance_variable_defined?(:@_authorized)
|
37
|
+
next if options[:if] && !controller.send(options[:if])
|
38
|
+
next if options[:unless] && controller.send(options[:unless])
|
39
|
+
raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def skip_authorization_check(*args)
|
44
|
+
self.before_filter(*args) do |controller|
|
45
|
+
controller.instance_variable_set(:@_authorized, true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def skip_authorization(*args)
|
50
|
+
raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
|
51
|
+
end
|
52
|
+
|
53
|
+
def cancan_resource_class
|
54
|
+
if ancestors.map(&:to_s).include? "InheritedResources::Actions"
|
55
|
+
InheritedResource
|
56
|
+
else
|
57
|
+
ControllerResource
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def cancan_skipper
|
62
|
+
@_cancan_skipper ||= {:authorize => {}, :load => {}}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.included(base)
|
67
|
+
base.extend ClassMethods
|
68
|
+
end
|
69
|
+
|
70
|
+
def authorize!(*args)
|
71
|
+
@_authorized = true
|
72
|
+
current_ability.authorize!(*args)
|
73
|
+
end
|
74
|
+
|
75
|
+
def unauthorized!(message = nil)
|
76
|
+
raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
|
77
|
+
end
|
78
|
+
|
79
|
+
def current_ability
|
80
|
+
@current_ability ||= ::Ability.new(current_account)
|
81
|
+
end
|
82
|
+
|
83
|
+
def can?(*args)
|
84
|
+
current_ability.can?(*args)
|
85
|
+
end
|
86
|
+
|
87
|
+
def cannot?(*args)
|
88
|
+
current_ability.cannot?(*args)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module SimpleCancan
|
2
|
+
# A general CanCan exception
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
# Raised when behavior is not implemented, usually used in an abstract class.
|
6
|
+
class NotImplemented < Error; end
|
7
|
+
|
8
|
+
# Raised when removed code is called, an alternative solution is provided in message.
|
9
|
+
class ImplementationRemoved < Error; end
|
10
|
+
|
11
|
+
# Raised when using check_authorization without calling authorized!
|
12
|
+
class AuthorizationNotPerformed < Error; end
|
13
|
+
|
14
|
+
# This error is raised when a user isn't allowed to access a given controller action.
|
15
|
+
# This usually happens within a call to ControllerAdditions#authorize! but can be
|
16
|
+
# raised manually.
|
17
|
+
#
|
18
|
+
# raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
|
19
|
+
#
|
20
|
+
# The passed message, action, and subject are optional and can later be retrieved when
|
21
|
+
# rescuing from the exception.
|
22
|
+
#
|
23
|
+
# exception.message # => "Not authorized!"
|
24
|
+
# exception.action # => :read
|
25
|
+
# exception.subject # => Article
|
26
|
+
#
|
27
|
+
# If the message is not specified (or is nil) it will default to "You are not authorized
|
28
|
+
# to access this page." This default can be overridden by setting default_message.
|
29
|
+
#
|
30
|
+
# exception.default_message = "Default error message"
|
31
|
+
# exception.message # => "Default error message"
|
32
|
+
#
|
33
|
+
# See ControllerAdditions#authorized! for more information on rescuing from this exception
|
34
|
+
# and customizing the message using I18n.
|
35
|
+
class AccessDenied < Error
|
36
|
+
attr_reader :action, :subject
|
37
|
+
attr_writer :default_message
|
38
|
+
|
39
|
+
def initialize(message = nil, action = nil, subject = nil)
|
40
|
+
@message = message
|
41
|
+
@action = action
|
42
|
+
@subject = subject
|
43
|
+
@default_message = I18n.t(:"unauthorized.default", :default => "You are not authorized to access this page.")
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
@message || @default_message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module SimpleCancan
|
2
|
+
# This class is used internally and should only be called through Ability.
|
3
|
+
# it holds the information about a "can" call made on Ability and provides
|
4
|
+
# helpful methods to determine permission checking and conditions hash generation.
|
5
|
+
class Rule # :nodoc:
|
6
|
+
attr_reader :base_behavior, :subjects, :actions, :conditions
|
7
|
+
attr_writer :expanded_actions
|
8
|
+
|
9
|
+
# The first argument when initializing is the base_behavior which is a true/false
|
10
|
+
# value. True for "can" and false for "cannot". The next two arguments are the action
|
11
|
+
# and subject respectively (such as :read, @project). The third argument is a hash
|
12
|
+
# of conditions and the last one is the block passed to the "can" call.
|
13
|
+
def initialize(base_behavior, action, subject, conditions, block)
|
14
|
+
raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil?
|
15
|
+
@match_all = action.nil? && subject.nil?
|
16
|
+
@base_behavior = base_behavior
|
17
|
+
@actions = [action].flatten
|
18
|
+
@subjects = [subject].flatten
|
19
|
+
@conditions = conditions || {}
|
20
|
+
@block = block
|
21
|
+
end
|
22
|
+
|
23
|
+
# Matches both the subject and action, not necessarily the conditions
|
24
|
+
def relevant?(action, subject)
|
25
|
+
subject = subject.values.first if subject.class == Hash
|
26
|
+
@match_all || (matches_action?(action) && matches_subject?(subject))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Matches the block or conditions hash
|
30
|
+
def matches_conditions?(action, subject, extra_args)
|
31
|
+
if @match_all
|
32
|
+
call_block_with_all(action, subject, extra_args)
|
33
|
+
elsif @block && !subject_class?(subject)
|
34
|
+
@block.call(subject, *extra_args)
|
35
|
+
elsif @conditions.kind_of?(Hash) && subject.class == Hash
|
36
|
+
nested_subject_matches_conditions?(subject)
|
37
|
+
elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
|
38
|
+
matches_conditions_hash?(subject)
|
39
|
+
else
|
40
|
+
# Don't stop at "cannot" definitions when there are conditions.
|
41
|
+
@conditions.empty? ? true : @base_behavior
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def only_block?
|
46
|
+
conditions_empty? && !@block.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def only_raw_sql?
|
50
|
+
@block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
|
51
|
+
end
|
52
|
+
|
53
|
+
def conditions_empty?
|
54
|
+
@conditions == {} || @conditions.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def unmergeable?
|
58
|
+
@conditions.respond_to?(:keys) && @conditions.present? &&
|
59
|
+
(!@conditions.keys.first.kind_of? Symbol)
|
60
|
+
end
|
61
|
+
|
62
|
+
def associations_hash(conditions = @conditions)
|
63
|
+
hash = {}
|
64
|
+
conditions.map do |name, value|
|
65
|
+
hash[name] = associations_hash(value) if value.kind_of? Hash
|
66
|
+
end if conditions.kind_of? Hash
|
67
|
+
hash
|
68
|
+
end
|
69
|
+
|
70
|
+
def attributes_from_conditions
|
71
|
+
attributes = {}
|
72
|
+
@conditions.each do |key, value|
|
73
|
+
attributes[key] = value unless [Array, Range, Hash].include? value.class
|
74
|
+
end if @conditions.kind_of? Hash
|
75
|
+
attributes
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def subject_class?(subject)
|
81
|
+
klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
|
82
|
+
klass == Class || klass == Module
|
83
|
+
end
|
84
|
+
|
85
|
+
def matches_action?(action)
|
86
|
+
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
|
87
|
+
end
|
88
|
+
|
89
|
+
def matches_subject?(subject)
|
90
|
+
@subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
|
91
|
+
end
|
92
|
+
|
93
|
+
def matches_subject_class?(subject)
|
94
|
+
@subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Checks if the given subject matches the given conditions hash.
|
98
|
+
# This behavior can be overriden by a model adapter by defining two class methods:
|
99
|
+
# override_matching_for_conditions?(subject, conditions) and
|
100
|
+
# matches_conditions_hash?(subject, conditions)
|
101
|
+
def matches_conditions_hash?(subject, conditions = @conditions)
|
102
|
+
if conditions.empty?
|
103
|
+
true
|
104
|
+
else
|
105
|
+
if model_adapter(subject).override_conditions_hash_matching? subject, conditions
|
106
|
+
model_adapter(subject).matches_conditions_hash? subject, conditions
|
107
|
+
else
|
108
|
+
conditions.all? do |name, value|
|
109
|
+
if model_adapter(subject).override_condition_matching? subject, name, value
|
110
|
+
model_adapter(subject).matches_condition? subject, name, value
|
111
|
+
else
|
112
|
+
attribute = subject.send(name)
|
113
|
+
if value.kind_of?(Hash)
|
114
|
+
if attribute.kind_of? Array
|
115
|
+
attribute.any? { |element| matches_conditions_hash? element, value }
|
116
|
+
else
|
117
|
+
!attribute.nil? && matches_conditions_hash?(attribute, value)
|
118
|
+
end
|
119
|
+
elsif !value.is_a?(String) && value.kind_of?(Enumerable)
|
120
|
+
value.include? attribute
|
121
|
+
else
|
122
|
+
attribute == value
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def nested_subject_matches_conditions?(subject_hash)
|
131
|
+
parent, child = subject_hash.first
|
132
|
+
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
|
133
|
+
end
|
134
|
+
|
135
|
+
def call_block_with_all(action, subject, extra_args)
|
136
|
+
if subject.class == Class
|
137
|
+
@block.call(action, subject, nil, *extra_args)
|
138
|
+
else
|
139
|
+
@block.call(action, subject.class, subject, *extra_args)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def model_adapter(subject)
|
144
|
+
CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_cancan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tianliang Bai
|
8
|
+
- Fuhao Xu
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-08-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ~>
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.10'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.10'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
description: A simpler cancan for padrino
|
43
|
+
email:
|
44
|
+
- happybyronbai@gmail.com
|
45
|
+
- xufuhaomap@gmail.com
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- lib/simple_cancan.rb
|
51
|
+
- lib/simple_cancan/ability.rb
|
52
|
+
- lib/simple_cancan/controller_additions.rb
|
53
|
+
- lib/simple_cancan/exceptions.rb
|
54
|
+
- lib/simple_cancan/rule.rb
|
55
|
+
- lib/simple_cancan/version.rb
|
56
|
+
homepage: https://rubygems.org/gems/simple_cancan
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.4.8
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: A simpler cancan for padrino
|
80
|
+
test_files: []
|