Module: Backlex::Filter

Defined in:
lib/backlex/filter.rb

Overview

Static condition constructors — a Ruby port of the leaf/logical helpers in query.ts. Compose them and pass to QueryBuilder#where. Everything compiles to the canonical JSON Condition the REST API speaks.

rows = client.from("orders").query
  .where(Backlex::Filter.and_(
    Backlex::Filter.eq("status", "active"),
    Backlex::Filter.gte("total", 100),
    Backlex::Filter.rel("customer", Backlex::Filter.eq("tier", "gold")), # -> "customer.tier"
    Backlex::Filter.gte("placed_at", Backlex::Filter.now(sub: { "months" => 1 })),
  ))
  .select("id", "total", "customer.name")
  .order_by("-placed_at", "id")
  .limit(50)
  .list

Class Method Summary collapse

Class Method Details

.and_(*conds) ⇒ Object



43
# File 'lib/backlex/filter.rb', line 43

def and_(*conds); { "$and" => conds }; end

.between(f, lo, hi) ⇒ Object



34
# File 'lib/backlex/filter.rb', line 34

def between(f, lo, hi); leaf(f, "_between", [lo, hi]); end

.comparison?(hash) ⇒ Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/backlex/filter.rb', line 99

def comparison?(hash)
  !hash.empty? && hash.keys.all? { |k| k.to_s.start_with?("_") }
end

.contains(f, v) ⇒ Object



38
# File 'lib/backlex/filter.rb', line 38

def contains(f, v); leaf(f, "_contains", v); end

.empty(f) ⇒ Object



36
# File 'lib/backlex/filter.rb', line 36

def empty(f); leaf(f, "_empty", true); end

.ends_with(f, v) ⇒ Object



41
# File 'lib/backlex/filter.rb', line 41

def ends_with(f, v); leaf(f, "_ends_with", v); end

.eq(f, v) ⇒ Object



26
# File 'lib/backlex/filter.rb', line 26

def eq(f, v); leaf(f, "_eq", v); end

.gt(f, v) ⇒ Object



28
# File 'lib/backlex/filter.rb', line 28

def gt(f, v); leaf(f, "_gt", v); end

.gte(f, v) ⇒ Object



29
# File 'lib/backlex/filter.rb', line 29

def gte(f, v); leaf(f, "_gte", v); end

.icontains(f, v) ⇒ Object



39
# File 'lib/backlex/filter.rb', line 39

def icontains(f, v); leaf(f, "_icontains", v); end

.in_(f, vs) ⇒ Object



32
# File 'lib/backlex/filter.rb', line 32

def in_(f, vs); leaf(f, "_in", vs); end

.is_null(f, is_null = true) ⇒ Object



35
# File 'lib/backlex/filter.rb', line 35

def is_null(f, is_null = true); leaf(f, "_null", is_null); end

.leaf(field, op, value) ⇒ Object



22
23
24
# File 'lib/backlex/filter.rb', line 22

def leaf(field, op, value)
  { field => { op => value } }
end

.lt(f, v) ⇒ Object



30
# File 'lib/backlex/filter.rb', line 30

def lt(f, v); leaf(f, "_lt", v); end

.lte(f, v) ⇒ Object



31
# File 'lib/backlex/filter.rb', line 31

def lte(f, v); leaf(f, "_lte", v); end

.nempty(f) ⇒ Object



37
# File 'lib/backlex/filter.rb', line 37

def nempty(f); leaf(f, "_nempty", true); end

.neq(f, v) ⇒ Object



27
# File 'lib/backlex/filter.rb', line 27

def neq(f, v); leaf(f, "_neq", v); end

.nin(f, vs) ⇒ Object



33
# File 'lib/backlex/filter.rb', line 33

def nin(f, vs); leaf(f, "_nin", vs); end

.normalize(raw) ⇒ Object

Turn any accepted filter shape into the canonical Condition: handles $and/$or/$not (and their _ aliases) and implicit equality ({ “status” => “active” } -> { “status” => { “_eq” => “active” } }). Idempotent.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/backlex/filter.rb', line 75

def normalize(raw)
  return {} unless raw.is_a?(Hash)

  a = raw["$and"] || raw["_and"]
  return { "$and" => a.map { |c| normalize(c) } } if a.is_a?(Array)

  o = raw["$or"] || raw["_or"]
  return { "$or" => o.map { |c| normalize(c) } } if o.is_a?(Array)

  if raw.key?("$not") || raw.key?("_not")
    return { "$not" => normalize(raw["$not"] || raw["_not"]) }
  end

  out = {}
  raw.each do |k, v|
    out[k] =
      if v.is_a?(Hash) && comparison?(v) then v
      elsif v.is_a?(Hash) then v # unknown object shape — pass through
      else { "_eq" => v }
      end
  end
  out
end

.not_(cond) ⇒ Object



45
# File 'lib/backlex/filter.rb', line 45

def not_(cond); { "$not" => cond }; end

.now(add: nil, sub: nil) ⇒ Object

Relative-date value, e.g. Filter.now(sub: { “months” => 1 }).



55
56
57
58
59
60
# File 'lib/backlex/filter.rb', line 55

def now(add: nil, sub: nil)
  opts = {}
  opts["add"] = add if add
  opts["sub"] = sub if sub
  { "$now" => opts }
end

.or_(*conds) ⇒ Object



44
# File 'lib/backlex/filter.rb', line 44

def or_(*conds); { "$or" => conds }; end

.prefix_keys(cond, head) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/backlex/filter.rb', line 62

def prefix_keys(cond, head)
  return { "$and" => cond["$and"].map { |c| prefix_keys(c, head) } } if cond["$and"].is_a?(Array)
  return { "$or" => cond["$or"].map { |c| prefix_keys(c, head) } } if cond["$or"].is_a?(Array)
  return { "$not" => prefix_keys(cond["$not"], head) } if cond["$not"].is_a?(Hash)

  out = {}
  cond.each { |k, v| out["#{head}.#{k}"] = v }
  out
end

.rel(head, *conds) ⇒ Object

Traverse a relation one hop: every leaf key produced by conds is prefixed with “head.”. Multiple conds are ANDed first.



49
50
51
52
# File 'lib/backlex/filter.rb', line 49

def rel(head, *conds)
  inner = conds.length == 1 ? conds[0] : { "$and" => conds }
  prefix_keys(inner, head)
end

.starts_with(f, v) ⇒ Object



40
# File 'lib/backlex/filter.rb', line 40

def starts_with(f, v); leaf(f, "_starts_with", v); end