Module: Squeel::Visitors::PredicateVisitation

Included in:
OrderVisitor, PredicateVisitor, SelectVisitor
Defined in:
lib/squeel/visitors/predicate_visitation.rb

Constant Summary collapse

EXPAND_BELONGS_TO_METHODS =
[:eq, :not_eq]
TRUE_SQL =
Arel.sql('1=1').freeze
FALSE_SQL =
Arel.sql('1=0').freeze

Instance Method Summary collapse

Instance Method Details

#arel_predicate_for(attribute, value, parent) ⇒ Arel::Nodes::Node (private)

Determine whether to use IN or equality testing for a predicate, based on its value class, then return the appropriate predicate.

Parameters:

  • attribute

    The Arel attribute (or function/operation) the predicate will be created for

  • value

    The value to be compared against

Returns:

  • (Arel::Nodes::Node)

    An Arel predicate node


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/squeel/visitors/predicate_visitation.rb', line 77

def arel_predicate_for(attribute, value, parent)
  if ActiveRecord::Relation === value && value.select_values.empty?
    value = visit(value.select(value.klass.arel_table[value.klass.primary_key]), parent)
  else
    value = can_visit?(value) ? visit(value, parent) : value
  end

  case value
  when Array
    attribute_in_array(attribute, value)
  when Range, Arel::SelectManager
    attribute.in(value)
  else
    attribute.eq(value)
  end
end

#attribute_in_array(attribute, array) ⇒ Object (private)


94
95
96
97
98
99
100
101
102
103
# File 'lib/squeel/visitors/predicate_visitation.rb', line 94

def attribute_in_array(attribute, array)
  if array.empty?
    FALSE_SQL
  elsif array.include? nil
    array = array.compact
    array.empty? ? attribute.eq(nil) : attribute.in(array).or(attribute.eq nil)
  else
    attribute.in array
  end
end

#attribute_not_in_array(attribute, array) ⇒ Object (private)


105
106
107
108
109
110
111
112
113
114
# File 'lib/squeel/visitors/predicate_visitation.rb', line 105

def attribute_not_in_array(attribute, array)
  if array.empty?
    TRUE_SQL
  elsif array.include? nil
    array = array.compact
    array.empty? ? attribute.not_eq(nil) : attribute.not_in(array).and(attribute.not_eq nil)
  else
    attribute.not_in array
  end
end

#quote_for_node(node, v) ⇒ Object (private)

Certain nodes require us to do the quoting before the Arel visitor gets a chance to try, because we want to avoid having our values quoted as a type of the last visited column. Otherwise, we can end up with annoyances like having “joe” quoted to 0, if the last visited column was of an integer type.

Parameters:

  • node

    The node we (might) be quoting for

  • v

    The value to (possibly) quote


124
125
126
127
128
129
130
131
132
133
# File 'lib/squeel/visitors/predicate_visitation.rb', line 124

def quote_for_node(node, v)
  case node
  when Nodes::Function, Nodes::Literal
    quote(v)
  when Nodes::Predicate
    quote_for_node(node.expr, v)
  else
    v
  end
end

#visit_Squeel_Nodes_Predicate(o, parent) ⇒ Object (private)

Visit a Squeel predicate, converting it into an Arel predicate

Parameters:

  • o (Nodes::Predicate)

    The predicate to visit

  • parent

    The parent object in the context

Returns:

  • An Arel predicate node (Arel::Nodes::Equality, Arel::Nodes::Matches, etc)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/squeel/visitors/predicate_visitation.rb', line 29

def visit_Squeel_Nodes_Predicate(o, parent)
  value = o.value

  # Short-circuit for stuff like `where{ author.eq User.first }`
  # This filthy hack emulates similar behavior in AR PredicateBuilder
  if ActiveRecord::Base === value &&
    EXPAND_BELONGS_TO_METHODS.include?(o.method_name) &&
    association = classify(parent).reflect_on_association(
      symbolify(o.expr)
    )
    return expand_belongs_to(o, parent, association)
  end

  case value
  when Nodes::KeyPath
    value = can_visit?(value.endpoint) ? visit(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_s]
  when ActiveRecord::Relation
    value = visit(
      value.select_values.empty? ? value.select(value.klass.arel_table[value.klass.primary_key]) : value,
      parent
    )
  else
    value = visit(value, parent) if can_visit?(value)
  end

  value = quote_for_node(o.expr, value)

  attribute = case o.expr
  when Nodes::Stub, Nodes::Function, Nodes::Literal, Nodes::Grouping
    visit(o.expr, parent)
  else
    contextualize(parent)[o.expr]
  end

  if Array === value && [:in, :not_in].include?(o.method_name)
    o.method_name == :in ? attribute_in_array(attribute, value) : attribute_not_in_array(attribute, value)
  else
    attribute.send(o.method_name, value)
  end
end

#visit_Squeel_Nodes_Sifter(o, parent) ⇒ Object (private)

Visit a Squeel sifter by executing its corresponding constraint block in the parent's class, with its given arguments, then visiting the result.

Parameters:

  • o (Nodes::Sifter)

    The Sifter to visit

  • parent

    The parent object in the context

Returns:

  • The result of visiting the executed block's return value


18
19
20
21
# File 'lib/squeel/visitors/predicate_visitation.rb', line 18

def visit_Squeel_Nodes_Sifter(o, parent)
  klass = classify(parent)
  visit(klass.send("sifter_#{o.name}", *o.args), parent)
end