Commits

heliostatic  committed 2e0c24f

testing new squeel fix

  • Participants
  • Parent commits 530d221

Comments (0)

Files changed (2)

File app/models/ability.rb

       can :read, :competitions, competitors: { id: user.id } # user is a competitor, problem: https://github.com/ryanb/cancan/issues/213
       can :read, :competitions, judges: { id: user.id } #user is a judge. same issue.
       can :read, :competitions, public: true # competition is public
-      # cannot :read, :competitions, ['deadline < ?', Date.today] do |c|
-      #   c.deadline < Date.today
-      # end
+      cannot :read, :competitions, Competition.expired do |c|
+        c.deadline < Date.today
+      end
       # TODO: add date/expiration based permissions that don't break with squeel and postgres and cancan 2.0
       can :vote, :competitions, judges: { id: user.id }
       can :access, :competitions, user_id: user.id # user is owner

File lib/cancan_squeel_fixes.rb

-# encoding: UTF-8
-
 require "cancan/model_adapters/active_record_adapter"
 
 class CanCan::Rule
 end
 
 # hack the option to have multiple abilities with scopes into cancan
-# only supports positive abilities right now if scopes are defined
 class CanCan::ModelAdapters::ActiveRecordAdapter
-
-  # disable the error when multiple conditions with scopes are used
-  def override_scope
-    return false
-  end
   
-  # use squeel's symbol-predicates to convert all joins into outer joins
-  # see https://github.com/ryanb/cancan/issues/374
-  def clean_joins(joins_hash)
-    joins = []
-    joins_hash.each do |name, nested|
-      name = name.outer if nested.is_a? Hash
-      joins << (nested.empty? ? name : {name => clean_joins(nested)})
-    end
-    joins
-  end
-  
-  # convert where-clauses from relations into sql strings which will be merged by the cancan  merge_conditions logic 
-  def tableized_conditions_with_relations(*args)
-    if args.first.kind_of?(ActiveRecord::Relation)
-      base_class = @model_class.unscoped
-      arel = args.first.arel
-      
-      arel.where_clauses.join(" AND ")
-    else
-      tableized_conditions_without_relations(*args)
-    end
-  end
-  alias_method_chain :tableized_conditions, :relations unless method_defined? :tableized_conditions_without_relations
-  
-  # workaround for https://github.com/ryanb/cancan/issues/646
-  def merge_conditions(sql, conditions_hash, behavior)
-    conditions_hash = Squeel::Nodes::Literal.new(conditions_hash) if conditions_hash.is_a? String
-    # Rails.logger.warn [sql, conditions_hash, behavior].inspect
-    if conditions_hash.blank?
-      Squeel::Nodes::Literal.new(behavior ? true_sql : false_sql)
-    else
-      # convert back to a string for the case statement
-      sql = sql.to_s if sql.is_a?(Squeel::Nodes::Literal)
-      case sql
-      when true_sql
-        behavior ? Squeel::Nodes::Literal.new(true_sql) : Squeel::Nodes::Not.new(conditions_hash)
-      when false_sql
-        behavior ? conditions_hash : Squeel::Nodes::Literal.new(false_sql)
-      else
-        sql = Squeel::Nodes::Literal.new(sql) if sql.is_a? String
-        behavior ? Squeel::Nodes::Or.new(conditions_hash,sql) : Squeel::Nodes::And.new([Squeel::Nodes::Not.new(conditions_hash),sql])
-      end
-    end
-  end
-  
-  # convert join-clauses from relations into sql strings which will be merged by the cancan merge_joins logic
-  def joins_with_relations
-    arr = joins_without_relations || []
-    arr.concat(@rules.map(&:conditions).select{|c| c.kind_of?(ActiveRecord::Relation)}.map do |rel|
-      arel = rel.arel
-      arel.ast.cores.each{|c| convert_join(c)}
-      arel.join_sql
-    end)
+  def database_records
+    base_query = @model_class.scoped
     
-    arr.delete_if{|e| e.blank?}
-    arr = nil if arr.empty?
-
-    arr 
-  end
-  alias_method_chain :joins, :relations unless method_defined? :joins_without_relations
-
-
-  private
-
-  # helper that traverses a arel AST to turn inner into outer joins
-  def convert_join(ast)
-    ast.froms = convert_join(ast.froms) if ast.is_a? Arel::Nodes::SelectCore
+    rule_query = @model_class.unscoped
     
-    if ast.is_a? Arel::Nodes::Join
-      ast.left = convert_join(ast.left)
-      ast.right = convert_join(ast.right)
+    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
     
-    return Arel::Nodes::OuterJoin.new(ast.left,ast.right,ast.constraint) if ast.is_a? Arel::Nodes::InnerJoin 
+    # 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
     
-    ast
+    base_query.merge(rule_query)
   end
   
 end