Created
November 8, 2012 16:11
-
-
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
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
# 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