public
Last active

Monkey patch for CanCan 1.6.7, replaces MetaWhere with Squeel and more

  • Download Gist
cancan.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
# 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'm interested in Cancan 2.0 compatibility too. I'll test and report.

The main difference with CanCan 2.0 would be made by Resource Attributes, meaning the queries should take them into account I belive. Otherwise things should be similar.

For belongs_to :custom_name, class_name: ClassName I had to make some changes (notably get tableized_conditions back into the mix) - see https://gist.github.com/2157530

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:

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

I keet getting a syntax error.

Can you help me,

Thanks in advance !

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

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.