Skip to content

Instantly share code, notes, and snippets.

@pcreux
Created July 23, 2013 17:25
Show Gist options
  • Save pcreux/6064275 to your computer and use it in GitHub Desktop.
Save pcreux/6064275 to your computer and use it in GitHub Desktop.
Monkey patch Cancan to use subqueries. Work around for complex conditions with deep joins.
require 'cancan/model_adapters/active_record_adapter'
class CanCan::ModelAdapters::ActiveRecordAdapter
# From CanCan
def database_records
if override_scope
@model_class.scoped.merge(override_scope)
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
mergeable_conditions = @rules.select {|rule| rule.unmergeable? }.blank?
if mergeable_conditions
# TODO Only use nested queries when necessary
# Let's use nested queries
BuildNestedRelation.call(@model_class, @rules)
else
@model_class.where(*(@rules.map(&:conditions))).joins(joins)
end
else
@model_class.scoped(:conditions => conditions, :joins => joins)
end
end
class BuildNestedRelation
def self.call(*args)
new(*args).call
end
def initialize(model_class, rules)
self.model_class = model_class
self.rules = rules
end
def call
model_class.where(query)
end
private
# @return [String] like:
# id IN ( SUBQUERY_FOR_RULE_1 ) OR id IN ( SUBQUERY_FOR_RULE_2 ) ...
#
def query
rules.map do |rule|
"#{select_id} IN (
#{subquery(rule).to_sql}
)"
end.join("\nOR\n")
end
def select_id
%|"#{model_class.table_name}"."id"|
end
# @return [ActiveRecord::Relation] a subquery for the rule passed in
def subquery(rule)
model_class.
select(select_id).
joins(joins(rule)).
where(conditions(rule)).
reorder(nil). # work around ambiguous column error
limit(nil). # don't take limit into account
offset(nil) # don't take offset into account
end
# @return conditions statement for the rule passed in
def conditions(rule)
cancan_adapter(rule).conditions
end
# @return joins statement for the rule passed in
def joins(rule)
cancan_adapter(rule).joins
end
def cancan_adapter(rule)
CanCan::ModelAdapters::ActiveRecordAdapter.new(model_class, [rule])
end
attr_accessor :model_class, :rules
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment