Class: Autoselect::Otu::Levels::Fast

Inherits:
Level
  • Object
show all
Defined in:
lib/autoselect/otu/levels/fast.rb

Overview

Fast level: prefix-only match via a single LEFT JOIN query; no similarity/fuzzy matching. Note the underlying cached/name columns only carry trigram GIN indexes (no plain btree under this DB's collation), so Postgres still serves these prefix scans off the GIN index — "no GIN" describes the matching strategy (deterministic prefix, no similarity scoring), not the query plan. Covers three patterns in one round-trip:

1. Otu#name (taxon_name_id IS NULL) — prefix match
2. TaxonName#cached               — prefix match
3. Multi-word hybrid — every possible split of the term into a
 (taxon_prefix, otu_part) pair; both halves use prefix matching so
 'P PE01' matches 'Pheidole' + 'PE01', 'Ph PE01' also matches, etc.
 All split points are OR'd into the same query (one round-trip).

Ordering: shorter coalesced name first (exact matches float up over prefix matches); standalone OTUs use Otu#name length; then shorter Otu#name as tiebreaker.

Constant Summary

Constants inherited from Level

Level::DEFAULT_FUSE_MS, Level::EXTERNAL_FUSE_MS, Level::MINIMUM_RESULTS

Instance Method Summary collapse

Methods inherited from Level

#external?, #fuse_ms, #metadata, #minimum_results, #model_key, #record_info, #record_info_html, #record_label, #record_label_html

Instance Method Details

#call(term:, operator: nil, project_id: nil, user_id: nil, **_kwargs) ⇒ Array<Otu>

Parameters:

  • term (String)
  • project_id (Integer, nil) (defaults to: nil)

Returns:



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
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/autoselect/otu/levels/fast.rb', line 37

def call(term:, operator: nil, project_id: nil, user_id: nil, **_kwargs)
  return [] if term.blank?

  sanitized = ::ApplicationRecord.sanitize_sql_like(term)
  o  = ::Otu.arel_table
  tn = ::TaxonName.arel_table

  # Pattern 1 — standalone OTU name (no taxon_name attachment)
  p1 = o[:taxon_name_id].eq(nil).and(
    o[:name].matches("#{sanitized}%")
  )

  # Pattern 2 — OTU backed by a TaxonName whose cached column matches
  p2 = tn[:cached].matches("#{sanitized}%")

  conditions = p1.or(p2)

  # Pattern 3 — multi-word hybrid: try every split point so that both
  # short abbreviations ('P PE01') and longer prefixes ('Phei PE01') work.
  # Both halves use prefix matching.
  words = term.split(' ')
  if words.length >= 2
    hybrid = (1...words.length).map do |i|
      taxon_part = words[0, i].join(' ')
      otu_part   = words[i..].join(' ')
      s_taxon = ::ApplicationRecord.sanitize_sql_like(taxon_part)
      s_otu   = ::ApplicationRecord.sanitize_sql_like(otu_part)
      tn[:cached].matches("#{s_taxon}%").and(
        o[:name].matches("#{s_otu}%")
      )
    end.reduce(:or)

    conditions = conditions.or(hybrid)
  end

  scope = ::Otu.eager_load(:taxon_name).where(conditions)
  scope = scope.where(project_id:) if project_id.present?
  # Shorter coalesced name = closer to exact match; standalone OTUs fall back to otus.name.
  scope = scope.order(
    Arel.sql('length(coalesce(taxon_names.cached, otus.name, \'\')) asc nulls last, length(coalesce(otus.name, \'\')) asc nulls last')
  )
  scope.limit(20).to_a
end

#descriptionObject



30
31
32
# File 'lib/autoselect/otu/levels/fast.rb', line 30

def description
  'Prefix match on OTU name, linked taxon name cached, and taxon name x OTU name combinations (no fuzzy matching)'
end

#keyObject



22
23
24
# File 'lib/autoselect/otu/levels/fast.rb', line 22

def key
  :fast
end

#labelObject



26
27
28
# File 'lib/autoselect/otu/levels/fast.rb', line 26

def label
  'Fast'
end