-
-
Save clyfe/1523940 to your computer and use it in GitHub Desktop.
| # 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 |
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
I'm interested in Cancan 2.0 compatibility too. I'll test and report.