cancancan 1.13.1 → 3.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 +5 -5
- data/cancancan.gemspec +18 -18
- data/init.rb +2 -0
- data/lib/cancan.rb +9 -11
- data/lib/cancan/ability.rb +93 -194
- data/lib/cancan/ability/actions.rb +93 -0
- data/lib/cancan/ability/rules.rb +93 -0
- data/lib/cancan/ability/strong_parameter_support.rb +41 -0
- data/lib/cancan/conditions_matcher.rb +106 -0
- data/lib/cancan/controller_additions.rb +38 -41
- data/lib/cancan/controller_resource.rb +52 -211
- data/lib/cancan/controller_resource_builder.rb +26 -0
- data/lib/cancan/controller_resource_finder.rb +42 -0
- data/lib/cancan/controller_resource_loader.rb +120 -0
- data/lib/cancan/controller_resource_name_finder.rb +23 -0
- data/lib/cancan/controller_resource_sanitizer.rb +32 -0
- data/lib/cancan/exceptions.rb +17 -5
- data/lib/cancan/matchers.rb +12 -3
- data/lib/cancan/model_adapters/abstract_adapter.rb +10 -8
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +39 -13
- data/lib/cancan/model_adapters/active_record_5_adapter.rb +68 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +77 -82
- data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
- data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
- data/lib/cancan/model_adapters/default_adapter.rb +2 -0
- data/lib/cancan/model_additions.rb +2 -1
- data/lib/cancan/parameter_validators.rb +9 -0
- data/lib/cancan/relevant.rb +29 -0
- data/lib/cancan/rule.rb +76 -105
- data/lib/cancan/rules_compressor.rb +23 -0
- data/lib/cancan/unauthorized_message_resolver.rb +24 -0
- data/lib/cancan/version.rb +3 -1
- data/lib/cancancan.rb +2 -0
- data/lib/generators/cancan/ability/ability_generator.rb +4 -2
- data/lib/generators/cancan/ability/templates/ability.rb +2 -0
- metadata +66 -56
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.travis.yml +0 -28
- data/Appraisals +0 -81
- data/CHANGELOG.rdoc +0 -518
- data/CONTRIBUTING.md +0 -23
- data/Gemfile +0 -3
- data/LICENSE +0 -22
- data/README.md +0 -214
- data/Rakefile +0 -9
- data/gemfiles/activerecord_3.2.gemfile +0 -16
- data/gemfiles/activerecord_4.0.gemfile +0 -17
- data/gemfiles/activerecord_4.1.gemfile +0 -17
- data/gemfiles/activerecord_4.2.gemfile +0 -18
- data/gemfiles/mongoid_2.x.gemfile +0 -16
- data/gemfiles/sequel_3.x.gemfile +0 -16
- data/lib/cancan/inherited_resource.rb +0 -20
- data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
- data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
- data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
- data/spec/README.rdoc +0 -27
- data/spec/cancan/ability_spec.rb +0 -521
- data/spec/cancan/controller_additions_spec.rb +0 -141
- data/spec/cancan/controller_resource_spec.rb +0 -632
- data/spec/cancan/exceptions_spec.rb +0 -58
- data/spec/cancan/inherited_resource_spec.rb +0 -71
- data/spec/cancan/matchers_spec.rb +0 -29
- data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -85
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -384
- data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
- data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
- data/spec/cancan/rule_spec.rb +0 -52
- data/spec/matchers.rb +0 -13
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -27
- data/spec/support/ability.rb +0 -7
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module ControllerResourceNameFinder
|
5
|
+
protected
|
6
|
+
|
7
|
+
def name_from_controller
|
8
|
+
@params[:controller].split('/').last.singularize
|
9
|
+
end
|
10
|
+
|
11
|
+
def namespaced_name
|
12
|
+
[namespace, name].join('/').singularize.camelize.safe_constantize || name
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
@name || name_from_controller
|
17
|
+
end
|
18
|
+
|
19
|
+
def namespace
|
20
|
+
@params[:controller].split('/')[0..-2]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module ControllerResourceSanitizer
|
5
|
+
protected
|
6
|
+
|
7
|
+
def sanitize_parameters
|
8
|
+
case params_method
|
9
|
+
when Symbol
|
10
|
+
@controller.send(params_method)
|
11
|
+
when String
|
12
|
+
@controller.instance_eval(params_method)
|
13
|
+
when Proc
|
14
|
+
params_method.call(@controller)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def params_methods
|
19
|
+
methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
|
20
|
+
methods.unshift(@options[:param_method]) if @options[:param_method].present?
|
21
|
+
methods
|
22
|
+
end
|
23
|
+
|
24
|
+
def params_method
|
25
|
+
params_methods.each do |method|
|
26
|
+
return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) ||
|
27
|
+
method.is_a?(String) || method.is_a?(Proc)
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/cancan/exceptions.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CanCan
|
2
4
|
# A general CanCan exception
|
3
5
|
class Error < StandardError; end
|
@@ -8,9 +10,18 @@ module CanCan
|
|
8
10
|
# Raised when removed code is called, an alternative solution is provided in message.
|
9
11
|
class ImplementationRemoved < Error; end
|
10
12
|
|
11
|
-
# Raised when using check_authorization without calling
|
13
|
+
# Raised when using check_authorization without calling authorize!
|
12
14
|
class AuthorizationNotPerformed < Error; end
|
13
15
|
|
16
|
+
# Raised when a rule is created with both a block and a hash of conditions
|
17
|
+
class BlockAndConditionsError < Error; end
|
18
|
+
|
19
|
+
# Raised when an unexpected argument is passed as an attribute
|
20
|
+
class AttributeArgumentError < Error; end
|
21
|
+
|
22
|
+
# Raised when using a wrong association name
|
23
|
+
class WrongAssociationName < Error; end
|
24
|
+
|
14
25
|
# This error is raised when a user isn't allowed to access a given controller action.
|
15
26
|
# This usually happens within a call to ControllerAdditions#authorize! but can be
|
16
27
|
# raised manually.
|
@@ -30,17 +41,18 @@ module CanCan
|
|
30
41
|
# exception.default_message = "Default error message"
|
31
42
|
# exception.message # => "Default error message"
|
32
43
|
#
|
33
|
-
# See ControllerAdditions#
|
44
|
+
# See ControllerAdditions#authorize! for more information on rescuing from this exception
|
34
45
|
# and customizing the message using I18n.
|
35
46
|
class AccessDenied < Error
|
36
|
-
attr_reader :action, :subject
|
47
|
+
attr_reader :action, :subject, :conditions
|
37
48
|
attr_writer :default_message
|
38
49
|
|
39
|
-
def initialize(message = nil, action = nil, subject = nil)
|
50
|
+
def initialize(message = nil, action = nil, subject = nil, conditions = nil)
|
40
51
|
@message = message
|
41
52
|
@action = action
|
42
53
|
@subject = subject
|
43
|
-
@
|
54
|
+
@conditions = conditions
|
55
|
+
@default_message = I18n.t(:"unauthorized.default", default: 'You are not authorized to access this page.')
|
44
56
|
end
|
45
57
|
|
46
58
|
def to_s
|
data/lib/cancan/matchers.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # RSpec 1 compatability
|
2
4
|
|
3
5
|
if rspec_module == 'RSpec'
|
@@ -9,16 +11,23 @@ end
|
|
9
11
|
|
10
12
|
Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
|
11
13
|
match do |ability|
|
12
|
-
|
14
|
+
actions = args.first
|
15
|
+
if actions.is_a? Array
|
16
|
+
break false if actions.empty?
|
17
|
+
|
18
|
+
actions.all? { |action| ability.can?(action, *args[1..-1]) }
|
19
|
+
else
|
20
|
+
ability.can?(*args)
|
21
|
+
end
|
13
22
|
end
|
14
23
|
|
15
24
|
# Check that RSpec is < 2.99
|
16
25
|
if !respond_to?(:failure_message) && respond_to?(:failure_message_for_should)
|
17
|
-
|
26
|
+
alias_method :failure_message, :failure_message_for_should
|
18
27
|
end
|
19
28
|
|
20
29
|
if !respond_to?(:failure_message_when_negated) && respond_to?(:failure_message_for_should_not)
|
21
|
-
|
30
|
+
alias_method :failure_message_when_negated, :failure_message_for_should_not
|
22
31
|
end
|
23
32
|
|
24
33
|
failure_message do
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CanCan
|
2
4
|
module ModelAdapters
|
3
5
|
class AbstractAdapter
|
@@ -11,7 +13,7 @@ module CanCan
|
|
11
13
|
end
|
12
14
|
|
13
15
|
# Used to determine if the given adapter should be used for the passed in class.
|
14
|
-
def self.for_class?(
|
16
|
+
def self.for_class?(_member_class)
|
15
17
|
false # override in subclass
|
16
18
|
end
|
17
19
|
|
@@ -22,24 +24,24 @@ module CanCan
|
|
22
24
|
|
23
25
|
# Used to determine if this model adapter will override the matching behavior for a hash of conditions.
|
24
26
|
# If this returns true then matches_conditions_hash? will be called. See Rule#matches_conditions_hash
|
25
|
-
def self.override_conditions_hash_matching?(
|
27
|
+
def self.override_conditions_hash_matching?(_subject, _conditions)
|
26
28
|
false
|
27
29
|
end
|
28
30
|
|
29
31
|
# Override if override_conditions_hash_matching? returns true
|
30
|
-
def self.matches_conditions_hash?(
|
31
|
-
raise NotImplemented,
|
32
|
+
def self.matches_conditions_hash?(_subject, _conditions)
|
33
|
+
raise NotImplemented, 'This model adapter does not support matching on a conditions hash.'
|
32
34
|
end
|
33
35
|
|
34
36
|
# Used to determine if this model adapter will override the matching behavior for a specific condition.
|
35
37
|
# If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
|
36
|
-
def self.override_condition_matching?(
|
38
|
+
def self.override_condition_matching?(_subject, _name, _value)
|
37
39
|
false
|
38
40
|
end
|
39
41
|
|
40
42
|
# Override if override_condition_matching? returns true
|
41
|
-
def self.matches_condition?(
|
42
|
-
raise NotImplemented,
|
43
|
+
def self.matches_condition?(_subject, _name, _value)
|
44
|
+
raise NotImplemented, 'This model adapter does not support matching on a specific condition.'
|
43
45
|
end
|
44
46
|
|
45
47
|
def initialize(model_class, rules)
|
@@ -49,7 +51,7 @@ module CanCan
|
|
49
51
|
|
50
52
|
def database_records
|
51
53
|
# This should be overridden in a subclass to return records which match @rules
|
52
|
-
raise NotImplemented,
|
54
|
+
raise NotImplemented, 'This model adapter does not support fetching records from the database.'
|
53
55
|
end
|
54
56
|
end
|
55
57
|
end
|
@@ -1,9 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CanCan
|
2
4
|
module ModelAdapters
|
3
|
-
class ActiveRecord4Adapter <
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
class ActiveRecord4Adapter < ActiveRecordAdapter
|
6
|
+
AbstractAdapter.inherited(self)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def for_class?(model_class)
|
10
|
+
version_lower?('5.0.0') && model_class <= ActiveRecord::Base
|
11
|
+
end
|
12
|
+
|
13
|
+
def override_condition_matching?(subject, name, _value)
|
14
|
+
subject.class.defined_enums.include?(name.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches_condition?(subject, name, value)
|
18
|
+
# Get the mapping from enum strings to values.
|
19
|
+
enum = subject.class.send(name.to_s.pluralize)
|
20
|
+
# Get the value of the attribute as an integer.
|
21
|
+
attribute = enum[subject.send(name)]
|
22
|
+
# Check to see if the value matches the condition.
|
23
|
+
if value.is_a?(Enumerable)
|
24
|
+
value.include? attribute
|
25
|
+
else
|
26
|
+
attribute == value
|
27
|
+
end
|
28
|
+
end
|
7
29
|
end
|
8
30
|
|
9
31
|
private
|
@@ -20,19 +42,23 @@ module CanCan
|
|
20
42
|
|
21
43
|
# Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
|
22
44
|
def sanitize_sql(conditions)
|
23
|
-
if
|
24
|
-
|
25
|
-
|
26
|
-
conditions = ActiveRecord::PredicateBuilder.resolve_column_aliases @model_class, conditions
|
27
|
-
conditions = @model_class.send(:expand_hash_conditions_for_aggregates, conditions)
|
28
|
-
|
29
|
-
ActiveRecord::PredicateBuilder.build_from_hash(@model_class, conditions, table).map { |b|
|
30
|
-
@model_class.send(:connection).visitor.compile b
|
31
|
-
}.join(' AND ')
|
45
|
+
if self.class.version_greater_or_equal?('4.2.0') && conditions.is_a?(Hash)
|
46
|
+
sanitize_sql_activerecord4(conditions)
|
32
47
|
else
|
33
48
|
@model_class.send(:sanitize_sql, conditions)
|
34
49
|
end
|
35
50
|
end
|
51
|
+
|
52
|
+
def sanitize_sql_activerecord4(conditions)
|
53
|
+
table = Arel::Table.new(@model_class.send(:table_name))
|
54
|
+
|
55
|
+
conditions = ActiveRecord::PredicateBuilder.resolve_column_aliases @model_class, conditions
|
56
|
+
conditions = @model_class.send(:expand_hash_conditions_for_aggregates, conditions)
|
57
|
+
|
58
|
+
ActiveRecord::PredicateBuilder.build_from_hash(@model_class, conditions, table).map do |b|
|
59
|
+
@model_class.send(:connection).visitor.compile b
|
60
|
+
end.join(' AND ')
|
61
|
+
end
|
36
62
|
end
|
37
63
|
end
|
38
64
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module ModelAdapters
|
5
|
+
class ActiveRecord5Adapter < ActiveRecord4Adapter
|
6
|
+
AbstractAdapter.inherited(self)
|
7
|
+
|
8
|
+
def self.for_class?(model_class)
|
9
|
+
version_greater_or_equal?('5.0.0') && model_class <= ActiveRecord::Base
|
10
|
+
end
|
11
|
+
|
12
|
+
# rails 5 is capable of using strings in enum
|
13
|
+
# but often people use symbols in rules
|
14
|
+
def self.matches_condition?(subject, name, value)
|
15
|
+
return super if Array.wrap(value).all? { |x| x.is_a? Integer }
|
16
|
+
|
17
|
+
attribute = subject.send(name)
|
18
|
+
raw_attribute = subject.class.send(name.to_s.pluralize)[attribute]
|
19
|
+
!(Array(value).map(&:to_s) & [attribute, raw_attribute]).empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_relation(*where_conditions)
|
25
|
+
if joins.present?
|
26
|
+
inner = @model_class.unscoped do
|
27
|
+
@model_class.left_joins(joins).where(*where_conditions)
|
28
|
+
end
|
29
|
+
@model_class.where(@model_class.primary_key => inner)
|
30
|
+
else
|
31
|
+
@model_class.where(*where_conditions)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
|
36
|
+
def sanitize_sql(conditions)
|
37
|
+
if conditions.is_a?(Hash)
|
38
|
+
sanitize_sql_activerecord5(conditions)
|
39
|
+
else
|
40
|
+
@model_class.send(:sanitize_sql, conditions)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sanitize_sql_activerecord5(conditions)
|
45
|
+
table = @model_class.send(:arel_table)
|
46
|
+
table_metadata = ActiveRecord::TableMetadata.new(@model_class, table)
|
47
|
+
predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
|
48
|
+
|
49
|
+
conditions = predicate_builder.resolve_column_aliases(conditions)
|
50
|
+
|
51
|
+
conditions.stringify_keys!
|
52
|
+
|
53
|
+
predicate_builder.build_from_hash(conditions).map { |b| visit_nodes(b) }.join(' AND ')
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_nodes(node)
|
57
|
+
# Rails 5.2 adds a BindParam node that prevents the visitor method from properly compiling the SQL query
|
58
|
+
if self.class.version_greater_or_equal?('5.2.0')
|
59
|
+
connection = @model_class.send(:connection)
|
60
|
+
collector = Arel::Collectors::SubstituteBinds.new(connection, Arel::Collectors::SQLString.new)
|
61
|
+
connection.visitor.accept(node, collector).value
|
62
|
+
else
|
63
|
+
@model_class.send(:connection).visitor.compile(node)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,6 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'conditions_extractor.rb'
|
4
|
+
require 'cancan/rules_compressor'
|
1
5
|
module CanCan
|
2
6
|
module ModelAdapters
|
3
|
-
|
7
|
+
class ActiveRecordAdapter < AbstractAdapter
|
8
|
+
def self.version_greater_or_equal?(version)
|
9
|
+
Gem::Version.new(ActiveRecord.version).release >= Gem::Version.new(version)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.version_lower?(version)
|
13
|
+
Gem::Version.new(ActiveRecord.version).release < Gem::Version.new(version)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(model_class, rules)
|
17
|
+
super
|
18
|
+
@compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
|
19
|
+
ConditionsNormalizer.normalize(model_class, @compressed_rules)
|
20
|
+
end
|
21
|
+
|
4
22
|
# Returns conditions intended to be used inside a database query. Normally you will not call this
|
5
23
|
# method directly, but instead go through ModelAdditions#accessible_by.
|
6
24
|
#
|
@@ -17,94 +35,91 @@ module CanCan
|
|
17
35
|
# query(:manage, User).conditions # => "not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))"
|
18
36
|
#
|
19
37
|
def conditions
|
20
|
-
|
38
|
+
conditions_extractor = ConditionsExtractor.new(@model_class)
|
39
|
+
if @compressed_rules.size == 1 && @compressed_rules.first.base_behavior
|
21
40
|
# Return the conditions directly if there's just one definition
|
22
|
-
|
41
|
+
conditions_extractor.tableize_conditions(@compressed_rules.first.conditions).dup
|
23
42
|
else
|
24
|
-
|
25
|
-
merge_conditions(sql, tableized_conditions(rule.conditions).dup, rule.base_behavior)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def tableized_conditions(conditions, model_class = @model_class)
|
31
|
-
return conditions unless conditions.kind_of? Hash
|
32
|
-
conditions.inject({}) do |result_hash, (name, value)|
|
33
|
-
if value.kind_of? Hash
|
34
|
-
value = value.dup
|
35
|
-
association_class = model_class.reflect_on_association(name).klass.name.constantize
|
36
|
-
nested = value.inject({}) do |nested,(k,v)|
|
37
|
-
if v.kind_of? Hash
|
38
|
-
value.delete(k)
|
39
|
-
nested[k] = v
|
40
|
-
else
|
41
|
-
result_hash[model_class.reflect_on_association(name).table_name.to_sym] = value
|
42
|
-
end
|
43
|
-
nested
|
44
|
-
end
|
45
|
-
result_hash.merge!(tableized_conditions(nested,association_class))
|
46
|
-
else
|
47
|
-
result_hash[name] = value
|
48
|
-
end
|
49
|
-
result_hash
|
43
|
+
extract_multiple_conditions(conditions_extractor, @compressed_rules)
|
50
44
|
end
|
51
45
|
end
|
52
46
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
joins_hash = {}
|
57
|
-
@rules.each do |rule|
|
58
|
-
merge_joins(joins_hash, rule.associations_hash)
|
47
|
+
def extract_multiple_conditions(conditions_extractor, rules)
|
48
|
+
rules.reverse.inject(false_sql) do |sql, rule|
|
49
|
+
merge_conditions(sql, conditions_extractor.tableize_conditions(rule.conditions).dup, rule.base_behavior)
|
59
50
|
end
|
60
|
-
clean_joins(joins_hash) unless joins_hash.empty?
|
61
51
|
end
|
62
52
|
|
63
53
|
def database_records
|
64
54
|
if override_scope
|
65
55
|
@model_class.where(nil).merge(override_scope)
|
66
56
|
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
|
67
|
-
|
68
|
-
build_relation(conditions)
|
69
|
-
else
|
70
|
-
build_relation(*(@rules.map(&:conditions)))
|
71
|
-
end
|
57
|
+
build_relation(conditions)
|
72
58
|
else
|
73
|
-
@model_class.all(:
|
59
|
+
@model_class.all(conditions: conditions, joins: joins)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the associations used in conditions for the :joins option of a search.
|
64
|
+
# See ModelAdditions#accessible_by
|
65
|
+
def joins
|
66
|
+
joins_hash = {}
|
67
|
+
@compressed_rules.reverse_each do |rule|
|
68
|
+
deep_merge(joins_hash, rule.associations_hash)
|
74
69
|
end
|
70
|
+
deep_clean(joins_hash) unless joins_hash.empty?
|
75
71
|
end
|
76
72
|
|
77
73
|
private
|
78
74
|
|
79
|
-
|
80
|
-
|
75
|
+
# Removes empty hashes and moves everything into arrays.
|
76
|
+
def deep_clean(joins_hash)
|
77
|
+
joins_hash.map { |name, nested| nested.empty? ? name : { name => deep_clean(nested) } }
|
81
78
|
end
|
82
79
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
if
|
87
|
-
|
80
|
+
# Takes two hashes and does a deep merge.
|
81
|
+
def deep_merge(base_hash, added_hash)
|
82
|
+
added_hash.each do |key, value|
|
83
|
+
if base_hash[key].is_a?(Hash)
|
84
|
+
deep_merge(base_hash[key], value) unless value.empty?
|
88
85
|
else
|
89
|
-
|
90
|
-
raise Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for #{rule.actions.first} #{rule.subjects.first} ability."
|
86
|
+
base_hash[key] = value
|
91
87
|
end
|
92
88
|
end
|
93
89
|
end
|
94
90
|
|
91
|
+
def override_scope
|
92
|
+
conditions = @compressed_rules.map(&:conditions).compact
|
93
|
+
return unless conditions.any? { |c| c.is_a?(ActiveRecord::Relation) }
|
94
|
+
return conditions.first if conditions.size == 1
|
95
|
+
|
96
|
+
raise_override_scope_error
|
97
|
+
end
|
98
|
+
|
99
|
+
def raise_override_scope_error
|
100
|
+
rule_found = @compressed_rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
|
101
|
+
raise Error,
|
102
|
+
'Unable to merge an Active Record scope with other conditions. '\
|
103
|
+
"Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
|
104
|
+
end
|
105
|
+
|
95
106
|
def merge_conditions(sql, conditions_hash, behavior)
|
96
107
|
if conditions_hash.blank?
|
97
108
|
behavior ? true_sql : false_sql
|
98
109
|
else
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
110
|
+
merge_non_empty_conditions(behavior, conditions_hash, sql)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def merge_non_empty_conditions(behavior, conditions_hash, sql)
|
115
|
+
conditions = sanitize_sql(conditions_hash)
|
116
|
+
case sql
|
117
|
+
when true_sql
|
118
|
+
behavior ? true_sql : "not (#{conditions})"
|
119
|
+
when false_sql
|
120
|
+
behavior ? conditions : false_sql
|
121
|
+
else
|
122
|
+
behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})"
|
108
123
|
end
|
109
124
|
end
|
110
125
|
|
@@ -119,30 +134,10 @@ module CanCan
|
|
119
134
|
def sanitize_sql(conditions)
|
120
135
|
@model_class.send(:sanitize_sql, conditions)
|
121
136
|
end
|
122
|
-
|
123
|
-
# Takes two hashes and does a deep merge.
|
124
|
-
def merge_joins(base, add)
|
125
|
-
add.each do |name, nested|
|
126
|
-
if base[name].is_a?(Hash)
|
127
|
-
merge_joins(base[name], nested) unless nested.empty?
|
128
|
-
else
|
129
|
-
base[name] = nested
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# Removes empty hashes and moves everything into arrays.
|
135
|
-
def clean_joins(joins_hash)
|
136
|
-
joins = []
|
137
|
-
joins_hash.each do |name, nested|
|
138
|
-
joins << (nested.empty? ? name : {name => clean_joins(nested)})
|
139
|
-
end
|
140
|
-
joins
|
141
|
-
end
|
142
137
|
end
|
143
138
|
end
|
144
139
|
end
|
145
140
|
|
146
|
-
|
147
|
-
include CanCan::ModelAdditions
|
141
|
+
ActiveSupport.on_load(:active_record) do
|
142
|
+
send :include, CanCan::ModelAdditions
|
148
143
|
end
|