Class: DwcOccurrence

Inherits:
ApplicationRecord show all
Includes:
Housekeeping
Defined in:
app/models/dwc_occurrence.rb

Overview

A Darwin Core Record for the Occurence core. Field generated from Ruby dwc-meta, which references the same spec that is used in the IPT, and the Dwc Assistant. Each record references a specific CollectionObject or AssertedDistribution.

Important: This is a cache/index, data here are periodically (regenerated) from multiple tables in TW.

TODO: The basisOfRecord CVTs are not super informative.

We know collection object is definitely 1:1 with PreservedSpecimen, however
AssertedDistribution could be HumanObservation (if source is person), or ... what? if
its a published record.  Seems we need a 'PublishedAssertation', just like we model the data.

DWC attributes are camelCase to facilitate matching dwcClass is a replacement for the Rails reserved 'Class'

All DC attributes (attributes not in DwcOccurrence::TW_ATTRIBUTES) in this table are namespaced to dc (“purl.org/dc/terms/”, “rs.tdwg.org/dwc/terms/”)

Constant Summary collapse

DC_NAMESPACE =
'http://rs.tdwg.org/dwc/terms/'.freeze
TW_ATTRIBUTES =

Not yet implemented, but likely needed ? :id

[
  :id,
  :project_id,
  :created_at,
  :updated_at,
  :created_by_id,
  :updated_by_id,
  :dwc_occurrence_object_type,
  :dwc_occurence_object_id
].freeze
HEADER_CONVERTERS =
{
  'dwcClass' => 'class',
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#occurrence_identifierObject

Returns the value of attribute occurrence_identifier.



58
59
60
# File 'app/models/dwc_occurrence.rb', line 58

def occurrence_identifier
  @occurrence_identifier
end

Class Method Details

.annotates?Boolean

Returns:

  • (Boolean)


61
62
63
# File 'app/models/dwc_occurrence.rb', line 61

def self.annotates?
  false 
end

.by_collection_object_filter(filter_scope: nil, project_id: nil) ⇒ Object

Return scopes by a collection object filter



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'app/models/dwc_occurrence.rb', line 76

def self.by_collection_object_filter(filter_scope: nil, project_id: nil)
  return DwcOccurrence.none if project_id.nil? || filter_scope.nil?

  c = ::CollectionObject.arel_table
  d = arel_table

  # TODO: hackish
  k = ::CollectionObject.select('coscope.id').from( '(' + filter_scope.to_sql + ') as coscope ' )

  a = self.collection_objects_join
    .where("dwc_occurrences.project_id = ?", project_id)
    .where(dwc_occurrence_object_id: k)
    .select(::DwcOccurrence.target_columns) # TODO !! Will have to change when AssertedDistribution and other types merge in
  a
end

.collection_objects_joinActiveRecord::Relation

TODO: will need similar join for AssertedDistribution, or any object that matches, consider moving to Shared

Returns:

  • (ActiveRecord::Relation)


68
69
70
71
72
73
# File 'app/models/dwc_occurrence.rb', line 68

def self.collection_objects_join
  a = arel_table
  b = ::CollectionObject.arel_table
  j = a.join(b).on(a[:dwc_occurrence_object_type].eq('CollectionObject').and(a[:dwc_occurrence_object_id].eq(b[:id])))
  joins(j.join_sources)
end

.computed_columnsScope

Returns the columns inferred to have data.

Returns:

  • (Scope)

    the columns inferred to have data



128
129
130
# File 'app/models/dwc_occurrence.rb', line 128

def self.computed_columns
  select(target_columns)
end

.empty_fieldsArray

Returns of column names as symbols that are blank in ALL projects (not just this one).

Returns:

  • (Array)

    of column names as symbols that are blank in ALL projects (not just this one)



94
95
96
97
98
99
100
101
102
103
104
105
# File 'app/models/dwc_occurrence.rb', line 94

def self.empty_fields
  empty_in_all_projects = ActiveRecord::Base.connection.execute("select attname
  from pg_stats
  where tablename = 'dwc_occurrences'
  and most_common_vals is null
  and most_common_freqs is null
  and histogram_bounds is null
  and correlation is null
  and null_frac = 1;").pluck('attname').map(&:to_sym)

  empty_in_all_projects #  - target_columns
end

.excluded_columnsArray

Returns of symbols.

Returns:

  • (Array)

    of symbols



122
123
124
# File 'app/models/dwc_occurrence.rb', line 122

def self.excluded_columns
  ::DwcOccurrence.columns.collect{|c| c.name.to_sym} - (self.target_columns - [:dwc_occurrence_object_id, :dwc_occurrence_object_type])
end

.target_columnsArray

!! TODO: When we come to adding AssertedDistributions, FieldOccurrnces, etc. we will have to make this more flexible

Returns:

  • (Array)

    of symbols



111
112
113
114
115
116
117
118
# File 'app/models/dwc_occurrence.rb', line 111

def self.target_columns
  [:id, # must be in position 0
   :occurrenceID,
   :basisOfRecord,
   :dwc_occurrence_object_id,   # !! We don't want this, but need it in joins, it is removed in trim via `.excluded_columns` below
   :dwc_occurrence_object_type, # !! ^
  ] + CollectionObject::DwcExtensions::DWC_OCCURRENCE_MAP.keys
end

Instance Method Details

#basisObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/models/dwc_occurrence.rb', line 132

def basis
  case dwc_occurrence_object_type
  when 'CollectionObject'
    if dwc_occurrence_object.is_fossil?
      return 'FossilSpecimen'
    else
      return 'PreservedSpecimen'
    end
  when 'AssertedDistribution'
    # Used to fork b/b Source::Human and Source::Bibtex:
    case dwc_occurrence_object.source&.type || dwc_occurrence_object.sources.order(cached_nomenclature_date: :DESC).first.type
    when 'Source::Bibtex'
      return 'MaterialCitation'
    when 'Source::Human'  
      return 'HumanObservation'
    else # Not recommended at this point
      return 'Occurrence'
    end
  end
  'Undefined'
end

#create_object_uuidObject (protected)



197
198
199
200
201
202
203
# File 'app/models/dwc_occurrence.rb', line 197

def create_object_uuid
  @occurrence_identifier = Identifier::Global::Uuid::TaxonworksDwcOccurrence.create!(
    identifier_object: dwc_occurrence_object,
    by: dwc_occurrence_object&.creator, # revisit, why required?
    project_id: dwc_occurrence_object&.project_id, # Current.project_id,  # revisit, why required?
    is_generated: true)
end

#generate_uuid_if_required(force = false) ⇒ Object

TODO: quick check if occurrenceID exists in table?! <-> locking sync !?

Parameters:

  • force (Boolean) (defaults to: false)

    true - only create identifier if identifier exists false - check if occurrenceID is present, if it is, assume identifier (still) exists



166
167
168
169
170
171
172
173
174
# File 'app/models/dwc_occurrence.rb', line 166

def generate_uuid_if_required(force = false)
  if force # really make sure there is an object to work with
    create_object_uuid if !occurrence_identifier && !dwc_occurrence_object.nil? # TODO: can be simplified when inverse_of/validation added to identifiers
  else # assume if occurrenceID is not blank identifier is present
    if occurrenceID.blank?
      create_object_uuid if !occurrence_identifier && !dwc_occurrence_object.nil? # TODO: can be simplified when inverse_of/validation added to identifiers
    end
  end
end

#is_stale?Boolean

!! A spot check, could be made more robust.

Returns:

  • (Boolean)

    By looking at the data, determine if a related record has been updated since this record ws updated at



180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'app/models/dwc_occurrence.rb', line 180

def is_stale?
  case dwc_occurrence_object_type
  when 'CollectionObject'
    t = [
      dwc_occurrence_object.updated_at,
      dwc_occurrence_object.collecting_event&.updated_at,
      dwc_occurrence_object&.taxon_determinations&.first&.updated_at
    ]
    t.compact!
    t.sort.first > (updated_at || Time.now)
  else # AssertedDistribution
    dwc_occurrence_object.updated_at > updated_at
  end
end

#set_metadata_attributesObject (protected)



205
206
207
208
# File 'app/models/dwc_occurrence.rb', line 205

def 
  write_attribute( :basisOfRecord, basis)
  write_attribute( :occurrenceID, occurrence_identifier&.identifier)
end

#uuid_identifier_scopeObject



154
155
156
# File 'app/models/dwc_occurrence.rb', line 154

def uuid_identifier_scope
  dwc_occurrence_object&.identifiers&.where('identifiers.type like ?', 'Identifier::Global::Uuid%')&.order(:position) # :created_at
end