Class: Queries::TaxonName::Autocomplete
Constant Summary
collapse
- DEFAULT_LIMIT =
20
- CACHED_NAME_WEIGHT =
Weights. Theory (using this loosely) is that this
will proportionally increase the importance in the list of the corresponding element.
The trade-off is subtle, but seems to work at first try.
8.0
- CACHED_AUTHOR_YEAR_WEIGHT =
6.0
- CACHED_WEIGHT =
4.0
- CACHED_ORIGINAL_COMBINATION_WEIGHT =
2.0
Query::Autocomplete::DELEGATED_DEPTH, Query::Autocomplete::DELEGATED_MINIMUM_LENGTH, Query::Autocomplete::LITERAL_RESTRICTION_MAX
Instance Attribute Summary collapse
#delegated_results, #dynamic_limit, #limit, #project_id, #query_string, #restrict_to
Attributes inherited from Query
#query_string, #terms
Instance Method Summary
collapse
#apply_restriction, #asserted_object_autocomplete, #asserted_object_restriction, #autocomplete_cached_wildcard_anywhere, #autocomplete_common_name_exact, #autocomplete_common_name_like, #autocomplete_exact_id, #autocomplete_exactly_named, #autocomplete_named, #autocomplete_ordered_wildcard_pieces_in_cached, #cached_facet, #combine_or_clauses, #common_name_name, #common_name_table, #common_name_wild_pieces, #delegated_autocomplete, #exactly_named, #fragments, #integers, #least_levenshtein, #match_wildcard_end_in_cached, #match_wildcard_in_cached, #named, #only_ids, #only_integers?, #order_by_id_rank, #parent, #parent_child_join, #parent_child_where, #pieces, #safe_integers, #scope, #string_fragments, #wildcard_wrapped_integers, #wildcard_wrapped_years, #with_cached, #with_cached_like, #with_id, #with_project_id, #year_letter, #years
Methods inherited from Query
#alphabetic_strings, #alphanumeric_strings, base_name, #base_name, #build_terms, #cached_facet, #end_wildcard, #levenshtein_distance, #match_ordered_wildcard_pieces_in_cached, #no_terms?, referenced_klass, #referenced_klass, #referenced_klass_except, #referenced_klass_intersection, #referenced_klass_union, #start_and_end_wildcard, #start_wildcard, #table, #wildcard_pieces
Constructor Details
#initialize(string, **params) ⇒ Autocomplete
Returns a new instance of Autocomplete.
44
45
46
47
48
49
50
51
52
53
54
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 44
def initialize(string, **params)
@nomenclature_group = params[:nomenclature_group]
@valid = boolean_param(params, :valid)
@type = params[:type]
@parent_id = params[:parent_id]
@no_leaves = boolean_param(params, :no_leaves)
@exact = boolean_param(params, :exact)
super
end
|
Instance Attribute Details
#authorship ⇒ String
Returns (including empty).
41
42
43
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 41
def authorship
@authorship
end
|
#exact ⇒ Boolean
Returns &exact=<"true"|"false">
if 'true' then only #name = query_string results are returned (no fuzzy matching).
33
34
35
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 33
def exact
@exact
end
|
#no_leaves ⇒ Boolean
Returns &no_leaves=<"true"|"false">
if 'true' then only names with descendents will be returned.
38
39
40
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 38
def no_leaves
@no_leaves
end
|
#nomenclature_group ⇒ Array
Returns &nomenclature_group=<<Iczn|Icnp|Icn>::<Higher|Family|Genus|Species>>.
10
11
12
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 10
def nomenclature_group
@nomenclature_group
end
|
#parent_id ⇒ Array
Returns &parent_id=&parent_id=<other_int> etc.
26
27
28
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 26
def parent_id
@parent_id
end
|
#type ⇒ Array
Returns &type=<Protonym, Combination, Hybrid, etc.>&type= etc.
22
23
24
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 22
def type
@type
end
|
#valid ⇒ Boolean?
Returns &valid=<"true"|"false">
if 'true' then id == cached_valid_taxon_name_id
if 'false' then id != cached_valid_taxon_name
if nil then no check made, i.e. all names
string is converted to Boolean here.
18
19
20
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 18
def valid
@valid
end
|
Instance Method Details
#and_clauses ⇒ Arel:Nodes?
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 69
def and_clauses
clauses = [
valid_state,
is_type,
with_parent_id,
with_nomenclature_group,
].compact
return nil if clauses.nil?
a = clauses.shift
clauses.each do |b|
a = a.and(b)
end
a
end
|
#autocomplete ⇒ Array
424
425
426
427
428
429
430
431
432
433
434
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 424
def autocomplete
result = []
strategy_queries.each do |a|
result += a.limit(limit).to_a
result.uniq!
break if result.count >= limit
end
result.first(limit)
end
|
#autocomplete_cached ⇒ Object
---- gin methods
Consider word_similarity()
278
279
280
281
282
283
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 278
def autocomplete_cached
::TaxonName.where(project_id:).select(ApplicationRecord.sanitize_sql(['taxon_names.*, similarity(?, taxon_names.cached) AS sml', query_string]))
.where('taxon_names.cached % ?', query_string) .where(ApplicationRecord.sanitize_sql_array(["similarity('%s', taxon_names.cached) > 0.6", query_string]))
.order('sml DESC, taxon_names.cached')
end
|
#autocomplete_cached_author_year ⇒ Object
292
293
294
295
296
297
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 292
def autocomplete_cached_author_year
::TaxonName.select(ApplicationRecord.sanitize_sql(['taxon_names.*, similarity(?, taxon_names.cached_author_year) AS sml', query_string]))
.where('taxon_names.cached_author_year % ?', query_string)
.where(ApplicationRecord.sanitize_sql(["similarity('%s', taxon_names.cached_author_year) > 0.6", query_string]))
.order('sml DESC, taxon_names.cached_author_year')
end
|
#autocomplete_cached_end_wildcard ⇒ Scope
189
190
191
192
193
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 189
def autocomplete_cached_end_wildcard
s = query_string.delete('\\')
a = table[:cached].matches("#{s}%")
base_query.where(a.to_sql).limit(20)
end
|
#autocomplete_cached_name_end_wildcard ⇒ Scope
218
219
220
221
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 218
def autocomplete_cached_name_end_wildcard
a = table[:name].matches("#{query_string}%")
base_query.where(a.to_sql).limit(20)
end
|
#autocomplete_cached_original_combination_wildcard_whitespace_with_space ⇒ Object
228
229
230
231
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 228
def autocomplete_cached_original_combination_wildcard_whitespace_with_space
a = table[:cached_original_combination].matches("#{query_string.gsub('. ', ' ').gsub(/[\s\\]/, '% ')}%")
base_query.where(a.to_sql).limit(20)
end
|
#autocomplete_cached_wildcard_whitespace ⇒ Scope
234
235
236
237
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 234
def autocomplete_cached_wildcard_whitespace
a = table[:cached].matches("#{query_string.gsub('. ', ' ').gsub(/[\s\\]/, '%')}")
base_query.where(a.to_sql).limit(20)
end
|
#autocomplete_cached_wildcard_whitespace_with_space ⇒ Object
223
224
225
226
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 223
def autocomplete_cached_wildcard_whitespace_with_space
a = table[:cached].matches("#{query_string.gsub('. ', ' ').gsub(/[\s\\]/, '% ')}%")
base_query.where(a.to_sql).limit(20)
end
|
#autocomplete_combined_gin ⇒ Object
Used in /otus/api/v1/autocomplete
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 308
def autocomplete_combined_gin
a = ::TaxonName.select(ApplicationRecord.sanitize_sql(
['taxon_names.*, similarity(?, name) AS sml_n, similarity(?, taxon_names.cached_author_year) AS sml_cay, similarity(?, cached) AS sml_c, similarity(?, taxon_names.cached_original_combination) AS sml_coc',
query_string, authorship, query_string, query_string])
).where('taxon_names.cached_author_year % ? OR taxon_names.cached_original_combination % ? OR taxon_names.cached % ?', query_string, query_string, query_string)
s = 'WITH tns AS (' + a.to_sql + ') ' +
::TaxonName
.select(Arel.sql("taxon_names.*, (( COALESCE(tns1.sml_n,0) * #{CACHED_NAME_WEIGHT} + \
COALESCE(tns1.sml_cay,0) * #{CACHED_AUTHOR_YEAR_WEIGHT} + \
COALESCE(tns1.sml_c,0) * #{CACHED_WEIGHT} + \
COALESCE(tns1.sml_coc,0) * #{CACHED_ORIGINAL_COMBINATION_WEIGHT} \
)) sml_tn"))
.joins('JOIN tns as tns1 on tns1.id = taxon_names.id')
.to_sql
::TaxonName.select('taxon_names.*, sml_tn as sml_t').from('(' + s + ') as taxon_names').order('taxon_names.sml_tn DESC').distinct
end
|
#autocomplete_exact_cached ⇒ Scope
115
116
117
118
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 115
def autocomplete_exact_cached
a = table[:cached].eq(query_string)
base_query.where(a.to_sql).order('taxon_names.cached_author_year ASC').limit(20)
end
|
#autocomplete_exact_cached_name_and_author_year ⇒ Scope?
Matches the full displayed label, e.g.
"Isocapnia crinita (Needham & Claassen, 1925)",
i.e. cached and cached_author_year (see
TaxonName#cached_name_and_author_year).
143
144
145
146
147
148
149
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 143
def autocomplete_exact_cached_name_and_author_year
p = parsed_name_and_author_year
return nil if p.nil?
a = table[:cached].eq(p[:name]).and(table[:cached_author_year].eq(p[:author_year]))
base_query.where(a.to_sql).order('taxon_names.id ASC').limit(5)
end
|
#autocomplete_exact_cached_original_combination ⇒ Scope
152
153
154
155
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 152
def autocomplete_exact_cached_original_combination
a = table[:cached_original_combination].eq(query_string)
base_query.where(a.to_sql).order('taxon_names.cached_author_year ASC').limit(20)
end
|
#autocomplete_exact_name ⇒ Scope
176
177
178
179
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 176
def autocomplete_exact_name
a = table[:name].eq(query_string)
base_query.where(a.to_sql).order('taxon_names.cached_author_year ASC').limit(20)
end
|
#autocomplete_exact_name_and_year ⇒ Scope
164
165
166
167
168
169
170
171
172
173
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 164
def autocomplete_exact_name_and_year
a = alphabetic_strings.select { |b| !(b =~ /\d/) }
b = years
if a.size == 1 && !b.empty?
a = table[:name].eq(a.first).and(table[:cached_author_year].matches_any(wildcard_wrapped_years))
base_query.where(a.to_sql).limit(10)
else
nil
end
end
|
#autocomplete_genus_species1(result) ⇒ Scope
203
204
205
206
207
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 203
def autocomplete_genus_species1(result)
return nil if result.nil?
a = table[:cached].matches(result)
base_query.where(a.to_sql).order('type DESC, cached ASC').limit(8)
end
|
#autocomplete_genus_species2(result) ⇒ Scope
211
212
213
214
215
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 211
def autocomplete_genus_species2(result)
return nil if result.nil?
a = table[:cached].matches(result + '%')
base_query.where(a.to_sql).order('taxon_names.type DESC, taxon_names.cached ASC').limit(8)
end
|
#autocomplete_name_author_year_fragment ⇒ Scope?
240
241
242
243
244
245
246
247
248
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 240
def autocomplete_name_author_year_fragment
f = fragments
if f.size == 2
a = table[:name].matches(f[0]).and(table[:cached_author_year].matches(f[1]))
base_query.where(a.to_sql).limit(20)
else
nil
end
end
|
#autocomplete_original_combination ⇒ Object
285
286
287
288
289
290
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 285
def autocomplete_original_combination
::TaxonName.select(ApplicationRecord.sanitize_sql(['taxon_names.*, similarity(?, taxon_names.cached_original_combination) AS sml', query_string]))
.where('taxon_names.cached_original_combination % ?', query_string)
.where(ApplicationRecord.sanitize_sql_array(["similarity('%s', taxon_names.cached_original_combination) > 0.6", query_string]))
.order('sml DESC, taxon_names.cached_original_combination')
end
|
#autocomplete_taxon_name_author_year_matches ⇒ Arel::Nodes::Matches
265
266
267
268
269
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 265
def autocomplete_taxon_name_author_year_matches
a = authorship
return nil if a.nil?
base_query.where(table[:cached_author_year].matches(a).to_sql).limit(10)
end
|
#autocomplete_top_cached ⇒ Scope
182
183
184
185
186
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 182
def autocomplete_top_cached
s = query_string
a = table[:cached].matches("#{s}%")
base_query.where(a.to_sql).limit(1)
end
|
#autocomplete_top_cached_subgenus ⇒ Scope
196
197
198
199
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 196
def autocomplete_top_cached_subgenus
a = table[:cached].matches("%(#{query_string})")
base_query.where(a.to_sql).limit(1)
end
|
#autocomplete_wildcard_author_year_joined_pieces ⇒ Scope?
251
252
253
254
255
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 251
def autocomplete_wildcard_author_year_joined_pieces
return nil if pieces.empty?
a = table[:cached_author_year].matches("%#{pieces.join('%')}%")
base_query.where(a.to_sql).order('taxon_names.cached ASC').limit(20)
end
|
#autocomplete_wildcard_cached_original_combination ⇒ Scope
158
159
160
161
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 158
def autocomplete_wildcard_cached_original_combination
a = table[:cached_original_combination].matches(wildcard_pieces)
base_query.where(a.to_sql).order('taxon_names.cached_author_year ASC').limit(20)
end
|
#autocomplete_wildcard_joined_strings ⇒ Scope?
258
259
260
261
262
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 258
def autocomplete_wildcard_joined_strings
return nil if alphabetic_strings.empty?
a = table[:cached].matches("%#{alphabetic_strings.join('%')}%")
base_query.where(a.to_sql).limit(10)
end
|
#base_query ⇒ Scope
TODO: this should deprecate for gin based approaches.
455
456
457
458
459
460
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 455
def base_query
::TaxonName.select(:id, :parent_id, :type, :rank_class, :name, :cached, :cached_html,
:cached_original_combination, :cached_author_year, :cached_valid_taxon_name_id, :cached_is_valid, 'char_length(taxon_names.cached)')
.eager_load(:parent)
.order(Arel.sql('char_length(taxon_names.cached), taxon_names.cached ASC'))
end
|
#comprehensive_autocomplete ⇒ Object
TODO: Refactor to OTU approach?
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 354
def comprehensive_autocomplete
z = genus_species
queries = [
autocomplete_exact_cached,
autocomplete_exact_cached_name_and_author_year,
autocomplete_exact_cached_original_combination,
autocomplete_exact_name_and_year,
autocomplete_exact_name,
autocomplete_exact_id,
autocomplete_identifier_cached_exact,
autocomplete_identifier_identifier_exact,
autocomplete_cached, autocomplete_original_combination, autocomplete_cached_author_year,
autocomplete_genus_species1(z), autocomplete_genus_species2(z), autocomplete_top_cached_subgenus,
autocomplete_wildcard_joined_strings,
autocomplete_wildcard_author_year_joined_pieces,
autocomplete_wildcard_cached_original_combination
]
end
|
#exact_autocomplete ⇒ Object
Used in New taxon name task, for example
TODO: what is intent?
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 329
def exact_autocomplete
[
autocomplete_exact_id,
autocomplete_exact_cached,
autocomplete_exact_cached_name_and_author_year,
autocomplete_exact_cached_original_combination,
autocomplete_identifier_cached_exact,
autocomplete_identifier_identifier_exact,
autocomplete_exact_name_and_year,
autocomplete_cached_end_wildcard,
autocomplete_cached_wildcard_whitespace_with_space,
autocomplete_cached_original_combination_wildcard_whitespace_with_space,
autocomplete_cached_wildcard_whitespace,
autocomplete_name_author_year_fragment,
autocomplete_taxon_name_author_year_matches,
autocomplete_wildcard_joined_strings,
autocomplete_wildcard_author_year_joined_pieces,
autocomplete_wildcard_cached_original_combination,
autocomplete_exact_name, ]
end
|
#genus_species ⇒ String?
Returns parse and only return what is assumed to be genus/species, with a wildcard in front.
438
439
440
441
442
443
444
445
446
447
448
449
450
451
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 438
def genus_species
p = Vendor::Biodiversity::Result.new
p.name = query_string
r = p.parse
a = p.genus
b = p.species
if a && b
a + '%' + b
else
nil
end
end
|
#is_type ⇒ Arel::Nodes::<>?
95
96
97
98
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 95
def is_type
return nil if type.empty?
table[:type].in(type)
end
|
#parsed_name_and_author_year ⇒ Hash?
Returns author_year: split from the query string via the GlobalNames
parser, reformatted to match how cached and cached_author_year are
stored (comma before the year, authors joined with '&'). nil when the
string doesn't parse with an authorship component.
125
126
127
128
129
130
131
132
133
134
135
136
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 125
def parsed_name_and_author_year
return @parsed_name_and_author_year if defined?(@parsed_name_and_author_year)
a = parsed_query_string
name = a.dig(:canonical, :simple)
author_year = a.dig(:authorship, :normalized)
@parsed_name_and_author_year = if a.dig(:parsed) && name.present? && author_year.present?
{ name:, author_year: author_year.sub(/ (\d{4})/, ', \1') }
end
end
|
#parsed_query_string ⇒ Hash
Returns the memoized GlobalNames parser result for query_string.
474
475
476
477
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 474
def parsed_query_string
return @parsed_query_string if defined?(@parsed_query_string)
@parsed_query_string = ::Biodiversity::Parser.parse(query_string)
end
|
#strategy_queries ⇒ Array<ActiveRecord::Relation>
Returns the queries #autocomplete runs, in priority order.
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 403
def strategy_queries
queries = (exact ? exact_autocomplete : comprehensive_autocomplete )
queries.compact!
queries.map do |q|
a = q
a = q.where(project_id:) if project_id.present? a = a.where(and_clauses.to_sql) if and_clauses
if !parent_id.empty?
a = a.descendants_of(::TaxonName.where(id: parent_id))
end
a = a.not_leaves if no_leaves
apply_restriction(a)
end
end
|
#taxon_name_hierarchies_table ⇒ Arel::Table
463
464
465
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 463
def taxon_name_hierarchies_table
Arel::Table.new('taxon_name_hierarchies')
end
|
#unified_autocomplete ⇒ Object
393
394
395
396
397
398
399
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 393
def unified_autocomplete
[
autocomplete_exact_id,
autocomplete_combined_gin,
autocomplete_identifier_cached_exact,
]
end
|
#valid_state ⇒ Arel::Nodes::<>?
88
89
90
91
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 88
def valid_state
return nil if @valid.nil?
valid ? table[:id].eq(table[:cached_valid_taxon_name_id]) : table[:id].not_eq(table[:cached_valid_taxon_name_id])
end
|
#with_cached_author_year ⇒ Arel::Nodes::Matches
468
469
470
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 468
def with_cached_author_year
table[:cached_author_year].matches_any(terms)
end
|
#with_nomenclature_group ⇒ Arel::Nodes::Grouping?
109
110
111
112
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 109
def with_nomenclature_group
return nil if nomenclature_group.empty?
table[:rank_class].matches_any(nomenclature_group)
end
|
#with_parent_id ⇒ Arel::Nodes::<>?
and clause, limit to ancestors or [ids]
102
103
104
105
|
# File 'lib/queries/taxon_name/autocomplete.rb', line 102
def with_parent_id
return nil if parent_id.empty?
taxon_name_hierarchies_table[:ancestor_id].in(parent_id)
end
|