Class: FieldOccurrence

Overview

A FieldOccurrence is

* A TaxonDetermination
* Done in the field (As defined by the CollectingEvent)

It is differentiated from a CollectionObject by:

* No physical individual is brought back to a physical collection.
* It requires a TaxonDetermination be present
* It is not Containable (or Loanable, etc.)

Direct Known Subclasses

BiologicalFieldOccurrence

Defined Under Namespace

Modules: DwcExtensions Classes: BiologicalFieldOccurrence

Constant Summary collapse

GRAPH_ENTRY_POINTS =
[:biological_associations, :taxon_determinations, :biocuration_classifications, :collecting_event, :origin_relationships]

Constants included from Shared::IsDwcOccurrence

Shared::IsDwcOccurrence::DWC_DELIMITER, Shared::IsDwcOccurrence::VIEW_EXCLUSIONS

Constants included from SoftValidation

SoftValidation::ANCESTORS_WITH_SOFT_VALIDATIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DwcExtensions

#dwc_associated_media, #dwc_associated_taxa, #dwc_caste, #dwc_class, #dwc_date_identified, #dwc_family, #dwc_genus, #dwc_higher_classification, #dwc_identification_remarks, #dwc_individual_count, #dwc_infraspecific_epithet, #dwc_institution_code, #dwc_internal_attribute_for, #dwc_kingdom, #dwc_life_stage, #dwc_nomenclatural_code, #dwc_occurrence_remarks, #dwc_occurrence_status, #dwc_order, #dwc_other_catalog_numbers, #dwc_phylum, #dwc_previous_identifications, #dwc_scientific_name, #dwc_sex, #dwc_specific_epithet, #dwc_subfamily, #dwc_subtribe, #dwc_superfamily, #dwc_taxon_name_authorship, #dwc_taxon_rank, #dwc_tribe, #dwc_type_status, #dwc_verbatim_label, #is_fossil?

Methods included from Shared::IsDwcOccurrence

#dwc_occurrence_attribute_values, #dwc_occurrence_attributes, #dwc_occurrence_id, #get_dwc_occurrence, #set_dwc_occurrence

Methods included from Shared::BiologicalExtensions

#missing_determination, #name_at_rank_string, #reject_otus, #reject_taxon_determinations

Methods included from SoftValidation

#clear_soft_validations, #fix_for, #fix_soft_validations, #soft_fixed?, #soft_valid?, #soft_validate, #soft_validated?, #soft_validations, #soft_validators

Methods included from Shared::QueryBatchUpdate

#query_update

Methods included from Shared::IsData

#errors_excepting, #full_error_messages_excepting, #identical, #is_community?, #is_destroyable?, #is_editable?, #is_in_use?, #is_in_users_projects?, #metamorphosize, #similar

Methods included from Shared::Tags

#reject_tags, #tag_with, #tagged?, #tagged_with?

Methods included from Shared::ProtocolRelationships

#machine_output?, #protocolled?, #reject_protocols

Methods included from Shared::OriginRelationship

#new_objects, #old_objects, #reject_origin_relationships, #set_origin

Methods included from Shared::Notes

#concatenated_notes_string, #reject_notes

Methods included from Shared::Identifiers

#dwc_occurrence_id, #identified?, #next_by_identifier, #previous_by_identifier, #reject_identifiers, #uri, #uuid

Methods included from Shared::HasPapertrail

#attribute_updated, #attribute_updater, #detect_version

Methods included from Shared::Conveyances

#has_conveyances?, #reject_conveyances, #reject_sounds, #sound_array=

Methods included from Shared::Depictions

#has_depictions?, #image_array=, #reject_depictions, #reject_images

Methods included from Shared::DataAttributes

#import_attributes, #internal_attributes, #keyword_value_hash, #reject_data_attributes

Methods included from Shared::Confidences

#reject_confidences

Methods included from Shared::Citations

#cited?, #mark_citations_for_destruction, #nomenclature_date, #origin_citation_source_id, #reject_citations, #requires_citation?, #sources_by_topic_id

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#is_absentBoolean

Returns a positive negative, when true then there exists an assertion that the taxon was not observed given the effort of the CollectingEvent. Inessence a confirmation/checksum of total=0 assertions. Total must be zero here.

Returns:

  • (Boolean)

    a positive negative, when true then there exists an assertion that the taxon was not observed given the effort of the CollectingEvent. Inessence a confirmation/checksum of total=0 assertions. Total must be zero here.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'app/models/field_occurrence.rb', line 19

class FieldOccurrence < ApplicationRecord
  include GlobalID::Identification
  include Housekeeping

  include Shared::Citations
  include Shared::Confidences
  include Shared::DataAttributes
  include Shared::Depictions
  include Shared::Conveyances
  include Shared::HasPapertrail
  include Shared::Identifiers
  include Shared::Notes
  include Shared::Observations
  include Shared::OriginRelationship
  include Shared::ProtocolRelationships
  include Shared::Tags
  include Shared::IsData
  include Shared::QueryBatchUpdate
  include SoftValidation

  # At present must be before BiologicalExtensions
  include Shared::TaxonDeterminationRequired
  include Shared::BiologicalExtensions

  include Shared::Taxonomy
  include FieldOccurrence::DwcExtensions

  is_origin_for 'Specimen', 'Lot', 'Extract', 'AssertedDistribution', 'Sequence', 'Sound'
  originates_from 'FieldOccurrence'

  GRAPH_ENTRY_POINTS = [:biological_associations, :taxon_determinations, :biocuration_classifications, :collecting_event, :origin_relationships]

  belongs_to :collecting_event, inverse_of: :field_occurrences
  belongs_to :ranged_lot_category, inverse_of: :ranged_lots

  has_many :georeferences, through: :collecting_event
  has_many :geographic_items, through: :georeferences

  # Hmmm- semantics here?
  # Should be observers?
  has_many :collectors, through: :collecting_event

  validates_presence_of :collecting_event

  validate :records_include_taxon_determination
  validate :check_that_either_total_or_ranged_lot_category_id_is_present
  validate :check_that_both_of_category_and_total_are_not_present
  validate :total_zero_when_absent
  validate :total_positive_when_present

  accepts_nested_attributes_for :collecting_event, allow_destroy: true, reject_if: :reject_collecting_event

  def requires_taxon_determination?
    true
  end

  private

  def total_zero_when_absent
    errors.add(:total, 'Must be zero when absent.') if (total != 0) && is_absent
  end

  def total_positive_when_present
    errors.add(:total, 'Must be positive when not absent.') if !is_absent && total.present? && total <= 0
  end

  def check_that_both_of_category_and_total_are_not_present
    errors.add(:ranged_lot_category_id, 'Both ranged_lot_category and total can not be set') if ranged_lot_category_id.present? && total.present?
  end

  def check_that_either_total_or_ranged_lot_category_id_is_present
    errors.add(:base, 'Either total or a ranged lot category must be provided') if ranged_lot_category_id.blank? && total.blank?
  end

  def records_include_taxon_determination
    # !! Be careful making changes here: remember that conditions on one
    # association may/not affect other associations when the actual save
    # happens.
    # Maintain with AssertedDistribution#new_records_include_citation

    # Watch out, taxon_determination and taxon_determination *do not*
    # necessarily share the same marked_for_destruction? info, even when
    # taxon_determination is a member of taxon_determinations.

    taxon_determination_is_marked_for_destruction_on_taxon_determinations =
      taxon_determination.present? && taxon_determinations.present? &&
      taxon_determinations.count == 1 && !taxon_determinations.first.id.nil? &&
      taxon_determination.id == taxon_determinations.first.id &&
      taxon_determinations.first.marked_for_destruction?

    the_one_taxon_determinations_is_marked_for_destruction_on_taxon_determination =
      taxon_determination.present? && taxon_determinations.present? &&
      taxon_determinations.count == 1 && !taxon_determinations.first.id.nil? &&
      taxon_determination.id == taxon_determinations.first.id &&
      taxon_determination.marked_for_destruction?

    has_valid_otu =
      otu.present? &&
      !otu.marked_for_destruction? && (
        !taxon_determination.present? ||
        (!taxon_determination.marked_for_destruction? &&
        !taxon_determination_is_marked_for_destruction_on_taxon_determinations)
      )

    has_valid_taxon_determination =
      taxon_determination.present? &&
      !taxon_determination.marked_for_destruction? &&
      !taxon_determination_is_marked_for_destruction_on_taxon_determinations

    has_valid_taxon_determinations =
     taxon_determinations.count(&:marked_for_destruction?) < taxon_determinations.size &&
     !the_one_taxon_determinations_is_marked_for_destruction_on_taxon_determination

    if !has_valid_otu && !has_valid_taxon_determination && !has_valid_taxon_determinations
      errors.add(:base, 'required taxon determination is not provided')
      return
    end

    # We loaded the taxon_determinations association above. If it was empty,
    # Rails caches that value and *will not* add a determination created during
    # save from otu or taxon_determination unless we reset the citations
    # association.
    if (otu.present? || taxon_determination.present?) && taxon_determinations.size == 0
      association(:taxon_determinations).reset
    end

    if otu.present? && taxon_determination.nil?
      association(:taxon_determination).reset
    end
  end

  # Duplicated with CollectionObject
  def reject_collecting_event(attributed)
    reject = true
    CollectingEvent.core_attributes.each do |a|
      if attributed[a].present?
        reject = false
        break
      end
    end
    # !! does not account for georeferences_attributes!
    reject
  end

  # @param used_on [String]
  # @return [Scope]
  #    the max 10 most recently used collection_objects, as `used_on`
  def self.used_recently(user_id, project_id, used_on = '', ba_target = 'object')
    return [] if used_on != 'TaxonDetermination' && used_on != 'BiologicalAssociation'
    t = case used_on
        when 'TaxonDetermination'
          TaxonDetermination.arel_table
        when 'BiologicalAssociation'
          BiologicalAssociation.arel_table
        end
    if ba_target == 'subject'
      target_type = 'biological_association_subject_type'
      target_id = 'biological_association_subject_id'
    else
      target_type = 'biological_association_object_type'
      target_id = 'biological_association_object_id'
    end

    p = FieldOccurrence.arel_table

    # i is a select manager
    i = case used_on
        when 'BiologicalAssociation'
          t.project(t[target_id], t['updated_at']).from(t)
           .where(t[target_type].eq('FieldOccurrence'))
           .where(t['updated_at'].gt(1.week.ago))
           .where(t['updated_by_id'].eq(user_id))
           .where(t['project_id'].eq(project_id))
           .order(t['updated_at'].desc)
        else
          # TODO: update to reference new TaxonDetermination
          t.project(t['taxon_determination_object_id'], t['updated_at']).from(t)
           .where(t['taxon_determination_object_type'].eq('FieldOccurrence'))
           .where(t['updated_at'].gt( 1.week.ago ))
           .where(t['updated_by_id'].eq(user_id))
           .where(t['project_id'].eq(project_id))
           .order(t['updated_at'].desc)
        end

    # z is a table alias
    z = i.as('recent_t')

    j = case used_on
        when 'BiologicalAssociation'
          Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(
            z[target_id].eq(p['id'])
          ))
        else
          Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(
            z['taxon_determination_object_id'].eq(p['id'])))
        end

    FieldOccurrence.joins(j).pluck(:id).uniq
  end

  # @params target [String] currently only 'TaxonDetermination' is accepted
  # @return [Hash] field_occurrences optimized for user selection
  def self.select_optimized(user_id, project_id, target = nil, ba_target = 'object')
    h = {
      quick: [],
      pinboard: FieldOccurrence.pinned_by(user_id).where(project_id:).to_a,
      recent: []
    }

    if target && !(r = used_recently(user_id, project_id, target, ba_target)).empty?
      h[:recent] = FieldOccurrence.where(id: r.first(10)).to_a
      h[:quick] = (
        FieldOccurrence
          .pinned_by(user_id)
          .pinboard_inserted
          .where(project_id:).to_a  +
        FieldOccurrence.where(id: r.first(4)).to_a
      ).uniq
    else
      h[:recent] = FieldOccurrence
        .where(project_id:, updated_by_id: user_id)
        .order('updated_at DESC')
        .limit(10).to_a
      h[:quick] = FieldOccurrence
        .pinned_by(user_id)
        .pinboard_inserted
        .where(project_id:).to_a
    end

    h
  end

end

#totalInteger

Returns The enumerated number of things observed in the field, as asserted by *the collector* of a CollectingEvent. Must be zero if is_absent = true.

Returns:

  • (Integer)

    The enumerated number of things observed in the field, as asserted by *the collector* of a CollectingEvent. Must be zero if is_absent = true.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'app/models/field_occurrence.rb', line 19

class FieldOccurrence < ApplicationRecord
  include GlobalID::Identification
  include Housekeeping

  include Shared::Citations
  include Shared::Confidences
  include Shared::DataAttributes
  include Shared::Depictions
  include Shared::Conveyances
  include Shared::HasPapertrail
  include Shared::Identifiers
  include Shared::Notes
  include Shared::Observations
  include Shared::OriginRelationship
  include Shared::ProtocolRelationships
  include Shared::Tags
  include Shared::IsData
  include Shared::QueryBatchUpdate
  include SoftValidation

  # At present must be before BiologicalExtensions
  include Shared::TaxonDeterminationRequired
  include Shared::BiologicalExtensions

  include Shared::Taxonomy
  include FieldOccurrence::DwcExtensions

  is_origin_for 'Specimen', 'Lot', 'Extract', 'AssertedDistribution', 'Sequence', 'Sound'
  originates_from 'FieldOccurrence'

  GRAPH_ENTRY_POINTS = [:biological_associations, :taxon_determinations, :biocuration_classifications, :collecting_event, :origin_relationships]

  belongs_to :collecting_event, inverse_of: :field_occurrences
  belongs_to :ranged_lot_category, inverse_of: :ranged_lots

  has_many :georeferences, through: :collecting_event
  has_many :geographic_items, through: :georeferences

  # Hmmm- semantics here?
  # Should be observers?
  has_many :collectors, through: :collecting_event

  validates_presence_of :collecting_event

  validate :records_include_taxon_determination
  validate :check_that_either_total_or_ranged_lot_category_id_is_present
  validate :check_that_both_of_category_and_total_are_not_present
  validate :total_zero_when_absent
  validate :total_positive_when_present

  accepts_nested_attributes_for :collecting_event, allow_destroy: true, reject_if: :reject_collecting_event

  def requires_taxon_determination?
    true
  end

  private

  def total_zero_when_absent
    errors.add(:total, 'Must be zero when absent.') if (total != 0) && is_absent
  end

  def total_positive_when_present
    errors.add(:total, 'Must be positive when not absent.') if !is_absent && total.present? && total <= 0
  end

  def check_that_both_of_category_and_total_are_not_present
    errors.add(:ranged_lot_category_id, 'Both ranged_lot_category and total can not be set') if ranged_lot_category_id.present? && total.present?
  end

  def check_that_either_total_or_ranged_lot_category_id_is_present
    errors.add(:base, 'Either total or a ranged lot category must be provided') if ranged_lot_category_id.blank? && total.blank?
  end

  def records_include_taxon_determination
    # !! Be careful making changes here: remember that conditions on one
    # association may/not affect other associations when the actual save
    # happens.
    # Maintain with AssertedDistribution#new_records_include_citation

    # Watch out, taxon_determination and taxon_determination *do not*
    # necessarily share the same marked_for_destruction? info, even when
    # taxon_determination is a member of taxon_determinations.

    taxon_determination_is_marked_for_destruction_on_taxon_determinations =
      taxon_determination.present? && taxon_determinations.present? &&
      taxon_determinations.count == 1 && !taxon_determinations.first.id.nil? &&
      taxon_determination.id == taxon_determinations.first.id &&
      taxon_determinations.first.marked_for_destruction?

    the_one_taxon_determinations_is_marked_for_destruction_on_taxon_determination =
      taxon_determination.present? && taxon_determinations.present? &&
      taxon_determinations.count == 1 && !taxon_determinations.first.id.nil? &&
      taxon_determination.id == taxon_determinations.first.id &&
      taxon_determination.marked_for_destruction?

    has_valid_otu =
      otu.present? &&
      !otu.marked_for_destruction? && (
        !taxon_determination.present? ||
        (!taxon_determination.marked_for_destruction? &&
        !taxon_determination_is_marked_for_destruction_on_taxon_determinations)
      )

    has_valid_taxon_determination =
      taxon_determination.present? &&
      !taxon_determination.marked_for_destruction? &&
      !taxon_determination_is_marked_for_destruction_on_taxon_determinations

    has_valid_taxon_determinations =
     taxon_determinations.count(&:marked_for_destruction?) < taxon_determinations.size &&
     !the_one_taxon_determinations_is_marked_for_destruction_on_taxon_determination

    if !has_valid_otu && !has_valid_taxon_determination && !has_valid_taxon_determinations
      errors.add(:base, 'required taxon determination is not provided')
      return
    end

    # We loaded the taxon_determinations association above. If it was empty,
    # Rails caches that value and *will not* add a determination created during
    # save from otu or taxon_determination unless we reset the citations
    # association.
    if (otu.present? || taxon_determination.present?) && taxon_determinations.size == 0
      association(:taxon_determinations).reset
    end

    if otu.present? && taxon_determination.nil?
      association(:taxon_determination).reset
    end
  end

  # Duplicated with CollectionObject
  def reject_collecting_event(attributed)
    reject = true
    CollectingEvent.core_attributes.each do |a|
      if attributed[a].present?
        reject = false
        break
      end
    end
    # !! does not account for georeferences_attributes!
    reject
  end

  # @param used_on [String]
  # @return [Scope]
  #    the max 10 most recently used collection_objects, as `used_on`
  def self.used_recently(user_id, project_id, used_on = '', ba_target = 'object')
    return [] if used_on != 'TaxonDetermination' && used_on != 'BiologicalAssociation'
    t = case used_on
        when 'TaxonDetermination'
          TaxonDetermination.arel_table
        when 'BiologicalAssociation'
          BiologicalAssociation.arel_table
        end
    if ba_target == 'subject'
      target_type = 'biological_association_subject_type'
      target_id = 'biological_association_subject_id'
    else
      target_type = 'biological_association_object_type'
      target_id = 'biological_association_object_id'
    end

    p = FieldOccurrence.arel_table

    # i is a select manager
    i = case used_on
        when 'BiologicalAssociation'
          t.project(t[target_id], t['updated_at']).from(t)
           .where(t[target_type].eq('FieldOccurrence'))
           .where(t['updated_at'].gt(1.week.ago))
           .where(t['updated_by_id'].eq(user_id))
           .where(t['project_id'].eq(project_id))
           .order(t['updated_at'].desc)
        else
          # TODO: update to reference new TaxonDetermination
          t.project(t['taxon_determination_object_id'], t['updated_at']).from(t)
           .where(t['taxon_determination_object_type'].eq('FieldOccurrence'))
           .where(t['updated_at'].gt( 1.week.ago ))
           .where(t['updated_by_id'].eq(user_id))
           .where(t['project_id'].eq(project_id))
           .order(t['updated_at'].desc)
        end

    # z is a table alias
    z = i.as('recent_t')

    j = case used_on
        when 'BiologicalAssociation'
          Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(
            z[target_id].eq(p['id'])
          ))
        else
          Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(
            z['taxon_determination_object_id'].eq(p['id'])))
        end

    FieldOccurrence.joins(j).pluck(:id).uniq
  end

  # @params target [String] currently only 'TaxonDetermination' is accepted
  # @return [Hash] field_occurrences optimized for user selection
  def self.select_optimized(user_id, project_id, target = nil, ba_target = 'object')
    h = {
      quick: [],
      pinboard: FieldOccurrence.pinned_by(user_id).where(project_id:).to_a,
      recent: []
    }

    if target && !(r = used_recently(user_id, project_id, target, ba_target)).empty?
      h[:recent] = FieldOccurrence.where(id: r.first(10)).to_a
      h[:quick] = (
        FieldOccurrence
          .pinned_by(user_id)
          .pinboard_inserted
          .where(project_id:).to_a  +
        FieldOccurrence.where(id: r.first(4)).to_a
      ).uniq
    else
      h[:recent] = FieldOccurrence
        .where(project_id:, updated_by_id: user_id)
        .order('updated_at DESC')
        .limit(10).to_a
      h[:quick] = FieldOccurrence
        .pinned_by(user_id)
        .pinboard_inserted
        .where(project_id:).to_a
    end

    h
  end

end

Class Method Details

.select_optimized(user_id, project_id, target = nil, ba_target = 'object') ⇒ Hash (private)

Returns field_occurrences optimized for user selection.

Returns:

  • (Hash)

    field_occurrences optimized for user selection



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'app/models/field_occurrence.rb', line 221

def self.select_optimized(user_id, project_id, target = nil, ba_target = 'object')
  h = {
    quick: [],
    pinboard: FieldOccurrence.pinned_by(user_id).where(project_id:).to_a,
    recent: []
  }

  if target && !(r = used_recently(user_id, project_id, target, ba_target)).empty?
    h[:recent] = FieldOccurrence.where(id: r.first(10)).to_a
    h[:quick] = (
      FieldOccurrence
        .pinned_by(user_id)
        .pinboard_inserted
        .where(project_id:).to_a  +
      FieldOccurrence.where(id: r.first(4)).to_a
    ).uniq
  else
    h[:recent] = FieldOccurrence
      .where(project_id:, updated_by_id: user_id)
      .order('updated_at DESC')
      .limit(10).to_a
    h[:quick] = FieldOccurrence
      .pinned_by(user_id)
      .pinboard_inserted
      .where(project_id:).to_a
  end

  h
end

.used_recently(user_id, project_id, used_on = '', ba_target = 'object') ⇒ Scope (private)

Returns the max 10 most recently used collection_objects, as ‘used_on`.

Parameters:

  • used_on (String) (defaults to: '')

Returns:

  • (Scope)

    the max 10 most recently used collection_objects, as ‘used_on`



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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/field_occurrence.rb', line 166

def self.used_recently(user_id, project_id, used_on = '', ba_target = 'object')
  return [] if used_on != 'TaxonDetermination' && used_on != 'BiologicalAssociation'
  t = case used_on
      when 'TaxonDetermination'
        TaxonDetermination.arel_table
      when 'BiologicalAssociation'
        BiologicalAssociation.arel_table
      end
  if ba_target == 'subject'
    target_type = 'biological_association_subject_type'
    target_id = 'biological_association_subject_id'
  else
    target_type = 'biological_association_object_type'
    target_id = 'biological_association_object_id'
  end

  p = FieldOccurrence.arel_table

  # i is a select manager
  i = case used_on
      when 'BiologicalAssociation'
        t.project(t[target_id], t['updated_at']).from(t)
         .where(t[target_type].eq('FieldOccurrence'))
         .where(t['updated_at'].gt(1.week.ago))
         .where(t['updated_by_id'].eq(user_id))
         .where(t['project_id'].eq(project_id))
         .order(t['updated_at'].desc)
      else
        # TODO: update to reference new TaxonDetermination
        t.project(t['taxon_determination_object_id'], t['updated_at']).from(t)
         .where(t['taxon_determination_object_type'].eq('FieldOccurrence'))
         .where(t['updated_at'].gt( 1.week.ago ))
         .where(t['updated_by_id'].eq(user_id))
         .where(t['project_id'].eq(project_id))
         .order(t['updated_at'].desc)
      end

  # z is a table alias
  z = i.as('recent_t')

  j = case used_on
      when 'BiologicalAssociation'
        Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(
          z[target_id].eq(p['id'])
        ))
      else
        Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(
          z['taxon_determination_object_id'].eq(p['id'])))
      end

  FieldOccurrence.joins(j).pluck(:id).uniq
end

Instance Method Details

#check_that_both_of_category_and_total_are_not_presentObject (private)



85
86
87
# File 'app/models/field_occurrence.rb', line 85

def check_that_both_of_category_and_total_are_not_present
  errors.add(:ranged_lot_category_id, 'Both ranged_lot_category and total can not be set') if ranged_lot_category_id.present? && total.present?
end

#check_that_either_total_or_ranged_lot_category_id_is_presentObject (private)



89
90
91
# File 'app/models/field_occurrence.rb', line 89

def check_that_either_total_or_ranged_lot_category_id_is_present
  errors.add(:base, 'Either total or a ranged lot category must be provided') if ranged_lot_category_id.blank? && total.blank?
end

#records_include_taxon_determinationObject (private)



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'app/models/field_occurrence.rb', line 93

def records_include_taxon_determination
  # !! Be careful making changes here: remember that conditions on one
  # association may/not affect other associations when the actual save
  # happens.
  # Maintain with AssertedDistribution#new_records_include_citation

  # Watch out, taxon_determination and taxon_determination *do not*
  # necessarily share the same marked_for_destruction? info, even when
  # taxon_determination is a member of taxon_determinations.

  taxon_determination_is_marked_for_destruction_on_taxon_determinations =
    taxon_determination.present? && taxon_determinations.present? &&
    taxon_determinations.count == 1 && !taxon_determinations.first.id.nil? &&
    taxon_determination.id == taxon_determinations.first.id &&
    taxon_determinations.first.marked_for_destruction?

  the_one_taxon_determinations_is_marked_for_destruction_on_taxon_determination =
    taxon_determination.present? && taxon_determinations.present? &&
    taxon_determinations.count == 1 && !taxon_determinations.first.id.nil? &&
    taxon_determination.id == taxon_determinations.first.id &&
    taxon_determination.marked_for_destruction?

  has_valid_otu =
    otu.present? &&
    !otu.marked_for_destruction? && (
      !taxon_determination.present? ||
      (!taxon_determination.marked_for_destruction? &&
      !taxon_determination_is_marked_for_destruction_on_taxon_determinations)
    )

  has_valid_taxon_determination =
    taxon_determination.present? &&
    !taxon_determination.marked_for_destruction? &&
    !taxon_determination_is_marked_for_destruction_on_taxon_determinations

  has_valid_taxon_determinations =
   taxon_determinations.count(&:marked_for_destruction?) < taxon_determinations.size &&
   !the_one_taxon_determinations_is_marked_for_destruction_on_taxon_determination

  if !has_valid_otu && !has_valid_taxon_determination && !has_valid_taxon_determinations
    errors.add(:base, 'required taxon determination is not provided')
    return
  end

  # We loaded the taxon_determinations association above. If it was empty,
  # Rails caches that value and *will not* add a determination created during
  # save from otu or taxon_determination unless we reset the citations
  # association.
  if (otu.present? || taxon_determination.present?) && taxon_determinations.size == 0
    association(:taxon_determinations).reset
  end

  if otu.present? && taxon_determination.nil?
    association(:taxon_determination).reset
  end
end

#reject_collecting_event(attributed) ⇒ Object (private)

Duplicated with CollectionObject



151
152
153
154
155
156
157
158
159
160
161
# File 'app/models/field_occurrence.rb', line 151

def reject_collecting_event(attributed)
  reject = true
  CollectingEvent.core_attributes.each do |a|
    if attributed[a].present?
      reject = false
      break
    end
  end
  # !! does not account for georeferences_attributes!
  reject
end

#requires_taxon_determination?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'app/models/field_occurrence.rb', line 71

def requires_taxon_determination?
  true
end

#total_positive_when_presentObject (private)



81
82
83
# File 'app/models/field_occurrence.rb', line 81

def total_positive_when_present
  errors.add(:total, 'Must be positive when not absent.') if !is_absent && total.present? && total <= 0
end

#total_zero_when_absentObject (private)



77
78
79
# File 'app/models/field_occurrence.rb', line 77

def total_zero_when_absent
  errors.add(:total, 'Must be zero when absent.') if (total != 0) && is_absent
end