Skip to content

Instantly share code, notes, and snippets.

@clyfe
Last active September 29, 2015 01:07
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save clyfe/1523940 to your computer and use it in GitHub Desktop.
Save clyfe/1523940 to your computer and use it in GitHub Desktop.
Monkey patch for CanCan 1.6.7, replaces MetaWhere with Squeel and more
# Setup
# =====
#
# Put this gist in Rails.root/config/initializers/cancan.rb
# Add Squeel to Gemfile, see https://github.com/ernie/squeel
#
# gem "squeel", "~> 0.9.3"
#
# Load Squeel hash and symbol extensions in squeel config initializer
#
# Squeel.configure do |config|
# config.load_core_extensions :hash, :symbol
# end
#
# then you can write
#
# can :manage, User, :permissions.outer => {:type.matches => 'Manage%'}}
#
# This should offer all the old MetaWhere capabilities,
# and extra, also allows outer joins
#
# you might also be interested in https://gist.github.com/1012332
# if you use MetaWhere
# 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
@vddgil
Copy link

vddgil commented Sep 25, 2013

I found the solution, I tried a different syntax:

:key.not_in(["invoice.created", "credit_note.created", "estimate.created"]) & :parameters.does_not_match("%:private: true%")'

@clyfe
Copy link
Author

clyfe commented Oct 2, 2013

I think this might had worked as a hash based solution, since the default semantics of juxtaposition is "AND"

{
  :key.not_in => ["invoice.created", "credit_note.created", "estimate.created"],
  :parameters.does_not_match => ":private: true"
}

@edestecd
Copy link

edestecd commented Dec 4, 2013

Forked and updated for cancan 1.6.10.
It was throwing exceptions since the patch:
fix namespace controllers not loading params (thanks andhapp) - issues #670, #664

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment