Class: Queries::Person::Filter
- Inherits:
-
Query::Filter
show all
- Includes:
- Concerns::DataAttributes, Concerns::Notes, Concerns::Tags
- Defined in:
- lib/queries/person/filter.rb
Constant Summary
collapse
- PARAMS =
[
:active_after_year,
:active_before_year,
:born_after_year,
:born_before_year,
:died_after_year,
:died_before_year,
:except_project_id,
:first_name,
:first_name_like,
:last_name,
:last_name_like,
:last_name_starts_with,
:levenshtein_cuttoff,
:name,
:only_project_id,
:person_id,
:prefix,
:regex, :repeated_total,
:suffix,
:use_max,
:use_min,
exact: [],
except_project_id: [],
except_role: [],
only_project_id: [],
person_id: [],
role: [],
with: [],
without: [],
].freeze
Query::Filter::FILTER_QUERIES, Query::Filter::SUBQUERIES
Instance Attribute Summary collapse
#anatomical_part_query, #api, #asserted_distribution_query, #biological_association_query, #biological_associations_graph_query, #collecting_event_query, #collection_object_query, #content_query, #controlled_vocabulary_term_query, #conveyance_query, #data_attribute_query, #depiction_query, #descriptor_query, #document_query, #dwc_occurrence_query, #extract_query, #field_occurrence_query, #image_query, #loan_query, #object_global_id, #observation_query, #order_by, #otu_query, #page, #paginate, #params, #per, #person_query, #project_id, #recent, #recent_target, #roll_call, #sound_query, #taxon_name_query, #taxon_name_relationship_query, #venn, #venn_ignore_pagination, #venn_mode
Attributes inherited from Query
#query_string, #terms
Class Method Summary
collapse
Instance Method Summary
collapse
#all, #all_and_clauses, #all_merge_clauses, #annotator_and_clauses, #annotator_merge_clauses, annotator_params, api_excluded_params, #apply_venn, #attribute_exact_facet, base_filter, base_query_name, base_query_to_h, #deep_permit, #disable_paging, included_annotator_facets, instantiated_base_filter, inverted_subqueries, #model_id_facet, #object_global_id_facet, #only_project?, #only_project_or_less?, #paging_state, params, #permitted_params, #process_url_into_params, query_name, #set_nested_queries, #set_paging, set_paging, #shared_and_clauses, #subquery_vector, #target_and_clauses, #venn_query
Methods inherited from Query
#alphabetic_strings, #alphanumeric_strings, base_name, #base_name, #base_query, #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(query_params = {}) ⇒ Filter
Returns a new instance of Filter.
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
|
# File 'lib/queries/person/filter.rb', line 164
def initialize(query_params = {})
super
@active_after_year = params[:active_after_year]
@active_before_year = params[:active_before_year]
@born_after_year = params[:born_after_year]
@born_before_year = params[:born_before_year]
@died_after_year = params[:died_after_year]
@died_before_year = params[:died_before_year]
@exact = params[:exact]
@except_project_id = params[:except_project_id]
@except_role = params[:except_role]
@first_name = params[:first_name]
@first_name_like = params[:first_name_like]
@last_name = params[:last_name]
@last_name_like = params[:last_name_like]
@last_name_starts_with = params[:last_name_starts_with]
@levenshtein_cuttoff = params[:levenshtein_cuttoff]
@name = params[:name]
@only_project_id = params[:only_project_id]
@person_id = params[:person_id]
@regex = params[:regex]
@repeated_total = params[:repeated_total]
@role = params[:role]
@use_max = params[:use_max]
@use_min = params[:use_min]
@with = params[:with]
@without = params[:without]
set_tags_params(params)
set_data_attributes_params(params)
set_notes_params(params)
end
|
Instance Attribute Details
#active_after_year ⇒ String?
47
48
49
|
# File 'lib/queries/person/filter.rb', line 47
def active_after_year
@active_after_year
end
|
#active_before_year ⇒ String?
50
51
52
|
# File 'lib/queries/person/filter.rb', line 50
def active_before_year
@active_before_year
end
|
#born_after_year ⇒ String?
53
54
55
|
# File 'lib/queries/person/filter.rb', line 53
def born_after_year
@born_after_year
end
|
#born_before_year ⇒ String?
56
57
58
|
# File 'lib/queries/person/filter.rb', line 56
def born_before_year
@born_before_year
end
|
#died_after_year ⇒ String?
59
60
61
|
# File 'lib/queries/person/filter.rb', line 59
def died_after_year
@died_after_year
end
|
#died_before_year ⇒ String?
62
63
64
|
# File 'lib/queries/person/filter.rb', line 62
def died_before_year
@died_before_year
end
|
#exact ⇒ Array
Returns values are attributes that should be wildcarded:
last_name, first_name, suffix, prefix, name
When name then matches cached.
76
77
78
|
# File 'lib/queries/person/filter.rb', line 76
def exact
@exact
end
|
#except_project_id ⇒ Array
Returns only return people with roles in this project(s) or roles through Sources in ProjectSources.
66
67
68
|
# File 'lib/queries/person/filter.rb', line 66
def except_project_id
@except_project_id
end
|
#except_role ⇒ Array
Returns Exclude all People linked by this Role.
82
83
84
|
# File 'lib/queries/person/filter.rb', line 82
def except_role
@except_role
end
|
#first_name ⇒ String?
Returns also matches any AlternateValue.
86
87
88
|
# File 'lib/queries/person/filter.rb', line 86
def first_name
@first_name
end
|
#first_name_like ⇒ String?
Returns Matches first_name using initial-expansion and part-sequence comparison.
Each dot-or-space-delimited part of the input is matched positionally:
a single-character part (initial) matches any word starting with that letter;
a multi-character part matches exactly or as a bare initial.
E.g. 'J.' matches 'John'; 'John K.' matches 'J. K.' but not 'Jack K.'.
Also matches any AlternateValue.
95
96
97
|
# File 'lib/queries/person/filter.rb', line 95
def first_name_like
@first_name_like
end
|
#last_name ⇒ String?
Returns also matches any AlternateValue.
99
100
101
|
# File 'lib/queries/person/filter.rb', line 99
def last_name
@last_name
end
|
#last_name_like ⇒ String?
Returns Matches last_name with word-level subset logic to handle maiden name variations.
Forward: every word of the input appears as a whole word in the stored value,
e.g. 'Smith' matches 'Smith Jones'.
Backward: the stored last_name appears as a whole word in the input,
e.g. input 'Smith Jones' matches stored 'Smith'.
Also matches any AlternateValue.
108
109
110
|
# File 'lib/queries/person/filter.rb', line 108
def last_name_like
@last_name_like
end
|
#last_name_starts_with ⇒ Object
Returns the value of attribute last_name_starts_with.
110
111
112
|
# File 'lib/queries/person/filter.rb', line 110
def last_name_starts_with
@last_name_starts_with
end
|
#levenshtein_cuttoff ⇒ Integer?
Returns matches cached, records less than this edit distance are returned
!! requires name or is ignored.
115
116
117
|
# File 'lib/queries/person/filter.rb', line 115
def levenshtein_cuttoff
@levenshtein_cuttoff
end
|
#name ⇒ String?
Returns where against cached.
119
120
121
|
# File 'lib/queries/person/filter.rb', line 119
def name
@name
end
|
#only_project_id ⇒ Array
Returns only return people with roles in this project(s) or roles through Sources in ProjectSources.
70
71
72
|
# File 'lib/queries/person/filter.rb', line 70
def only_project_id
@only_project_id
end
|
#person_id ⇒ Array
44
45
46
|
# File 'lib/queries/person/filter.rb', line 44
def person_id
@person_id
end
|
#prefix ⇒ String?
Returns also matches any AlternateValue.
123
124
125
|
# File 'lib/queries/person/filter.rb', line 123
def prefix
@prefix
end
|
#regex ⇒ String?
Returns a regular expression, Postgres compatible, matches against cached.
131
132
133
|
# File 'lib/queries/person/filter.rb', line 131
def regex
@regex
end
|
#repeated_total ⇒ String?
Returns the number of times this name must be an identical match
must be 2 or higher or will be ignored.
136
137
138
|
# File 'lib/queries/person/filter.rb', line 136
def repeated_total
@repeated_total
end
|
#role ⇒ Array
141
142
143
|
# File 'lib/queries/person/filter.rb', line 141
def role
@role
end
|
#suffix ⇒ String?
Returns also matches any AlternateValue.
127
128
129
|
# File 'lib/queries/person/filter.rb', line 127
def suffix
@suffix
end
|
#use_max ⇒ String?
Returns the maximum number of roles the Person must be in, further scoped to only counting role when provided.
145
146
147
|
# File 'lib/queries/person/filter.rb', line 145
def use_max
@use_max
end
|
#use_min ⇒ String?
Returns the minimum number of roles the Person must be in, further scoped to only counting role when provided.
149
150
151
|
# File 'lib/queries/person/filter.rb', line 149
def use_min
@use_min
end
|
#with ⇒ Array
155
156
157
|
# File 'lib/queries/person/filter.rb', line 155
def with
@with
end
|
#without ⇒ Array
161
162
163
|
# File 'lib/queries/person/filter.rb', line 161
def without
@without
end
|
Class Method Details
.api_except_params ⇒ Object
198
199
200
|
# File 'lib/queries/person/filter.rb', line 198
def self.api_except_params
[:regex]
end
|
Instance Method Details
#active_after_year_facet ⇒ Object
259
260
261
262
263
|
# File 'lib/queries/person/filter.rb', line 259
def active_after_year_facet
return nil if active_after_year.nil?
table[:year_active_start].gt(active_after_year)
.or(table[:year_active_end].gt(active_after_year))
end
|
#active_before_year_facet ⇒ Object
265
266
267
268
269
|
# File 'lib/queries/person/filter.rb', line 265
def active_before_year_facet
return nil if active_before_year.nil?
table[:year_active_start].lt(active_before_year)
.or(table[:year_active_end].lt(active_before_year))
end
|
#and_clauses ⇒ Object
504
505
506
507
508
509
510
511
512
513
514
515
516
517
|
# File 'lib/queries/person/filter.rb', line 504
def and_clauses
clauses = [
active_after_year_facet,
active_before_year_facet,
born_after_year_facet,
born_before_year_facet,
died_after_year_facet,
died_before_year_facet,
last_name_starts_with_facet,
name_facet,
with_facet,
without_facet,
]
end
|
#born_after_year_facet ⇒ Object
239
240
241
242
|
# File 'lib/queries/person/filter.rb', line 239
def born_after_year_facet
return nil if born_after_year.nil?
table[:year_born].gt(born_after_year)
end
|
#born_before_year_facet ⇒ Object
244
245
246
247
|
# File 'lib/queries/person/filter.rb', line 244
def born_before_year_facet
return nil if born_before_year.nil?
table[:year_born].lt(born_before_year)
end
|
#build_first_name_like_pattern(input) ⇒ Object
Builds a PostgreSQL regex pattern for positional initial-expansion matching.
Each part of the input is matched in sequence:
single char -> matches any word starting with that letter
(e.g. 'j' -> 'j\w*\.?')
multiple chars -> matches exactly OR as a bare initial
(e.g. 'john' -> '(?:john|j\.?)')
Parts are joined by a flexible whitespace/period separator.
Trailing name parts (e.g. middle names absent from input) are allowed
via '(\s.*)?$'.
When all input parts are full names (no initials), each part after the
first is optional, so e.g. 'John Stuart' also matches stored
'J.' or 'J. S.'.
When any input part is an initial, all parts are required (the caller is
being explicit).
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
# File 'lib/queries/person/filter.rb', line 322
def build_first_name_like_pattern(input)
parts = input.downcase.gsub(/[.\-]/, ' ').split.reject(&:empty?)
return nil if parts.empty?
regex_parts = parts.map do |part|
if part.length == 1
"#{::Regexp.escape(part)}\\w*\\.?"
else
"(?:#{::Regexp.escape(part)}|#{::Regexp.escape(part[0])}\\.?)"
end
end
if parts.length > 1 && parts.all? { |p| p.length > 1 }
tail = "([\\s.\\-]*)?"
regex_parts.reverse.each_with_index do |rp, i|
tail = "#{rp}#{tail}"
tail = "([\\s.\\-]*#{tail})?" unless i == regex_parts.length - 1
end
"^#{tail}$"
else
"^#{regex_parts.join('[\\s.\\-]+')}([\\s.\\-].*)?$"
end
end
|
#died_after_year_facet ⇒ Object
249
250
251
252
|
# File 'lib/queries/person/filter.rb', line 249
def died_after_year_facet
return nil if died_after_year.nil?
table[:year_died].gt(died_after_year)
end
|
#died_before_year_facet ⇒ Object
254
255
256
257
|
# File 'lib/queries/person/filter.rb', line 254
def died_before_year_facet
return nil if died_before_year.nil?
table[:year_died].lt(died_before_year)
end
|
#except_project_id_facet ⇒ Object
487
488
489
490
491
492
493
494
495
496
497
|
# File 'lib/queries/person/filter.rb', line 487
def except_project_id_facet
return nil if except_project_id.empty?
w1 = role_table[:project_id].not_in(except_project_id)
w2 = ::ProjectSource.arel_table[:project_id].not_in(except_project_id)
a = ::Person.joins(:roles).where(w1.to_sql)
b = ::Person.joins(sources: [:project_sources]).where( w2.to_sql)
referenced_klass_union([a,b])
end
|
#except_role_facet ⇒ Object
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
|
# File 'lib/queries/person/filter.rb', line 404
def except_role_facet
return nil if except_role.empty?
::Person.joins(
table.join(role_table, Arel::Nodes::OuterJoin).on(
table[:id].eq(role_table[:person_id]).and(role_table[:type]).in(except_role)
).join_sources
).merge(
::Role.where(id: nil)
).distinct
end
|
#first_name_like_facet ⇒ Object
352
353
354
355
356
357
358
359
360
361
362
363
364
|
# File 'lib/queries/person/filter.rb', line 352
def first_name_like_facet
return nil if first_name_like.blank?
pattern = build_first_name_like_pattern(first_name_like)
return nil if pattern.nil?
::Person.left_outer_joins(:alternate_values)
.where(
"people.first_name ~* ? OR (alternate_values.alternate_value_object_attribute = 'first_name' AND alternate_values.value ~* ?)",
pattern, pattern
)
.distinct
end
|
#last_name_like_facet ⇒ Object
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
392
|
# File 'lib/queries/person/filter.rb', line 366
def last_name_like_facet
return nil if last_name_like.blank?
words = last_name_like.downcase.gsub('-', ' ').strip.split(/\s+/).reject(&:empty?)
return nil if words.empty?
forward_clauses = words.map { "(people.last_name ~* ? OR (alternate_values.alternate_value_object_attribute = 'last_name' AND alternate_values.value ~* ?))" }
forward_values = words.flat_map { |w|
pat = "\\m#{::Regexp.escape(w)}\\M"
[pat, pat]
}
q = ::Person.left_outer_joins(:alternate_values)
forward = q.where(forward_clauses.join(' AND '), *forward_values)
return forward.distinct if words.length == 1
backward_clause = "LOWER(people.last_name) = ANY(ARRAY[#{words.map { '?' }.join(',')}])"
forward.or(q.where(backward_clause, *words)).distinct
end
|
#last_name_starts_with_facet ⇒ Object
394
395
396
397
|
# File 'lib/queries/person/filter.rb', line 394
def last_name_starts_with_facet
return nil if last_name_starts_with.blank? || levenshtein_cuttoff.present?
table[:last_name].matches(last_name_starts_with + '%')
end
|
#levenshtein_facet ⇒ Object
471
472
473
474
475
476
|
# File 'lib/queries/person/filter.rb', line 471
def levenshtein_facet
return nil unless levenshtein_cuttoff && (name.present?)
::Person.where(
levenshtein_distance(:cached, name).lteq(levenshtein_cuttoff).to_sql
)
end
|
#merge_clauses ⇒ Object
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
|
# File 'lib/queries/person/filter.rb', line 519
def merge_clauses
[
except_project_id_facet,
except_role_facet,
first_name_like_facet,
last_name_like_facet,
levenshtein_facet,
name_part_facet(:first_name),
name_part_facet(:last_name),
name_part_facet(:prefix),
name_part_facet(:suffix),
only_project_id_facet,
regex_facet,
repeated_total_facet,
role_facet,
use_facet,
]
end
|
#name_facet ⇒ Object
299
300
301
302
303
304
305
306
|
# File 'lib/queries/person/filter.rb', line 299
def name_facet
return nil if name.nil? || levenshtein_cuttoff.present?
if exact.include?('name')
table[:cached].eq(name)
else
table[:cached].matches('%' + name + '%')
end
end
|
#name_part_facet(part = :last_name) ⇒ Object
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
|
# File 'lib/queries/person/filter.rb', line 271
def name_part_facet(part = :last_name)
v = send(part)
return nil if v.nil?
q = ::Person.left_outer_joins(:alternate_values)
a = ::AlternateValue.arel_table
w1, w2 = nil, nil
if exact.include?(part.to_s)
w = '%' + v + '%'
w1 = table[part].matches(w)
w2 = a[:value].matches(w)
else
w1 = table[part].eq(v)
w2 = a[:value].eq(v)
end
q.where( w1.or(w2).to_sql ).distinct
end
|
#only_project_id_facet ⇒ Object
478
479
480
481
482
483
484
485
|
# File 'lib/queries/person/filter.rb', line 478
def only_project_id_facet
return nil if only_project_id.empty?
a = ::Person.joins(:roles).where(roles: {project_id: only_project_id})
b = ::Person.joins(sources: [:project_sources]).where( project_sources: {project_id: only_project_id})
referenced_klass_union([a,b])
end
|
#project_id_facet ⇒ Object
Applies specificly to model, there is no such thing in Person
500
501
502
|
# File 'lib/queries/person/filter.rb', line 500
def project_id_facet
nil
end
|
#regex_facet ⇒ Object
294
295
296
297
|
# File 'lib/queries/person/filter.rb', line 294
def regex_facet
return nil if regex.blank?
::Person.where('cached ~* ?', regex)
end
|
#repeated_total_facet ⇒ Object
424
425
426
427
428
429
|
# File 'lib/queries/person/filter.rb', line 424
def repeated_total_facet
return nil if repeated_total.blank? || repeated_total.to_i < 2
::Person.where(
cached: ::Person.select(:cached).group(:cached).having('COUNT(cached) > ?', repeated_total)
)
end
|
#role_facet ⇒ Object
399
400
401
402
|
# File 'lib/queries/person/filter.rb', line 399
def role_facet
return nil if role.empty?
::Person.joins(:roles).where( role_table[:type].in(role) ).distinct
end
|
#role_table ⇒ Arel::Table
227
228
229
|
# File 'lib/queries/person/filter.rb', line 227
def role_table
::Role.arel_table
end
|
#use_facet ⇒ Object
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
|
# File 'lib/queries/person/filter.rb', line 431
def use_facet
return nil if (use_min.blank? && use_max.blank?)
min_max = [use_min&.to_i, use_max&.to_i ].compact
q = ::Person.joins(:roles)
.group('people.id, roles.person_id')
.having("COUNT(roles.person_id) >= #{min_max[0]}")
if !role.empty?
q = q.where(role_table[:type].in(role))
end
q = q.having("COUNT(roles.person_id) <= #{min_max[1]}") if min_max[1]
::Person.from('(' + q.to_sql + ') as people').distinct
end
|
#with_facet ⇒ Object
449
450
451
452
453
454
455
456
457
458
|
# File 'lib/queries/person/filter.rb', line 449
def with_facet
return nil if with.empty?
a = with.shift
q = table[a.to_sym].not_eq(nil)
with.each do |f|
q = q.and(table[f.to_sym].not_eq(nil))
end
q
end
|
#without_facet ⇒ Object
460
461
462
463
464
465
466
467
468
469
|
# File 'lib/queries/person/filter.rb', line 460
def without_facet
return nil if without.empty?
a = without.shift
q = table[a.to_sym].eq(nil)
without.each do |f|
q = q.and(table[f.to_sym].eq(nil))
end
q
end
|