# https://gist.github.com/1523940 class String include Squeel::Nodes::PredicateOperators end module Squeel module Visitors class PredicateVisitor < Visitor def visit_String(o, parent) Arel::Nodes::SqlLiteral.new(o) end end end end module CanCan module ModelAdapters class ActiveRecordAdapter < AbstractAdapter def self.override_condition_matching?(subject, name, value) name.kind_of?(Squeel::Nodes::Predicate) if defined? Squeel end def self.matches_condition?(subject, name, value) subject_value = subject.send(name.expr) method_name = name.method_name.to_s if method_name.ends_with? "_any" value.any? { |v| squeel_match? subject_value, method_name.sub("_any", ""), v } elsif method_name.ends_with? "_all" value.all? { |v| squeel_match? subject_value, method_name.sub("_all", ""), v } else squeel_match? subject_value, name.method_name, value end end def self.squeel_match?(subject_value, method, value) case method.to_sym when :eq then subject_value == value when :not_eq then subject_value != value when :in then value.include?(subject_value) when :not_in then !value.include?(subject_value) when :lt then subject_value < value when :lteq then subject_value <= value when :gt then subject_value > value when :gteq then subject_value >= value when :matches then subject_value =~ Regexp.new("^" + Regexp.escape(value).gsub("%", ".*") + "$", true) when :does_not_match then !squeel_match?(subject_value, :matches, value) else raise NotImplemented, "The #{method} Squeel condition is not supported." end end # mostly let Squeel do the job in building the query def conditions if @rules.size == 1 && @rules.first.base_behavior # Return the conditions directly if there's just one definition @rules.first.conditions.dup else @rules.reverse.inject(false_sql) do |accumulator, rule| conditions = rule.conditions.dup if conditions.blank? rule.base_behavior ? (accumulator | true_sql) : (accumulator & false_sql) else rule.base_behavior ? (accumulator | conditions) : (accumulator & -conditions) end end end end private # override to fix overwrites # do not write existing hashes using empty hashes def merge_joins(base, add) add.each do |name, nested| if base[name].is_a?(Hash) && nested.present? merge_joins(base[name], nested) elsif !base[name].is_a?(Hash) || nested.present? base[name] = nested end end end end end class Rule # allow Squeel def matches_conditions_hash?(subject, conditions = @conditions) return true if conditions.empty? conditions.all? do |name, value| if model_adapter(subject).override_condition_matching? subject, name, value model_adapter(subject).matches_condition? subject, name, value else method_name = case name when Symbol then name when Squeel::Nodes::Join then name._name when Squeel::Nodes::Predicate then name.expr else raise name end attribute = subject.send(method_name) if value.kind_of?(Hash) case attribute when Array, ActiveRecord::Associations::CollectionProxy attribute.any? { |element| matches_conditions_hash? element, value } else !attribute.nil? && matches_conditions_hash?(attribute, value) end elsif value.kind_of?(Array) || value.kind_of?(Range) value.include? attribute else attribute == value end end end end end end