class ActiveRecord::Relation::WhereClause

Attributes

Public Class Methods

empty() click to toggle source
# File lib/where-or.rb, line 200
def self.empty
  new([], [])
end
new(predicates, binds) click to toggle source
# File lib/where-or.rb, line 116
def initialize(predicates, binds)
  @predicates = predicates
  @binds = binds
end

Public Instance Methods

+(other) click to toggle source
# File lib/where-or.rb, line 121
def +(other)
  ActiveRecord::Relation::WhereClause.new(
    predicates + other.predicates,
    binds + other.binds,
  )
end
-(other) click to toggle source

monkey patching around the fact that the rails 4.2 implementation is an array of things, all 'and'd together but the rails 5 implemention that they backported replaces that array with ActiveRecord::Relation::WhereClause that contains AND's and OR's … on testing, I discover it mostly works except when you attempt to use the preloader, which this hack here fixes.

# File lib/where-or.rb, line 132
def -(other)
  raise "where-or internal error: expect only empty array, not #{other.inspect}" unless other.empty? || (other.size == 1 && other.first.blank?)
  [self]
end
==(other) click to toggle source
# File lib/where-or.rb, line 190
def ==(other)
  other.is_a?(ActiveRecord::Relation::WhereClause) &&
    predicates == other.predicates &&
    binds == other.binds
end
ast() click to toggle source
# File lib/where-or.rb, line 186
def ast
  Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
end
except(*columns) click to toggle source
# File lib/where-or.rb, line 144
def except(*columns)
  ActiveRecord::Relation::WhereClause.new(
    predicates_except(columns),
    binds_except(columns),
  )
end
invert() click to toggle source
# File lib/where-or.rb, line 196
def invert
  ActiveRecord::Relation::WhereClause.new(inverted_predicates, binds)
end
merge(other) click to toggle source
# File lib/where-or.rb, line 137
def merge(other)
  ActiveRecord::Relation::WhereClause.new(
    predicates_unreferenced_by(other) + other.predicates,
    non_conflicting_binds(other) + other.binds,
  )
end
or(other) click to toggle source
# File lib/where-or.rb, line 151
def or(other)
  if empty?
    other
  elsif other.empty?
    self
  else
    ActiveRecord::Relation::WhereClause.new(
      [ast.or(other.ast)],
      binds + other.binds
    )
  end
end
to_h(table_name = nil) click to toggle source
# File lib/where-or.rb, line 164
def to_h(table_name = nil)
  equalities = predicates.grep(Arel::Nodes::Equality)
  if table_name
    equalities = equalities.select do |node|
      node.left.relation.name == table_name
    end
  end

  binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h

  equalities.map { |node|
    name = node.left.name
    [name, binds.fetch(name.to_s) {
      case node.right
      when Array then node.right.map(&:val)
      when Arel::Nodes::Casted, Arel::Nodes::Quoted
        node.right.val
      end
    }]
  }.to_h
end

Protected Instance Methods

referenced_columns() click to toggle source
# File lib/where-or.rb, line 206
def referenced_columns
  @referenced_columns ||= begin
                            equality_nodes = predicates.select { |n| equality_node?(n) }
                            Set.new(equality_nodes, &:left)
                          end
end

Private Instance Methods

binds_except(columns) click to toggle source
# File lib/where-or.rb, line 260
def binds_except(columns)
  binds.reject do |attr|
    columns.include?(attr.name)
  end
end
equality_node?(node) click to toggle source
# File lib/where-or.rb, line 221
def equality_node?(node)
  node.respond_to?(:operator) && node.operator == :==
end
invert_predicate(node) click to toggle source
# File lib/where-or.rb, line 235
def invert_predicate(node)
  case node
  when NilClass
    raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
  when Arel::Nodes::In
    Arel::Nodes::NotIn.new(node.left, node.right)
  when Arel::Nodes::Equality
    Arel::Nodes::NotEqual.new(node.left, node.right)
  when String
    Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
  else
    Arel::Nodes::Not.new(node)
  end
end
inverted_predicates() click to toggle source
# File lib/where-or.rb, line 231
def inverted_predicates
  predicates.map { |node| invert_predicate(node) }
end
non_conflicting_binds(other) click to toggle source
# File lib/where-or.rb, line 225
def non_conflicting_binds(other)
  conflicts = referenced_columns & other.referenced_columns
  conflicts.map! { |node| node.name.to_s }
  binds.reject { |attr| conflicts.include?(attr.name) }
end
non_empty_predicates() click to toggle source
# File lib/where-or.rb, line 276
def non_empty_predicates
  predicates - ['']
end
predicates_except(columns) click to toggle source
# File lib/where-or.rb, line 250
def predicates_except(columns)
  predicates.reject do |node|
    case node
    when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
      subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
      columns.include?(subrelation.name.to_s)
    end
  end
end
predicates_unreferenced_by(other) click to toggle source
# File lib/where-or.rb, line 215
def predicates_unreferenced_by(other)
  predicates.reject do |n|
    equality_node?(n) && other.referenced_columns.include?(n.left)
  end
end
predicates_with_wrapped_sql_literals() click to toggle source
# File lib/where-or.rb, line 266
def predicates_with_wrapped_sql_literals
  non_empty_predicates.map do |node|
    if Arel::Nodes::Equality === node
      node
    else
      wrap_sql_literal(node)
    end
  end
end
wrap_sql_literal(node) click to toggle source
# File lib/where-or.rb, line 280
def wrap_sql_literal(node)
  if ::String === node
    node = Arel.sql(node)
  end
  Arel::Nodes::Grouping.new(node)
end