Class: OrderQuery::WhereBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/order_query/where_builder.rb

Overview

Build where clause for searching around a record in an order space

Constant Summary collapse

WHERE_IDENTITY =
[''.freeze, [].freeze].freeze
WHERE_NONE =
[''.freeze, [].freeze].freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(record, order_space) ⇒ WhereBuilder

Returns a new instance of WhereBuilder.

Parameters:



12
13
14
15
# File 'lib/order_query/where_builder.rb', line 12

def initialize(record, order_space)
  @order  = order_space
  @record = record
end

Class Attribute Details

.wrap_top_level_orObject

Returns the value of attribute wrap_top_level_or.



120
121
122
# File 'lib/order_query/where_builder.rb', line 120

def wrap_top_level_or
  @wrap_top_level_or
end

Instance Attribute Details

#orderOrderQuery::OrderSpace (readonly)



8
9
10
# File 'lib/order_query/where_builder.rb', line 8

def order
  @order
end

#recordActiveRecord::Base (readonly)

Returns:

  • (ActiveRecord::Base)


6
7
8
# File 'lib/order_query/where_builder.rb', line 6

def record
  @record
end

Instance Method Details

#attr_value(cond) ⇒ Object



115
116
117
# File 'lib/order_query/where_builder.rb', line 115

def attr_value(cond)
  record.send cond.name
end

#build_query(mode) ⇒ query, parameters

Returns conditions that exclude all elements not before / after the current one.

Parameters:

  • mode (:before or :after)

Returns:

  • (query, parameters)

    conditions that exclude all elements not before / after the current one



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/order_query/where_builder.rb', line 19

def build_query(mode)
  # pairs of [x0, y0]
  pairs = order.conditions.map { |cond|
    [where_relative(cond, mode, true), (where_eq(cond) unless cond.unique?)].reject { |x|
      x.nil? || x == WHERE_IDENTITY || x == WHERE_NONE
    }.compact
  }
  query = group_operators pairs
  return query unless self.class.wrap_top_level_or
  # Wrap top level OR clause for performance, see https://github.com/glebm/order_query/issues/3
  top_pair_idx = pairs.index(&:present?)
  if top_pair_idx && pairs[top_pair_idx].length == 2 && (top_level_cond = order.conditions[top_pair_idx])
    join_terms 'AND'.freeze, where_relative(top_level_cond, mode, false), wrap_parens(query)
  else
    query
  end
end

#group_operators(term_pairs) ⇒ query, parameters

Join condition pairs internally with OR, and nested within each other with AND Since x matches order criteria with values that come before / after the current record, and y matches order criteria with values equal to the current record’s value (for resolving ties), the resulting condition matches just the elements that come before / after the record

Parameters:

  • term_pairs (Array)

    of query terms [[x0, y0], [x1, y1], …], xi, yi are pairs of [query, parameters]

Returns:

  • (query, parameters)

    x0 OR y0 AND (x1 OR

    y1 AND (x2 OR
            y2 AND x3))
    


49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/order_query/where_builder.rb', line 49

def group_operators(term_pairs)
  # create "x OR y" string
  disjunctive = join_terms 'OR'.freeze, *term_pairs[0]
  rest        = term_pairs.from(1)
  if rest.present?
    # nest the remaining pairs recursively, appending them with " AND "
    rest_grouped = group_operators rest
    join_terms 'AND'.freeze, disjunctive, (rest.length == 1 ? rest_grouped : wrap_parens(rest_grouped))
  else
    disjunctive
  end
end

#join_terms(op, *terms) ⇒ query, parameters

joins terms with an operator

Returns:

  • (query, parameters)


68
69
70
71
# File 'lib/order_query/where_builder.rb', line 68

def join_terms(op, *terms)
  [terms.map { |t| t.first.presence }.compact.join(" #{op} "),
   terms.map(&:second).reduce(:+) || []]
end

#where_eq(cond, value = attr_value(cond)) ⇒ Object



101
102
103
# File 'lib/order_query/where_builder.rb', line 101

def where_eq(cond, value = attr_value(cond))
  [%Q(#{cond.col_name_sql} = ?).freeze, [value]]
end

#where_in(cond, values) ⇒ Object



90
91
92
93
94
95
96
97
98
99
# File 'lib/order_query/where_builder.rb', line 90

def where_in(cond, values)
  case values.length
    when 0
      WHERE_NONE
    when 1
      where_eq cond, values[0]
    else
      ["#{cond.col_name_sql} IN (?)".freeze, [values]]
  end
end

#where_ray(cond, from, mode, strict = true) ⇒ Object



105
106
107
108
109
110
# File 'lib/order_query/where_builder.rb', line 105

def where_ray(cond, from, mode, strict = true)
  ops = %w(< >)
  ops = ops.reverse if mode == :after
  op  = {asc: ops[0], desc: ops[1]}[cond.order || :asc]
  ["#{cond.col_name_sql} #{op}#{'=' unless strict} ?".freeze, [from]]
end

#where_relative(cond, mode, strict = true, skip_complete = true) ⇒ query, params

Return query conditions for attribute values before / after the current one

Parameters:

  • mode (:before or :after)

Returns:

  • (query, params)

    return query conditions for attribute values before / after the current one



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/order_query/where_builder.rb', line 75

def where_relative(cond, mode, strict = true, skip_complete = true)
  value = attr_value cond
  if cond.list?
    values = cond.filter_values(value, mode, strict)
    if cond.complete? && values.length == cond.order.length
      WHERE_IDENTITY
    else
      where_in cond, values
    end
  else
    where_ray cond, value, mode, strict
  end
end

#wrap_parens(t) ⇒ Object



62
63
64
# File 'lib/order_query/where_builder.rb', line 62

def wrap_parens(t)
  ["(#{t[0]})", t[1]]
end