Class: Queries::CollectingEvent::Filter

Inherits:
Object
  • Object
show all
Includes:
Queries::Concerns::DateRanges, Queries::Concerns::Tags
Defined in:
lib/queries/collecting_event/filter.rb

Constant Summary collapse

ATTRIBUTES =

TODO: likely move to model (replicated in Source too) Params exists for all CollectingEvent attributes except these

(::CollectingEvent.column_names - %w{project_id created_by_id updated_by_id created_at updated_at})
PARAMS =
%w{collector_ids
  collector_ids_or
  spatial_geographic_areas
  wkt
  geographic_area_ids
  start_date
  end_date
  radius
  partial_overlap_dates
  md5_verbatim_label
  in_verbatim_locality
  in_labels
  geo_json
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params) ⇒ Filter

Returns a new instance of Filter.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/queries/collecting_event/filter.rb', line 86

def initialize(params)
  # @spatial_geographic_area_ids = params[:spatial_geographic_areas].blank? ? [] : params[:spatial_geographic_area_ids]

  @collecting_event_wildcards = params[:collecting_event_wildcards] || []
  @collector_ids = params[:collector_ids].blank? ? [] : params[:collector_ids]
  @collector_ids_or = (params[:collector_ids_or]&.downcase == 'true' ? true : false) if !params[:collector_ids_or].nil?
  @geo_json = params[:geo_json]
  @geographic_area_ids = params[:geographic_area_ids].blank? ? [] : params[:geographic_area_ids]
  @in_labels = params[:in_labels]
  @in_verbatim_locality = params[:in_verbatim_locality]
  @md5_verbatim_label = (params[:md5_verbatim_label]&.downcase == 'true' ? true : false) if !params[:md5_verbatim_label].nil?
  @otu_ids = params[:otu_ids].blank? ? [] : params[:otu_ids]
  @radius = params[:radius].blank? ? 100 : params[:radius]
  @recent = params[:recent].blank? ? nil : params[:recent].to_i
  @spatial_geographic_areas = (params[:spatial_geographic_areas]&.downcase == 'true' ? true : false) if !params[:spatial_geographic_areas].nil?
  @wkt = params[:wkt]

  set_tags_params(params)
  set_attributes(params)
  set_dates(params)
end

Instance Attribute Details

#collecting_event_wildcardsArray

Returns values are ATTRIBUTES that should be wildcarded.

Returns:

  • (Array)

    values are ATTRIBUTES that should be wildcarded



70
71
72
# File 'lib/queries/collecting_event/filter.rb', line 70

def collecting_event_wildcards
  @collecting_event_wildcards
end

#collector_idsArray

TODO: singularize and handle array or single

Returns:

  • (Array)


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

def collector_ids
  @collector_ids
end

#collector_ids_orBoolean

Parameters:

  • collector_ids_or (String)

    'true' - all ids treated as “or” 'false', nil - all ids treated as “and”

Returns:

  • (Boolean)


84
85
86
# File 'lib/queries/collecting_event/filter.rb', line 84

def collector_ids_or
  @collector_ids_or
end

#geo_jsonHash?

Returns in geo_json format (no Feature …) ?!.

Returns:

  • (Hash, nil)

    in geo_json format (no Feature …) ?!



56
57
58
# File 'lib/queries/collecting_event/filter.rb', line 56

def geo_json
  @geo_json
end

#geographic_area_idsArray

Returns match only CollectionObjects mapped to CollectingEvents that have these specific ids. No spatial calculations are included in this parameter by default. See 'spatial_geographic_areas = true'.

Returns:

  • (Array)

    match only CollectionObjects mapped to CollectingEvents that have these specific ids. No spatial calculations are included in this parameter by default. See 'spatial_geographic_areas = true'.



66
67
68
# File 'lib/queries/collecting_event/filter.rb', line 66

def geographic_area_ids
  @geographic_area_ids
end

#in_labelsObject

Wildcard wrapped matching any label



35
36
37
# File 'lib/queries/collecting_event/filter.rb', line 35

def in_labels
  @in_labels
end

#in_verbatim_localityObject

TODO: remove for exact/array Wildcard wrapped matching verbatim_locality via ATTRIBUTES



42
43
44
# File 'lib/queries/collecting_event/filter.rb', line 42

def in_verbatim_locality
  @in_verbatim_locality
end

#md5_verbatim_labelObject

If true then in_labels checks only the MD5



38
39
40
# File 'lib/queries/collecting_event/filter.rb', line 38

def md5_verbatim_label
  @md5_verbatim_label
end

#otu_idsArray

TODO: singularize and handle array or single

Returns:

  • (Array)


74
75
76
# File 'lib/queries/collecting_event/filter.rb', line 74

def otu_ids
  @otu_ids
end

#radiusObject

Integer in Meters



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

def radius
  @radius
end

#recentTrue?

TODO: reffactor to Concern or remove (likely doesn't belong here)

Returns:

  • (True, nil)


46
47
48
# File 'lib/queries/collecting_event/filter.rb', line 46

def recent
  @recent
end

#spatial_geographic_areasTrue?

Reference geographic areas to do a spatial query

Returns:

  • (True, nil)


60
61
62
# File 'lib/queries/collecting_event/filter.rb', line 60

def spatial_geographic_areas
  @spatial_geographic_areas
end

#wktObject

A spatial representation in well known text



49
50
51
# File 'lib/queries/collecting_event/filter.rb', line 49

def wkt
  @wkt
end

Instance Method Details

#allActiveRecord::Relation

Returns:

  • (ActiveRecord::Relation)


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/queries/collecting_event/filter.rb', line 293

def all
  a = and_clauses
  b = merge_clauses

  q = nil
  if a && b
    q = b.where(a).distinct
  elsif a
    q = ::CollectingEvent.where(a).distinct
  elsif b
    q = b.distinct
  else
    q = ::CollectingEvent.includes(:identifiers, :roles, :pinboard_items, :geographic_area, georeferences: [:geographic_item, :error_geographic_item]).all
  end

  q = q.order(updated_at: :desc).limit(recent) if recent
  q
end

#and_clausesActiveRecord::Relation

Returns:

  • (ActiveRecord::Relation)


270
271
272
273
274
275
276
277
278
279
# File 'lib/queries/collecting_event/filter.rb', line 270

def and_clauses
  clauses = base_and_clauses
  return nil if clauses.empty?

  a = clauses.shift
  clauses.each do |b|
    a = a.and(b)
  end
  a
end

#attribute_clausesObject



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/queries/collecting_event/filter.rb', line 123

def attribute_clauses
  c = []
  ATTRIBUTES.each do |a|
    if v = send(a)
      if !v.blank?
        if collecting_event_wildcards.include?(a)
          c.push table[a.to_sym].matches('%' + v.to_s + '%')
        else
          c.push table[a.to_sym].eq(v)
        end
      end
    end
  end
  c
end

#base_and_clausesArray

Returns:

  • (Array)


242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/queries/collecting_event/filter.rb', line 242

def base_and_clauses
  clauses = []
  clauses += attribute_clauses

  clauses += [
    between_date_range,
    matching_geographic_area_ids,
    matching_verbatim_label_md5,
    matching_any_label,
    matching_verbatim_locality,
  ].compact!

  clauses
end

#base_merge_clausesObject



257
258
259
260
261
262
263
264
265
266
267
# File 'lib/queries/collecting_event/filter.rb', line 257

def base_merge_clauses
  clauses = [
    keyword_id_facet,
    matching_otu_ids,
    wkt_facet,
    geo_json_facet,
    collector_ids_facet,
    matching_spatial_via_geographic_area_ids
  ].compact!
  clauses
end

#base_queryObject



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

def base_query
  ::CollectingEvent.select('collecting_events.*')
end

#collector_ids_facetObject

TODO: dry with Source, TaxonName, etc.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/queries/collecting_event/filter.rb', line 140

def collector_ids_facet
  return nil if collector_ids.empty?
  o = table
  r = ::Role.arel_table

  a = o.alias("a_")
  b = o.project(a[Arel.star]).from(a)

  c = r.alias('r1')

  b = b.join(c, Arel::Nodes::OuterJoin)
    .on(
      a[:id].eq(c[:role_object_id])
    .and(c[:role_object_type].eq('CollectingEvent'))
    .and(c[:type].eq('Collector'))
  )

  e = c[:id].not_eq(nil)
  f = c[:person_id].eq_any(collector_ids)

  b = b.where(e.and(f))
  b = b.group(a['id'])
  b = b.having(a['id'].count.eq(collector_ids.length)) unless collector_ids_or
  b = b.as('col_z_')

  ::CollectingEvent.joins(Arel::Nodes::InnerJoin.new(b, Arel::Nodes::On.new(b['id'].eq(o['id']))))
end

#geo_json_facetObject

Shape is a Hash in GeoJSON format



184
185
186
187
188
# File 'lib/queries/collecting_event/filter.rb', line 184

def geo_json_facet
  return nil if geo_json.nil?
  a = RGeo::GeoJSON.decode(geo_json)
  spatial_query(a.geometry_type.to_s, a.to_s)
end

#matching_any_labelObject



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

def matching_any_label
  return nil if in_labels.blank?
  t = "%#{in_labels}%"
  table[:verbatim_label].matches(t).or(table[:print_label].matches(t)).or(table[:document_label].matches(t))
end

#matching_geographic_area_idsObject



225
226
227
228
# File 'lib/queries/collecting_event/filter.rb', line 225

def matching_geographic_area_ids
  return nil if geographic_area_ids.empty? || spatial_geographic_areas
  table[:geographic_area_id].eq_any(geographic_area_ids)
end

#matching_otu_idsObject



230
231
232
233
# File 'lib/queries/collecting_event/filter.rb', line 230

def matching_otu_ids
  return nil if otu_ids.empty?
  ::CollectingEvent.joins(:otus).where(otus: {id: otu_ids}) #  table[:geographic_area_id].eq_any(geographic_area_ids)
end

#matching_spatial_via_geographic_area_idsObject

TODO: throttle by size?



206
207
208
209
210
# File 'lib/queries/collecting_event/filter.rb', line 206

def matching_spatial_via_geographic_area_ids
    return nil unless spatial_geographic_areas && !geographic_area_ids.empty?
    a = ::GeographicItem.default_by_geographic_area_ids(geographic_area_ids).ids
  ::CollectingEvent.joins(:geographic_items).where( ::GeographicItem.contained_by_where_sql( a ) )
end

#matching_verbatim_label_md5Object



218
219
220
221
222
223
# File 'lib/queries/collecting_event/filter.rb', line 218

def matching_verbatim_label_md5
  return nil unless md5_verbatim_label && !in_labels.blank?
  md5 = ::Utilities::Strings.generate_md5(in_labels)

  table[:md5_of_verbatim_label].eq(md5)
end

#matching_verbatim_localityObject



235
236
237
238
239
# File 'lib/queries/collecting_event/filter.rb', line 235

def matching_verbatim_locality
  return nil if in_verbatim_locality.blank?
  t = "%#{in_verbatim_locality}%"
  table[:verbatim_locality].matches(t)
end

#merge_clausesObject



281
282
283
284
285
286
287
288
289
290
# File 'lib/queries/collecting_event/filter.rb', line 281

def merge_clauses
  clauses = base_merge_clauses
  return nil if clauses.empty?

  a = clauses.shift
  clauses.each do |b|
    a = a.merge(b)
  end
  a
end

#set_attributes(params) ⇒ Object



108
109
110
111
112
# File 'lib/queries/collecting_event/filter.rb', line 108

def set_attributes(params)
  ATTRIBUTES.each do |a|
    send("#{a}=", params[a.to_sym])
  end
end

#spatial_query(geometry_type, wkt) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/queries/collecting_event/filter.rb', line 190

def spatial_query(geometry_type, wkt)
  case geometry_type
  when 'Point'
    ::CollectingEvent
      .joins(:geographic_items)
      .where(::GeographicItem.within_radius_of_wkt_sql(wkt, radius ))
  when 'Polygon', 'MultiPolygon'
    ::CollectingEvent
      .joins(:geographic_items)
      .where(::GeographicItem.contained_by_wkt_sql(wkt))
  else
    nil
  end
end

#tableArel::Table

Returns:

  • (Arel::Table)


115
116
117
# File 'lib/queries/collecting_event/filter.rb', line 115

def table
  ::CollectingEvent.arel_table
end

#wkt_facetObject

TODO: what is it @param value [String] ?! In def shape=(value)

@shape = ::RGeo::GeoJSON.decode(value, json_parser: :json)
@shape

end



176
177
178
179
180
181
# File 'lib/queries/collecting_event/filter.rb', line 176

def wkt_facet
  return nil if wkt.blank?
  a = RGeo::WKRep::WKTParser.new
  b = a.parse(wkt)
  spatial_query(b.geometry_type.to_s, wkt)
end