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.
Files changed (73) hide show
  1. checksums.yaml +5 -5
  2. data/cancancan.gemspec +18 -18
  3. data/init.rb +2 -0
  4. data/lib/cancan.rb +9 -11
  5. data/lib/cancan/ability.rb +93 -194
  6. data/lib/cancan/ability/actions.rb +93 -0
  7. data/lib/cancan/ability/rules.rb +93 -0
  8. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  9. data/lib/cancan/conditions_matcher.rb +106 -0
  10. data/lib/cancan/controller_additions.rb +38 -41
  11. data/lib/cancan/controller_resource.rb +52 -211
  12. data/lib/cancan/controller_resource_builder.rb +26 -0
  13. data/lib/cancan/controller_resource_finder.rb +42 -0
  14. data/lib/cancan/controller_resource_loader.rb +120 -0
  15. data/lib/cancan/controller_resource_name_finder.rb +23 -0
  16. data/lib/cancan/controller_resource_sanitizer.rb +32 -0
  17. data/lib/cancan/exceptions.rb +17 -5
  18. data/lib/cancan/matchers.rb +12 -3
  19. data/lib/cancan/model_adapters/abstract_adapter.rb +10 -8
  20. data/lib/cancan/model_adapters/active_record_4_adapter.rb +39 -13
  21. data/lib/cancan/model_adapters/active_record_5_adapter.rb +68 -0
  22. data/lib/cancan/model_adapters/active_record_adapter.rb +77 -82
  23. data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
  24. data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
  25. data/lib/cancan/model_adapters/default_adapter.rb +2 -0
  26. data/lib/cancan/model_additions.rb +2 -1
  27. data/lib/cancan/parameter_validators.rb +9 -0
  28. data/lib/cancan/relevant.rb +29 -0
  29. data/lib/cancan/rule.rb +76 -105
  30. data/lib/cancan/rules_compressor.rb +23 -0
  31. data/lib/cancan/unauthorized_message_resolver.rb +24 -0
  32. data/lib/cancan/version.rb +3 -1
  33. data/lib/cancancan.rb +2 -0
  34. data/lib/generators/cancan/ability/ability_generator.rb +4 -2
  35. data/lib/generators/cancan/ability/templates/ability.rb +2 -0
  36. metadata +66 -56
  37. data/.gitignore +0 -15
  38. data/.rspec +0 -1
  39. data/.travis.yml +0 -28
  40. data/Appraisals +0 -81
  41. data/CHANGELOG.rdoc +0 -518
  42. data/CONTRIBUTING.md +0 -23
  43. data/Gemfile +0 -3
  44. data/LICENSE +0 -22
  45. data/README.md +0 -214
  46. data/Rakefile +0 -9
  47. data/gemfiles/activerecord_3.2.gemfile +0 -16
  48. data/gemfiles/activerecord_4.0.gemfile +0 -17
  49. data/gemfiles/activerecord_4.1.gemfile +0 -17
  50. data/gemfiles/activerecord_4.2.gemfile +0 -18
  51. data/gemfiles/mongoid_2.x.gemfile +0 -16
  52. data/gemfiles/sequel_3.x.gemfile +0 -16
  53. data/lib/cancan/inherited_resource.rb +0 -20
  54. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
  55. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
  56. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  57. data/spec/README.rdoc +0 -27
  58. data/spec/cancan/ability_spec.rb +0 -521
  59. data/spec/cancan/controller_additions_spec.rb +0 -141
  60. data/spec/cancan/controller_resource_spec.rb +0 -632
  61. data/spec/cancan/exceptions_spec.rb +0 -58
  62. data/spec/cancan/inherited_resource_spec.rb +0 -71
  63. data/spec/cancan/matchers_spec.rb +0 -29
  64. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -85
  65. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -384
  66. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  67. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
  68. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
  69. data/spec/cancan/rule_spec.rb +0 -52
  70. data/spec/matchers.rb +0 -13
  71. data/spec/spec.opts +0 -2
  72. data/spec/spec_helper.rb +0 -27
  73. 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
@@ -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 authorized!
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#authorized! for more information on rescuing from this exception
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
- @default_message = I18n.t(:"unauthorized.default", :default => "You are not authorized to access this page.")
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
@@ -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
- ability.can?(*args)
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
- alias :failure_message :failure_message_for_should
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
- alias :failure_message_when_negated :failure_message_for_should_not
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?(member_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?(subject, conditions)
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?(subject, conditions)
31
- raise NotImplemented, "This model adapter does not support matching on a conditions hash."
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?(subject, name, value)
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?(subject, name, value)
42
- raise NotImplemented, "This model adapter does not support matching on a specific condition."
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, "This model adapter does not support fetching records from the database."
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 < AbstractAdapter
4
- include ActiveRecordAdapter
5
- def self.for_class?(model_class)
6
- model_class <= ActiveRecord::Base
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 ActiveRecord::VERSION::MINOR >= 2 && Hash === conditions
24
- table = Arel::Table.new(@model_class.send(:table_name))
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
- module ActiveRecordAdapter
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
- if @rules.size == 1 && @rules.first.base_behavior
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
- tableized_conditions(@rules.first.conditions).dup
41
+ conditions_extractor.tableize_conditions(@compressed_rules.first.conditions).dup
23
42
  else
24
- @rules.reverse.inject(false_sql) do |sql, rule|
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
- # Returns the associations used in conditions for the :joins option of a search.
54
- # See ModelAdditions#accessible_by
55
- def joins
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
- if mergeable_conditions?
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(:conditions => conditions, :joins => joins)
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
- def mergeable_conditions?
80
- @rules.find {|rule| rule.unmergeable? }.blank?
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
- def override_scope
84
- conditions = @rules.map(&:conditions).compact
85
- if defined?(ActiveRecord::Relation) && conditions.any? { |c| c.kind_of?(ActiveRecord::Relation) }
86
- if conditions.size == 1
87
- conditions.first
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
- rule = @rules.detect { |rule| rule.conditions.kind_of?(ActiveRecord::Relation) }
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
- conditions = sanitize_sql(conditions_hash)
100
- case sql
101
- when true_sql
102
- behavior ? true_sql : "not (#{conditions})"
103
- when false_sql
104
- behavior ? conditions : false_sql
105
- else
106
- behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})"
107
- end
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
- ActiveRecord::Base.class_eval do
147
- include CanCan::ModelAdditions
141
+ ActiveSupport.on_load(:active_record) do
142
+ send :include, CanCan::ModelAdditions
148
143
  end