Skip to content

Instantly share code, notes, and snippets.

@the8472
Created November 8, 2012 16:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save the8472/4039777 to your computer and use it in GitHub Desktop.
Save the8472/4039777 to your computer and use it in GitHub Desktop.
outerjoin fixes + rails 3.2.8 compat + scope merging for cancan, using squeel
# encoding: UTF-8
require "cancan/model_adapters/active_record_adapter"
class CanCan::Rule
def unmergeable?
false
end
end
# hack the option to have multiple abilities with scopes into cancan
class CanCan::ModelAdapters::ActiveRecordAdapter
def database_records
base_query = @model_class.scoped
rule_query = @model_class.unscoped
relevant_rules = @rules.reverse
last_unconditional_rule = relevant_rules.rindex{|r| r.conditions.blank?}
relevant_rules = relevant_rules[last_unconditional_rule..-1] if last_unconditional_rule
# first we infer the desired joins from the conditions
# squeel needs them to create the appropriate table aliases
rule_query = rule_query.joins do
relevant_rules.map(&:conditions).map do |c|
if c.is_a? ActiveRecord::Relation
c.joins_values
elsif c.is_a? Hash
# deep-convert a conditions hash into squeel keypaths
# this is important so that the wheres know about aliased tables
arr = []
walk = Proc.new do |ancestor,target|
case target
when Hash # {:a => {:b => {:column => "value"}}} becomes <dsl>.a.outer.b.outer
target.each do |k,v|
keypath = (Squeel::Nodes::KeyPath === ancestor ? ancestor.dup : ancestor).__send__(k).outer # need to clone key paths to avoid mutation
arr << keypath if v.is_a?(Hash) || (v.is_a?(Array) && v.any?{|e| e.is_a?(Hash)}) # skip leaf nodes, they're not table references
walk[keypath,v]
end
when Array # deal with {:a => [:b, {:c => :d}]} constructs
target.each{|e| walk[ancestor,e]}
end
end
walk[self,c]
arr
end
end.flatten.compact
end
# now the where-conditions
# we need to do this inside a single dsl call since multiple where calls are forced to AND conditions
rule_query = rule_query.where do
combined = relevant_rules.reduce(false) do |prev,rule|
b = rule.base_behavior
c = rule.conditions
# array inside .where() = OR
# array inside ActiveRecord::Relation = AND
# -> need to convert
c = Squeel::Nodes::And.new(c.where_values) if c.is_a? ActiveRecord::Relation
if c.blank?
b
else
case prev
when true
b ? true : Squeel::Nodes::Not.new(c)
when false
b ? c : false
else
b ? Squeel::Nodes::Or.new(prev,c) : Squeel::Nodes::And.new([Squeel::Nodes::Not.new(c),prev])
end
end
end
# convert primitive values to something squeel understands
combined = `0 = 1` if combined == false
combined = nil if combined == true
combined
end
base_query.merge(rule_query)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment