cancancan 1.8.0 → 1.14.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/.travis.yml +12 -11
- data/Appraisals +23 -16
- data/CHANGELOG.rdoc +75 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +217 -0
- data/cancancan.gemspec +5 -8
- data/gemfiles/{activerecord_3.1.gemfile → activerecord_4.0.gemfile} +2 -1
- data/gemfiles/{activerecord_3.0.gemfile → activerecord_4.1.gemfile} +2 -3
- data/gemfiles/activerecord_4.2.gemfile +18 -0
- data/gemfiles/sequel_3.x.gemfile +0 -1
- data/lib/cancan/ability.rb +94 -17
- data/lib/cancan/controller_additions.rb +1 -1
- data/lib/cancan/controller_resource.rb +32 -24
- data/lib/cancan/matchers.rb +29 -6
- data/lib/cancan/model_adapters/active_record_3_adapter.rb +16 -0
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +55 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +9 -41
- data/lib/cancan/model_adapters/mongoid_adapter.rb +23 -2
- data/lib/cancan/rule.rb +10 -5
- data/lib/cancan/version.rb +1 -1
- data/lib/cancan.rb +11 -2
- data/lib/generators/cancan/ability/templates/ability.rb +1 -1
- data/spec/README.rdoc +2 -2
- data/spec/cancan/ability_spec.rb +174 -111
- data/spec/cancan/controller_additions_spec.rb +8 -8
- data/spec/cancan/controller_resource_spec.rb +96 -79
- data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +154 -0
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +92 -64
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +26 -6
- data/spec/cancan/model_adapters/sequel_adapter_spec.rb +49 -65
- data/spec/matchers.rb +2 -2
- data/spec/spec_helper.rb +3 -2
- metadata +19 -40
- data/README.rdoc +0 -183
- data/gemfiles/datamapper_1.x.gemfile +0 -10
- data/lib/cancan/model_adapters/data_mapper_adapter.rb +0 -34
- data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +0 -119
data/lib/cancan/ability.rb
CHANGED
|
@@ -61,14 +61,11 @@ module CanCan
|
|
|
61
61
|
#
|
|
62
62
|
# Also see the RSpec Matchers to aid in testing.
|
|
63
63
|
def can?(action, subject, *extra_args)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
relevant_rules_for_match(action, subject).detect do |rule|
|
|
68
|
-
rule.matches_conditions?(action, subject, extra_args)
|
|
64
|
+
match = extract_subjects(subject).lazy.map do |a_subject|
|
|
65
|
+
relevant_rules_for_match(action, a_subject).detect do |rule|
|
|
66
|
+
rule.matches_conditions?(action, a_subject, extra_args)
|
|
69
67
|
end
|
|
70
|
-
end.
|
|
71
|
-
|
|
68
|
+
end.reject(&:nil?).first
|
|
72
69
|
match ? match.base_behavior : false
|
|
73
70
|
end
|
|
74
71
|
# Convenience method which works the same as "can?" but returns the opposite value.
|
|
@@ -119,7 +116,7 @@ module CanCan
|
|
|
119
116
|
# can :read, :stats
|
|
120
117
|
# can? :read, :stats # => true
|
|
121
118
|
#
|
|
122
|
-
# IMPORTANT: Neither a hash of conditions
|
|
119
|
+
# IMPORTANT: Neither a hash of conditions nor a block will be used when checking permission on a class.
|
|
123
120
|
#
|
|
124
121
|
# can :update, Project, :priority => 3
|
|
125
122
|
# can? :update, Project # => true
|
|
@@ -133,7 +130,7 @@ module CanCan
|
|
|
133
130
|
# end
|
|
134
131
|
#
|
|
135
132
|
def can(action = nil, subject = nil, conditions = nil, &block)
|
|
136
|
-
|
|
133
|
+
add_rule(Rule.new(true, action, subject, conditions, block))
|
|
137
134
|
end
|
|
138
135
|
|
|
139
136
|
# Defines an ability which cannot be done. Accepts the same arguments as "can".
|
|
@@ -149,7 +146,7 @@ module CanCan
|
|
|
149
146
|
# end
|
|
150
147
|
#
|
|
151
148
|
def cannot(action = nil, subject = nil, conditions = nil, &block)
|
|
152
|
-
|
|
149
|
+
add_rule(Rule.new(false, action, subject, conditions, block))
|
|
153
150
|
end
|
|
154
151
|
|
|
155
152
|
# Alias one or more actions into another one.
|
|
@@ -246,12 +243,49 @@ module CanCan
|
|
|
246
243
|
end
|
|
247
244
|
|
|
248
245
|
def merge(ability)
|
|
249
|
-
ability.
|
|
250
|
-
|
|
246
|
+
ability.rules.each do |rule|
|
|
247
|
+
add_rule(rule.dup)
|
|
251
248
|
end
|
|
252
249
|
self
|
|
253
250
|
end
|
|
254
251
|
|
|
252
|
+
# Return a hash of permissions for the user in the format of:
|
|
253
|
+
# {
|
|
254
|
+
# can: can_hash,
|
|
255
|
+
# cannot: cannot_hash
|
|
256
|
+
# }
|
|
257
|
+
#
|
|
258
|
+
# Where can_hash and cannot_hash are formatted thusly:
|
|
259
|
+
# {
|
|
260
|
+
# action: array_of_objects
|
|
261
|
+
# }
|
|
262
|
+
def permissions
|
|
263
|
+
permissions_list = {:can => {}, :cannot => {}}
|
|
264
|
+
|
|
265
|
+
rules.each do |rule|
|
|
266
|
+
subjects = rule.subjects
|
|
267
|
+
expand_actions(rule.actions).each do |action|
|
|
268
|
+
if(rule.base_behavior)
|
|
269
|
+
permissions_list[:can][action] ||= []
|
|
270
|
+
permissions_list[:can][action] += subjects.map(&:to_s)
|
|
271
|
+
else
|
|
272
|
+
permissions_list[:cannot][action] ||= []
|
|
273
|
+
permissions_list[:cannot][action] += subjects.map(&:to_s)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
permissions_list
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
protected
|
|
282
|
+
|
|
283
|
+
# Must be protected as an ability can merge with other abilities.
|
|
284
|
+
# This means that an ability must expose their rules with another ability.
|
|
285
|
+
def rules
|
|
286
|
+
@rules ||= []
|
|
287
|
+
end
|
|
288
|
+
|
|
255
289
|
private
|
|
256
290
|
|
|
257
291
|
def unauthorized_message_keys(action, subject)
|
|
@@ -285,7 +319,7 @@ module CanCan
|
|
|
285
319
|
|
|
286
320
|
# It translates to an array the subject or the hash with multiple subjects given to can?.
|
|
287
321
|
def extract_subjects(subject)
|
|
288
|
-
|
|
322
|
+
if subject.kind_of?(Hash) && subject.key?(:any)
|
|
289
323
|
subject[:any]
|
|
290
324
|
else
|
|
291
325
|
[subject]
|
|
@@ -302,21 +336,64 @@ module CanCan
|
|
|
302
336
|
results
|
|
303
337
|
end
|
|
304
338
|
|
|
305
|
-
def
|
|
306
|
-
|
|
339
|
+
def add_rule(rule)
|
|
340
|
+
rules << rule
|
|
341
|
+
add_rule_to_index(rule, rules.size - 1)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def add_rule_to_index(rule, position)
|
|
345
|
+
@rules_index ||= Hash.new { |h, k| h[k] = [] }
|
|
346
|
+
|
|
347
|
+
subjects = rule.subjects.compact
|
|
348
|
+
subjects << :all if subjects.empty?
|
|
349
|
+
|
|
350
|
+
subjects.each do |subject|
|
|
351
|
+
@rules_index[subject] << position
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def alternative_subjects(subject)
|
|
356
|
+
subject = subject.class unless subject.is_a?(Module)
|
|
357
|
+
[:all, *subject.ancestors, subject.class.to_s]
|
|
307
358
|
end
|
|
308
359
|
|
|
309
360
|
# Returns an array of Rule instances which match the action and subject
|
|
310
361
|
# This does not take into consideration any hash conditions or block statements
|
|
311
362
|
def relevant_rules(action, subject)
|
|
312
|
-
|
|
363
|
+
return [] unless @rules
|
|
364
|
+
relevant = possible_relevant_rules(subject).select do |rule|
|
|
313
365
|
rule.expanded_actions = expand_actions(rule.actions)
|
|
314
366
|
rule.relevant? action, subject
|
|
315
367
|
end
|
|
316
|
-
relevant.reverse!
|
|
368
|
+
relevant.reverse!.uniq!
|
|
369
|
+
optimize_order! relevant
|
|
317
370
|
relevant
|
|
318
371
|
end
|
|
319
372
|
|
|
373
|
+
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
|
|
374
|
+
def optimize_order!(rules)
|
|
375
|
+
first_can_in_group = -1
|
|
376
|
+
rules.each_with_index do |rule, i|
|
|
377
|
+
(first_can_in_group = -1) and next unless rule.base_behavior
|
|
378
|
+
(first_can_in_group = i) and next if first_can_in_group == -1
|
|
379
|
+
if rule.subjects == [:all]
|
|
380
|
+
rules[i] = rules[first_can_in_group]
|
|
381
|
+
rules[first_can_in_group] = rule
|
|
382
|
+
first_can_in_group += 1
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def possible_relevant_rules(subject)
|
|
388
|
+
if subject.is_a?(Hash)
|
|
389
|
+
rules
|
|
390
|
+
else
|
|
391
|
+
positions = @rules_index.values_at(subject, *alternative_subjects(subject))
|
|
392
|
+
positions.flatten!.sort!
|
|
393
|
+
positions.map { |i| @rules[i] }
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
320
397
|
def relevant_rules_for_match(action, subject)
|
|
321
398
|
relevant_rules(action, subject).each do |rule|
|
|
322
399
|
if rule.only_raw_sql?
|
|
@@ -294,7 +294,7 @@ module CanCan
|
|
|
294
294
|
|
|
295
295
|
def self.included(base)
|
|
296
296
|
base.extend ClassMethods
|
|
297
|
-
base.helper_method :can?, :cannot?, :current_ability
|
|
297
|
+
base.helper_method :can?, :cannot?, :current_ability if base.respond_to? :helper_method
|
|
298
298
|
end
|
|
299
299
|
|
|
300
300
|
# Raises a CanCan::AccessDenied exception if the current_ability cannot
|
|
@@ -46,17 +46,12 @@ module CanCan
|
|
|
46
46
|
@options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
def skip?(behavior)
|
|
50
|
-
options = @controller.class.cancan_skipper[behavior][@name]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym)
|
|
56
|
-
true
|
|
57
|
-
elsif [options[:only]].flatten.include?(@params[:action].to_sym)
|
|
58
|
-
true
|
|
59
|
-
end
|
|
49
|
+
def skip?(behavior)
|
|
50
|
+
return false unless options = @controller.class.cancan_skipper[behavior][@name]
|
|
51
|
+
|
|
52
|
+
options == {} ||
|
|
53
|
+
options[:except] && !action_exists_in?(options[:except]) ||
|
|
54
|
+
action_exists_in?(options[:only])
|
|
60
55
|
end
|
|
61
56
|
|
|
62
57
|
protected
|
|
@@ -123,7 +118,11 @@ module CanCan
|
|
|
123
118
|
end
|
|
124
119
|
|
|
125
120
|
def authorization_action
|
|
126
|
-
parent? ?
|
|
121
|
+
parent? ? parent_authorization_action : @params[:action].to_sym
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def parent_authorization_action
|
|
125
|
+
@options[:parent_action] || :show
|
|
127
126
|
end
|
|
128
127
|
|
|
129
128
|
def id_param
|
|
@@ -220,22 +219,29 @@ module CanCan
|
|
|
220
219
|
end
|
|
221
220
|
|
|
222
221
|
def resource_params
|
|
223
|
-
if
|
|
222
|
+
if parameters_require_sanitizing? && params_method.present?
|
|
224
223
|
return case params_method
|
|
225
224
|
when Symbol then @controller.send(params_method)
|
|
226
225
|
when String then @controller.instance_eval(params_method)
|
|
227
226
|
when Proc then params_method.call(@controller)
|
|
228
227
|
end
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return @params[params_key] if @params[params_key]
|
|
228
|
+
else
|
|
229
|
+
resource_params_by_namespaced_name
|
|
232
230
|
end
|
|
231
|
+
end
|
|
233
232
|
|
|
234
|
-
|
|
233
|
+
def parameters_require_sanitizing?
|
|
234
|
+
save_actions.include?(@params[:action].to_sym) || resource_params_by_namespaced_name.present?
|
|
235
235
|
end
|
|
236
236
|
|
|
237
237
|
def resource_params_by_namespaced_name
|
|
238
|
-
@params
|
|
238
|
+
if @options[:instance_name] && @params.has_key?(extract_key(@options[:instance_name]))
|
|
239
|
+
@params[extract_key(@options[:instance_name])]
|
|
240
|
+
elsif @options[:class] && @params.has_key?(extract_key(@options[:class]))
|
|
241
|
+
@params[extract_key(@options[:class])]
|
|
242
|
+
else
|
|
243
|
+
@params[extract_key(namespaced_name)]
|
|
244
|
+
end
|
|
239
245
|
end
|
|
240
246
|
|
|
241
247
|
def params_method
|
|
@@ -252,17 +258,15 @@ module CanCan
|
|
|
252
258
|
end
|
|
253
259
|
|
|
254
260
|
def namespace
|
|
255
|
-
@params[:controller].split(
|
|
261
|
+
@params[:controller].split('/')[0..-2]
|
|
256
262
|
end
|
|
257
263
|
|
|
258
264
|
def namespaced_name
|
|
259
|
-
[namespace, name
|
|
260
|
-
rescue NameError
|
|
261
|
-
name
|
|
265
|
+
([namespace, name] * '/').singularize.camelize.safe_constantize || name
|
|
262
266
|
end
|
|
263
267
|
|
|
264
268
|
def name_from_controller
|
|
265
|
-
@params[:controller].
|
|
269
|
+
@params[:controller].split('/').last.singularize
|
|
266
270
|
end
|
|
267
271
|
|
|
268
272
|
def instance_name
|
|
@@ -277,12 +281,16 @@ module CanCan
|
|
|
277
281
|
[:new, :create] + Array(@options[:new])
|
|
278
282
|
end
|
|
279
283
|
|
|
280
|
-
def
|
|
284
|
+
def save_actions
|
|
281
285
|
[:create, :update]
|
|
282
286
|
end
|
|
283
287
|
|
|
284
288
|
private
|
|
285
289
|
|
|
290
|
+
def action_exists_in?(options)
|
|
291
|
+
Array(options).include?(@params[:action].to_sym)
|
|
292
|
+
end
|
|
293
|
+
|
|
286
294
|
def extract_key(value)
|
|
287
295
|
value.to_s.underscore.gsub('/', '_')
|
|
288
296
|
end
|
data/lib/cancan/matchers.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec'
|
|
1
|
+
rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # RSpec 1 compatability
|
|
2
2
|
|
|
3
3
|
if rspec_module == 'RSpec'
|
|
4
4
|
require 'rspec/core'
|
|
5
5
|
require 'rspec/expectations'
|
|
6
6
|
else
|
|
7
|
-
ActiveSupport::Deprecation.warn(
|
|
7
|
+
ActiveSupport::Deprecation.warn('RSpec < 3 will not be supported in the CanCanCan >= 2.0.0')
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
|
|
@@ -12,11 +12,34 @@ Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
|
|
|
12
12
|
ability.can?(*args)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
# Check that RSpec is < 2.99
|
|
16
|
+
if !respond_to?(:failure_message) && respond_to?(:failure_message_for_should)
|
|
17
|
+
alias :failure_message :failure_message_for_should
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
if !respond_to?(:failure_message_when_negated) && respond_to?(:failure_message_for_should_not)
|
|
21
|
+
alias :failure_message_when_negated :failure_message_for_should_not
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
failure_message do
|
|
25
|
+
resource = args[1]
|
|
26
|
+
if resource.instance_of?(Class)
|
|
27
|
+
"expected to be able to #{args.map(&:to_s).join(' ')}"
|
|
28
|
+
else
|
|
29
|
+
"expected to be able to #{args.map(&:inspect).join(' ')}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
failure_message_when_negated do
|
|
34
|
+
resource = args[1]
|
|
35
|
+
if resource.instance_of?(Class)
|
|
36
|
+
"expected not to be able to #{args.map(&:to_s).join(' ')}"
|
|
37
|
+
else
|
|
38
|
+
"expected not to be able to #{args.map(&:inspect).join(' ')}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
description do
|
|
43
|
+
"be able to #{args.map(&:to_s).join(' ')}"
|
|
21
44
|
end
|
|
22
45
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module CanCan
|
|
2
|
+
module ModelAdapters
|
|
3
|
+
class ActiveRecord3Adapter < AbstractAdapter
|
|
4
|
+
include ActiveRecordAdapter
|
|
5
|
+
def self.for_class?(model_class)
|
|
6
|
+
model_class <= ActiveRecord::Base
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def build_relation(*where_conditions)
|
|
12
|
+
@model_class.where(*where_conditions).includes(joins)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module CanCan
|
|
2
|
+
module ModelAdapters
|
|
3
|
+
class ActiveRecord4Adapter < AbstractAdapter
|
|
4
|
+
include ActiveRecordAdapter
|
|
5
|
+
def self.for_class?(model_class)
|
|
6
|
+
model_class <= ActiveRecord::Base
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# As of rails 4, `includes()` no longer causes active record to
|
|
12
|
+
# look inside the where clause to decide to outer join tables
|
|
13
|
+
# you're using in the where. Instead, `references()` is required
|
|
14
|
+
# in addition to `includes()` to force the outer join.
|
|
15
|
+
def build_relation(*where_conditions)
|
|
16
|
+
relation = @model_class.where(*where_conditions)
|
|
17
|
+
relation = relation.includes(joins).references(joins) if joins.present?
|
|
18
|
+
relation
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.override_condition_matching?(subject, name, value)
|
|
22
|
+
# ActiveRecord introduced enums in version 4.1.
|
|
23
|
+
ActiveRecord::VERSION::MINOR >= 1 &&
|
|
24
|
+
subject.class.defined_enums.include?(name.to_s)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.matches_condition?(subject, name, value)
|
|
28
|
+
# Get the mapping from enum strings to values.
|
|
29
|
+
enum = subject.class.send(name.to_s.pluralize)
|
|
30
|
+
# Get the value of the attribute as an integer.
|
|
31
|
+
attribute = enum[subject.send(name)]
|
|
32
|
+
# Check to see if the value matches the condition.
|
|
33
|
+
value.is_a?(Enumerable) ?
|
|
34
|
+
(value.include? attribute) :
|
|
35
|
+
attribute == value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
|
|
39
|
+
def sanitize_sql(conditions)
|
|
40
|
+
if ActiveRecord::VERSION::MINOR >= 2 && Hash === conditions
|
|
41
|
+
table = Arel::Table.new(@model_class.send(:table_name))
|
|
42
|
+
|
|
43
|
+
conditions = ActiveRecord::PredicateBuilder.resolve_column_aliases @model_class, conditions
|
|
44
|
+
conditions = @model_class.send(:expand_hash_conditions_for_aggregates, conditions)
|
|
45
|
+
|
|
46
|
+
ActiveRecord::PredicateBuilder.build_from_hash(@model_class, conditions, table).map { |b|
|
|
47
|
+
@model_class.send(:connection).visitor.compile b
|
|
48
|
+
}.join(' AND ')
|
|
49
|
+
else
|
|
50
|
+
@model_class.send(:sanitize_sql, conditions)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -1,41 +1,6 @@
|
|
|
1
1
|
module CanCan
|
|
2
2
|
module ModelAdapters
|
|
3
|
-
|
|
4
|
-
def self.for_class?(model_class)
|
|
5
|
-
model_class <= ActiveRecord::Base
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def self.override_condition_matching?(subject, name, value)
|
|
9
|
-
name.kind_of?(MetaWhere::Column) if defined? MetaWhere
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def self.matches_condition?(subject, name, value)
|
|
13
|
-
subject_value = subject.send(name.column)
|
|
14
|
-
if name.method.to_s.ends_with? "_any"
|
|
15
|
-
value.any? { |v| meta_where_match? subject_value, name.method.to_s.sub("_any", ""), v }
|
|
16
|
-
elsif name.method.to_s.ends_with? "_all"
|
|
17
|
-
value.all? { |v| meta_where_match? subject_value, name.method.to_s.sub("_all", ""), v }
|
|
18
|
-
else
|
|
19
|
-
meta_where_match? subject_value, name.method, value
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.meta_where_match?(subject_value, method, value)
|
|
24
|
-
case method.to_sym
|
|
25
|
-
when :eq then subject_value == value
|
|
26
|
-
when :not_eq then subject_value != value
|
|
27
|
-
when :in then value.include?(subject_value)
|
|
28
|
-
when :not_in then !value.include?(subject_value)
|
|
29
|
-
when :lt then subject_value < value
|
|
30
|
-
when :lteq then subject_value <= value
|
|
31
|
-
when :gt then subject_value > value
|
|
32
|
-
when :gteq then subject_value >= value
|
|
33
|
-
when :matches then subject_value =~ Regexp.new("^" + Regexp.escape(value).gsub("%", ".*") + "$", true)
|
|
34
|
-
when :does_not_match then !meta_where_match?(subject_value, :matches, value)
|
|
35
|
-
else raise NotImplemented, "The #{method} MetaWhere condition is not supported."
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
3
|
+
module ActiveRecordAdapter
|
|
39
4
|
# Returns conditions intended to be used inside a database query. Normally you will not call this
|
|
40
5
|
# method directly, but instead go through ModelAdditions#accessible_by.
|
|
41
6
|
#
|
|
@@ -67,7 +32,7 @@ module CanCan
|
|
|
67
32
|
conditions.inject({}) do |result_hash, (name, value)|
|
|
68
33
|
if value.kind_of? Hash
|
|
69
34
|
value = value.dup
|
|
70
|
-
association_class = model_class.reflect_on_association(name).
|
|
35
|
+
association_class = model_class.reflect_on_association(name).klass.name.constantize
|
|
71
36
|
nested = value.inject({}) do |nested,(k,v)|
|
|
72
37
|
if v.kind_of? Hash
|
|
73
38
|
value.delete(k)
|
|
@@ -99,11 +64,10 @@ module CanCan
|
|
|
99
64
|
if override_scope
|
|
100
65
|
@model_class.where(nil).merge(override_scope)
|
|
101
66
|
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
|
|
102
|
-
mergeable_conditions
|
|
103
|
-
|
|
104
|
-
@model_class.where(conditions).includes(joins)
|
|
67
|
+
if mergeable_conditions?
|
|
68
|
+
build_relation(conditions)
|
|
105
69
|
else
|
|
106
|
-
|
|
70
|
+
build_relation(*(@rules.map(&:conditions)))
|
|
107
71
|
end
|
|
108
72
|
else
|
|
109
73
|
@model_class.all(:conditions => conditions, :joins => joins)
|
|
@@ -112,6 +76,10 @@ module CanCan
|
|
|
112
76
|
|
|
113
77
|
private
|
|
114
78
|
|
|
79
|
+
def mergeable_conditions?
|
|
80
|
+
@rules.find {|rule| rule.unmergeable? }.blank?
|
|
81
|
+
end
|
|
82
|
+
|
|
115
83
|
def override_scope
|
|
116
84
|
conditions = @rules.map(&:conditions).compact
|
|
117
85
|
if defined?(ActiveRecord::Relation) && conditions.any? { |c| c.kind_of?(ActiveRecord::Relation) }
|
|
@@ -35,15 +35,36 @@ module CanCan
|
|
|
35
35
|
|
|
36
36
|
rules.inject(@model_class.all) do |records, rule|
|
|
37
37
|
if process_can_rules && rule.base_behavior
|
|
38
|
-
records.or rule.conditions
|
|
38
|
+
records.or simplify_relations(@model_class, rule.conditions)
|
|
39
39
|
elsif !rule.base_behavior
|
|
40
|
-
records.excludes rule.conditions
|
|
40
|
+
records.excludes simplify_relations(@model_class, rule.conditions)
|
|
41
41
|
else
|
|
42
42
|
records
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
# Look for criteria on relations and replace with simple id queries
|
|
50
|
+
# eg.
|
|
51
|
+
# {user: {:tags.all => []}} becomes {"user_id" => {"$in" => [__, ..]}}
|
|
52
|
+
# {user: {:session => {:tags.all => []}}} becomes {"user_id" => {"session_id" => {"$in" => [__, ..]} }}
|
|
53
|
+
def simplify_relations model_class, conditions
|
|
54
|
+
model_relations = model_class.relations.with_indifferent_access
|
|
55
|
+
Hash[
|
|
56
|
+
conditions.map {|k,v|
|
|
57
|
+
if relation = model_relations[k]
|
|
58
|
+
relation_class_name = relation[:class_name].blank? ? k.to_s.classify : relation[:class_name]
|
|
59
|
+
v = simplify_relations(relation_class_name.constantize, v)
|
|
60
|
+
relation_ids = relation_class_name.constantize.where(v).only(:id).map(&:id)
|
|
61
|
+
k = "#{k}_id"
|
|
62
|
+
v = { "$in" => relation_ids }
|
|
63
|
+
end
|
|
64
|
+
[k,v]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
end
|
|
47
68
|
end
|
|
48
69
|
end
|
|
49
70
|
end
|
data/lib/cancan/rule.rb
CHANGED
|
@@ -91,7 +91,11 @@ module CanCan
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def matches_subject_class?(subject)
|
|
94
|
-
@subjects.any?
|
|
94
|
+
@subjects.any? do |sub|
|
|
95
|
+
sub.kind_of?(Module) && (subject.kind_of?(sub) ||
|
|
96
|
+
subject.class.to_s == sub.to_s ||
|
|
97
|
+
(subject.kind_of?(Module) && subject.ancestors.include?(sub)))
|
|
98
|
+
end
|
|
95
99
|
end
|
|
96
100
|
|
|
97
101
|
# Checks if the given subject matches the given conditions hash.
|
|
@@ -108,15 +112,15 @@ module CanCan
|
|
|
108
112
|
|
|
109
113
|
conditions.all? do |name, value|
|
|
110
114
|
if adapter.override_condition_matching?(subject, name, value)
|
|
111
|
-
|
|
115
|
+
adapter.matches_condition?(subject, name, value)
|
|
116
|
+
else
|
|
117
|
+
condition_match?(subject.send(name), value)
|
|
112
118
|
end
|
|
113
|
-
|
|
114
|
-
condition_match?(subject.send(name), value)
|
|
115
119
|
end
|
|
116
120
|
end
|
|
117
121
|
|
|
118
122
|
def nested_subject_matches_conditions?(subject_hash)
|
|
119
|
-
parent,
|
|
123
|
+
parent, _child = subject_hash.first
|
|
120
124
|
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
|
|
121
125
|
end
|
|
122
126
|
|
|
@@ -136,6 +140,7 @@ module CanCan
|
|
|
136
140
|
case value
|
|
137
141
|
when Hash then hash_condition_match?(attribute, value)
|
|
138
142
|
when String then attribute == value
|
|
143
|
+
when Range then value.cover?(attribute)
|
|
139
144
|
when Enumerable then value.include?(attribute)
|
|
140
145
|
else attribute == value
|
|
141
146
|
end
|
data/lib/cancan/version.rb
CHANGED
data/lib/cancan.rb
CHANGED
|
@@ -9,7 +9,16 @@ require 'cancan/inherited_resource'
|
|
|
9
9
|
|
|
10
10
|
require 'cancan/model_adapters/abstract_adapter'
|
|
11
11
|
require 'cancan/model_adapters/default_adapter'
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
if defined? ActiveRecord
|
|
14
|
+
require 'cancan/model_adapters/active_record_adapter'
|
|
15
|
+
if ActiveRecord.respond_to?(:version) &&
|
|
16
|
+
ActiveRecord.version >= Gem::Version.new("4")
|
|
17
|
+
require 'cancan/model_adapters/active_record_4_adapter'
|
|
18
|
+
else
|
|
19
|
+
require 'cancan/model_adapters/active_record_3_adapter'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
14
23
|
require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)
|
|
15
24
|
require 'cancan/model_adapters/sequel_adapter' if defined? Sequel
|
data/spec/README.rdoc
CHANGED
|
@@ -6,7 +6,7 @@ To run the specs first run the +bundle+ command to install the necessary gems an
|
|
|
6
6
|
|
|
7
7
|
bundle
|
|
8
8
|
|
|
9
|
-
Then run the appraisal command to install all the
|
|
9
|
+
Then run the appraisal command to install all the necessary test sets:
|
|
10
10
|
|
|
11
11
|
appraisal install
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ You can then run all test sets:
|
|
|
16
16
|
|
|
17
17
|
Or individual ones:
|
|
18
18
|
|
|
19
|
-
appraisal
|
|
19
|
+
appraisal activerecord_3.2 rake
|
|
20
20
|
|
|
21
21
|
A list of the tests is in the +Appraisal+ file.
|
|
22
22
|
|