Created
July 23, 2013 17:25
-
-
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.
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
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