Skip to content

Instantly share code, notes, and snippets.

@edestecd
Forked from clyfe/cancan.rb
Last active December 30, 2015 05:09
Show Gist options
  • Save edestecd/7781201 to your computer and use it in GitHub Desktop.
Save edestecd/7781201 to your computer and use it in GitHub Desktop.
# Setup
# =====
#
# Compatable with:
# cancan 1.6.10
# squeel 1.0.18
#
# 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)
merge_joins(base[name], nested) unless nested.empty?
else
base[name] = nested
end
end
end
end
end
class ControllerResource # :nodoc:
protected
def assign_attributes(resource)
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
initial_attributes.each do |attr_name, value|
unless squeel_attr_name?(attr_name) # don't set if squeel used, just authorize (hopefully)
resource.send("#{attr_name}=", value)
end
end
resource
end
def initial_attributes
current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value|
resource_params && resource_params.include?(squeel_attr_name(key))
end
end
def squeel_attr_name(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
end
def squeel_attr_name?(name)
case name
when Symbol then false
when Squeel::Nodes::Join then true
when Squeel::Nodes::Predicate then true
else raise name
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.is_a?(String) && value.kind_of?(Enumerable)
value.include? attribute
else
attribute == value
end
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment