Class: Queries::Query

Inherits:
Object
  • Object
show all
Includes:
Arel::Nodes, Concerns::Identifiers
Defined in:
lib/queries/query.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(string, project_id: nil, **keyword_args) ⇒ Query

Returns a new instance of Query.

Parameters:

  • args (Hash)


35
36
37
38
39
40
# File 'lib/queries/query.rb', line 35

def initialize(string, project_id: nil, **keyword_args)
  @query_string = ::ApplicationRecord.sanitize_sql(string)&.delete("\u0000") # remove null bytes
  @options = keyword_args
  @project_id = project_id
  build_terms
end

Instance Attribute Details

#dynamic_limitInteger

Returns:

  • (Integer)


32
33
34
# File 'lib/queries/query.rb', line 32

def dynamic_limit
  @dynamic_limit
end

#optionsObject

parameters from keyword_args, used to group and pass along things like annotator params



29
30
31
# File 'lib/queries/query.rb', line 29

def options
  @options
end

#project_idObject

Returns the value of attribute project_id.



26
27
28
# File 'lib/queries/query.rb', line 26

def project_id
  @project_id
end

#query_stringString?

Returns the initial, unparsed value.

Returns:

  • (String, nil)

    the initial, unparsed value



23
24
25
# File 'lib/queries/query.rb', line 23

def query_string
  @query_string
end

#termsArray

Returns:

  • (Array)


64
65
66
# File 'lib/queries/query.rb', line 64

def terms
  @terms
end

Instance Method Details

#alphabetic_stringsArray

Returns:

  • (Array)


98
99
100
# File 'lib/queries/query.rb', line 98

def alphabetic_strings
  Utilities::Strings.alphabetic_strings(query_string) #alphanumeric allows searches by page number, year, etc.
end

#alphanumeric_stringsArray

Returns:

  • (Array)


103
104
105
# File 'lib/queries/query.rb', line 103

def alphanumeric_strings
  Utilities::Strings.alphanumeric_strings(query_string) #alphanumeric allows searches by page number, year, etc.
end

#attribute_exact_facet(attribute = nil) ⇒ Object

params attribute [Symbol]

a facet for use when params include, `author`, and `exact_author` combinations
See queries/source/filter.rb for example use


195
196
197
198
199
200
201
202
203
# File 'lib/queries/query.rb', line 195

def attribute_exact_facet(attribute = nil)
  a = attribute.to_sym
  return nil if send(a).blank?
  if send("exact_#{a}".to_sym)
    table[a].eq(send(a).strip)
  else
    table[a].matches('%' + send(a).strip.gsub(/\s+/, '%') + '%')
  end
end

#autocompleteArray

Returns default the autocomplete result to all TODO: eliminate.

Returns:

  • (Array)

    default the autocomplete result to all TODO: eliminate



325
326
327
# File 'lib/queries/query.rb', line 325

def autocomplete
  all.to_a
end

#autocomplete_cachedActiveRecord::Relation

Returns:

  • (ActiveRecord::Relation)


350
351
352
353
354
355
356
# File 'lib/queries/query.rb', line 350

def autocomplete_cached
  if a = cached
    base_query.where(a.to_sql).limit(20)
  else
    nil
  end
end

#autocomplete_cached_wildcard_anywhereActiveRecord::Relation

Returns removes years/integers!.

Returns:

  • (ActiveRecord::Relation)

    removes years/integers!



343
344
345
346
347
# File 'lib/queries/query.rb', line 343

def autocomplete_cached_wildcard_anywhere
  a = match_wildcard_in_cached
  return nil if a.nil?
  base_query.where(a.to_sql)
end

#autocomplete_common_name_exactObject



382
383
384
385
# File 'lib/queries/query.rb', line 382

def autocomplete_common_name_exact
  return nil if no_terms?
  query_base.joins(:common_names).where(common_name_name.to_sql).limit(1)
end

#autocomplete_common_name_likeObject



387
388
389
390
# File 'lib/queries/query.rb', line 387

def autocomplete_common_name_like
  return nil if no_terms?
  query_base.joins(:common_names).where(common_name_wild_pieces.to_sql).limit(5)
end

#autocomplete_exact_idActiveRecord::Relation

Returns:

  • (ActiveRecord::Relation)


330
331
332
333
# File 'lib/queries/query.rb', line 330

def autocomplete_exact_id
  return nil if no_terms?
  base_query.where(id: query_string).limit(1)
end

#autocomplete_exactly_namedActiveRecord::Relation

Returns:

  • (ActiveRecord::Relation)


359
360
361
362
# File 'lib/queries/query.rb', line 359

def autocomplete_exactly_named
  return nil if no_terms?
  base_query.where(exactly_named.to_sql).limit(20)
end

#autocomplete_namedActiveRecord::Relation

Returns:

  • (ActiveRecord::Relation)


365
366
367
368
# File 'lib/queries/query.rb', line 365

def autocomplete_named
  return nil if no_terms?
  base_query.where(named.to_sql).limit(20)
end

#autocomplete_ordered_wildcard_pieces_in_cachedActiveRecord::Relation

Returns:

  • (ActiveRecord::Relation)


336
337
338
339
# File 'lib/queries/query.rb', line 336

def autocomplete_ordered_wildcard_pieces_in_cached
  return nil if no_terms?
  base_query.where(match_ordered_wildcard_pieces_in_cached.to_sql)
end

#build_termsArray

Ultimately we should replace this concept with full text indexing.

Returns:

  • (Array)

    a reasonable (starting) interpretation of any query string



78
79
80
# File 'lib/queries/query.rb', line 78

def build_terms
  @terms = @query_string.blank? ? [] : [end_wildcard, start_and_end_wildcard]
end

#cachedActiveRecord::Relation?

!!TODO: rename :cached_matches or similar (this is problematic !!)

Returns:

  • (ActiveRecord::Relation, nil)

    cached matches full query string wildcarded



266
267
268
269
# File 'lib/queries/query.rb', line 266

def cached
  return nil if no_terms?
  (table[:cached].matches_any(terms)).or(match_ordered_wildcard_pieces_in_cached)
end

#combine_or_clauses(clauses) ⇒ Arel::Nodes::Grouping

Returns:

  • (Arel::Nodes::Grouping)

Raises:

  • (TaxonWorks::Error)


301
302
303
304
305
306
307
308
309
# File 'lib/queries/query.rb', line 301

def combine_or_clauses(clauses)
  clauses.compact!
  raise TaxonWorks::Error, 'combine_or_clauses called without a clause, ensure at least one exists' unless !clauses.empty?
  a = clauses.shift
  clauses.each do |b|
    a = a.or(b)
  end
  a
end

#common_name_nameObject



374
375
376
# File 'lib/queries/query.rb', line 374

def common_name_name
  common_name_table[:name].eq(query_string)
end

#common_name_tableObject



370
371
372
# File 'lib/queries/query.rb', line 370

def common_name_table
  ::CommonName.arel_table
end

#common_name_wild_piecesObject



378
379
380
# File 'lib/queries/query.rb', line 378

def common_name_wild_pieces
  common_name_table[:name].matches(wildcard_pieces)
end

#end_wildcardString

Returns:

  • (String)


88
89
90
# File 'lib/queries/query.rb', line 88

def end_wildcard
  query_string + '%'
end

#exactly_namedArel::Nodes::Matches

Returns:

  • (Arel::Nodes::Matches)


244
245
246
# File 'lib/queries/query.rb', line 244

def exactly_named
  table[:name].eq(query_string) if !query_string.blank?
end

#fragmentsArray

Used in unordered AND searches

Returns:

  • (Array)

    if 1-5 alphanumeric_strings, those alphabetic_strings wrapped in wildcards, else none.



131
132
133
134
135
136
137
138
# File 'lib/queries/query.rb', line 131

def fragments
  a = alphanumeric_strings
  if a.size > 0 && a.size < 6
    a.collect{|a| "%#{a}%"}
  else
    []
  end
end

#integersArray

Returns of strings representing integers.

Returns:

  • (Array)

    of strings representing integers



119
120
121
# File 'lib/queries/query.rb', line 119

def integers
  Utilities::Strings.integers(query_string)
end

#levenshtein_distance(attribute, value) ⇒ Object



311
312
313
314
315
# File 'lib/queries/query.rb', line 311

def levenshtein_distance(attribute, value)
  value = "'" + value.gsub(/'/, "''") + "'"
  a = ApplicationRecord.sanitize_sql(value)
  Arel::Nodes::NamedFunction.new("levenshtein", [table[attribute], Arel::Nodes::SqlLiteral.new(a) ] )
end

#match_ordered_wildcard_pieces_in_cachedArel::Nodes::Matches

Returns:

  • (Arel::Nodes::Matches)


282
283
284
# File 'lib/queries/query.rb', line 282

def match_ordered_wildcard_pieces_in_cached
  table[:cached].matches(wildcard_pieces)
end

#match_wildcard_end_in_cachedArel::Nodes::Matches

match ALL wildcards, but unordered, if 2 - 6 pieces provided

Returns:

  • (Arel::Nodes::Matches)


288
289
290
# File 'lib/queries/query.rb', line 288

def match_wildcard_end_in_cached
  table[:cached].matches(end_wildcard)
end

#match_wildcard_in_cachedArel::Nodes::Matches

match ALL wildcards, but unordered, if 2 - 6 pieces provided

Returns:

  • (Arel::Nodes::Matches)


294
295
296
297
298
# File 'lib/queries/query.rb', line 294

def match_wildcard_in_cached
  b = fragments
  return nil if b.empty?
  table[:cached].matches_all(b)
end

#namedArel::Nodes::Matches

Returns:

  • (Arel::Nodes::Matches)


239
240
241
# File 'lib/queries/query.rb', line 239

def named
  table[:name].matches_any(terms) if terms.any?
end

#no_terms?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/queries/query.rb', line 71

def no_terms?
  terms.none?
end

#only_idsQuery?

Returns used in or_clauses, match on id only if integers alone provided.

Returns:

  • (Query, nil)

    used in or_clauses, match on id only if integers alone provided.



230
231
232
233
234
235
236
# File 'lib/queries/query.rb', line 230

def only_ids
  if only_integers?
    with_id
  else
    nil
  end
end

#only_integers?Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/queries/query.rb', line 124

def only_integers?
  Utilities::Strings.only_integers?(query_string)
end

#parentArel::Nodes::TableAlias

Returns:

  • (Arel::Nodes::TableAlias)


249
250
251
# File 'lib/queries/query.rb', line 249

def parent
  table.alias
end

#parent_child_joinScope

Returns:

  • (Scope)


206
207
208
# File 'lib/queries/query.rb', line 206

def parent_child_join
  table.join(parent).on(table[:parent_id].eq(parent[:id])).join_sources # !! join_sources ftw
end

#parent_child_whereArel::Nodes::Grouping

Match at two levels, for example, 'wa te“ will match ”Washington Co., Texas“

Returns:

  • (Arel::Nodes::Grouping)


212
213
214
215
216
# File 'lib/queries/query.rb', line 212

def parent_child_where
  a,b = query_string.split(/\s+/, 2)
  return table[:id].eq(-1) if a.nil? || b.nil?
  table[:name].matches("#{a}%").and(parent[:name].matches("#{b}%"))
end

#piecesArray

TODO: used?!

Returns:

  • (Array)

    split on whitespace



155
156
157
# File 'lib/queries/query.rb', line 155

def pieces
  query_string.split(/\s+/)
end

#resultArray

TODO: deprecate? probably unused

Returns:

  • (Array)

    the results of the query as ActiveRecord objects



45
46
47
# File 'lib/queries/query.rb', line 45

def result
  []
end

#scopeScope

stub TODO: deprecate? probably unused

Returns:

  • (Scope)


52
53
54
# File 'lib/queries/query.rb', line 52

def scope
  where('1 = 2')
end

#start_and_end_wildcardString

Returns:

  • (String)


93
94
95
# File 'lib/queries/query.rb', line 93

def start_and_end_wildcard
  '%' + query_string + '%'
end

#start_wildcardString

Returns:

  • (String)


83
84
85
# File 'lib/queries/query.rb', line 83

def start_wildcard
  '%' + query_string
end

#string_fragmentsArray

Used in unordered AND searches

Returns:

  • (Array)

    if 1-5 alphabetic_strings, those alphabetic_strings wrapped in wildcards, else none.



143
144
145
146
147
148
149
150
# File 'lib/queries/query.rb', line 143

def string_fragments
  a = alphabetic_strings
  if a.size > 0 && a.size < 6
    a.collect{|a| "%#{a}%"}
  else
    []
  end
end

#wildcard_piecesString

Returns if `foo, and 123 and stuff` then %foo%and%123%and%stuff%.

Returns:

  • (String)

    if `foo, and 123 and stuff` then %foo%and%123%and%stuff%



171
172
173
174
175
# File 'lib/queries/query.rb', line 171

def wildcard_pieces
  a = '%' + query_string.gsub(/[^[[:word:]]]+/, '%') + '%' ### DD: if query_string is cyrilic or diacritics, it returns '%%%'
  a = 'NothingToMatch' if a.gsub('%','').gsub(' ', '').blank?
  a
end

#wildcard_wrapped_integersArray

Returns:

  • (Array)


160
161
162
# File 'lib/queries/query.rb', line 160

def wildcard_wrapped_integers
  integers.collect{|i| "%#{i}%"}
end

#wildcard_wrapped_yearsArray

Returns:

  • (Array)


165
166
167
# File 'lib/queries/query.rb', line 165

def wildcard_wrapped_years
  years.collect{|i| "%#{i}%"}
end

#with_cachedArel::Nodes::Matches

Returns:

  • (Arel::Nodes::Matches)


272
273
274
# File 'lib/queries/query.rb', line 272

def with_cached
  table[:cached].eq(query_string)
end

#with_cached_likeArel::Nodes::Matches

Returns:

  • (Arel::Nodes::Matches)


277
278
279
# File 'lib/queries/query.rb', line 277

def with_cached_like
  table[:cached].matches(start_and_end_wildcard)
end

#with_idQuery?

Returns used in or_clauses.

Returns:

  • (Query, nil)

    used in or_clauses



220
221
222
223
224
225
226
# File 'lib/queries/query.rb', line 220

def with_id
  if integers.any?
    table[:id].eq_any(integers)
  else
    nil
  end
end

#with_project_idArel::Nodes::Equality

TODO: nil/or clause this

Returns:

  • (Arel::Nodes::Equality)


255
256
257
258
259
260
261
# File 'lib/queries/query.rb', line 255

def with_project_id
  if project_id
    table[:project_id].eq(project_id)
  else
    nil
  end
end

#year_letterString?

Returns:

  • (String, nil)


113
114
115
# File 'lib/queries/query.rb', line 113

def year_letter
  Utilities::Strings.year_letter(query_string)
end

#yearsArray

Returns:

  • (Array)


108
109
110
# File 'lib/queries/query.rb', line 108

def years
  Utilities::Strings.years(query_string)
end