/
rule.rb
140 lines (115 loc) · 4.22 KB
/
rule.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# frozen_string_literal: true
require_relative 'conditions_matcher.rb'
require_relative 'class_matcher.rb'
require_relative 'relevant.rb'
module CanCan
# This class is used internally and should only be called through Ability.
# it holds the information about a "can" call made on Ability and provides
# helpful methods to determine permission checking and conditions hash generation.
class Rule # :nodoc:
include ConditionsMatcher
include Relevant
include ParameterValidators
attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes, :block
attr_writer :expanded_actions, :conditions
# The first argument when initializing is the base_behavior which is a true/false
# value. True for "can" and false for "cannot". The next two arguments are the action
# and subject respectively (such as :read, @project). The third argument is a hash
# of conditions and the last one is the block passed to the "can" call.
def initialize(base_behavior, action, subject, *extra_args, &block)
# for backwards compatibility, attributes are an optional parameter. Check if
# attributes were passed or are actually conditions
attributes, extra_args = parse_attributes_from_extra_args(extra_args)
condition_and_block_check(extra_args, block, action, subject)
@match_all = action.nil? && subject.nil?
raise Error, "Subject is required for #{action}" if action && subject.nil?
@base_behavior = base_behavior
@actions = wrap(action)
@subjects = wrap(subject)
@attributes = wrap(attributes)
@conditions = extra_args || {}
@block = block
end
def inspect
repr = "#<#{self.class.name}"
repr += "#{@base_behavior ? 'can' : 'cannot'} #{@actions.inspect}, #{@subjects.inspect}, #{@attributes.inspect}"
if with_scope?
repr += ", #{@conditions.where_values_hash}"
elsif [Hash, String].include?(@conditions.class)
repr += ", #{@conditions.inspect}"
end
repr + '>'
end
def can_rule?
base_behavior
end
def cannot_catch_all?
!can_rule? && catch_all?
end
def catch_all?
(with_scope? && @conditions.where_values_hash.empty?) ||
(!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions))
end
def only_block?
conditions_empty? && @block
end
def only_raw_sql?
@block.nil? && !conditions_empty? && !@conditions.is_a?(Hash)
end
def with_scope?
defined?(ActiveRecord) && @conditions.is_a?(ActiveRecord::Relation)
end
def associations_hash(conditions = @conditions)
hash = {}
if conditions.is_a? Hash
conditions.map do |name, value|
hash[name] = associations_hash(value) if value.is_a? Hash
end
end
hash
end
def attributes_from_conditions
attributes = {}
if @conditions.is_a? Hash
@conditions.each do |key, value|
attributes[key] = value unless [Array, Range, Hash].include? value.class
end
end
attributes
end
def matches_attributes?(attribute)
return true if @attributes.empty?
return @base_behavior if attribute.nil?
@attributes.include?(attribute.to_sym)
end
private
def matches_action?(action)
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
end
def matches_subject?(subject)
@subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
end
def matches_subject_class?(subject)
SubjectClassMatcher.matches_subject_class?(@subjects, subject)
end
def parse_attributes_from_extra_args(args)
attributes = args.shift if valid_attribute_param?(args.first)
extra_args = args.shift
[attributes, extra_args]
end
def condition_and_block_check(conditions, block, action, subject)
return unless conditions.is_a?(Hash) && block
raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. ' \
"Check \":#{action} #{subject}\" ability."
end
def wrap(object)
if object.nil?
[]
elsif object.respond_to?(:to_ary)
object.to_ary || [object]
else
[object]
end
end
end
end