Last active
September 29, 2015 01:07
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
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%")'
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"
}
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
Hello clyfe, i'm using your Gist to integrate squeel DSL in cancan abilities and it's pretty great.
But i can't figure out how to make a more complex condition like this one:
I keet getting a syntax error.
Can you help me,
Thanks in advance !