Class: TaxonNameClassification

Inherits:
ApplicationRecord show all
Includes:
Housekeeping, Shared::Citations, Shared::IsData, Shared::Notes, SoftValidation
Defined in:
app/models/taxon_name_classification.rb

Overview

A NOMEN derived classfication (roughly, a status) for a TaxonName.

Direct Known Subclasses

Icn, Icnp, Icvcn, Iczn, Latinized

Defined Under Namespace

Classes: Icn, Icnp, Icvcn, Iczn, Latinized

Constant Summary

Constants included from SoftValidation

SoftValidation::ANCESTORS_WITH_SOFT_VALIDATIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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::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::Notes

#concatenated_notes_string, #reject_notes

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

#project_idInteger

the project ID

Returns:

  • (Integer)


18
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
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
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'app/models/taxon_name_classification.rb', line 18

class TaxonNameClassification < ApplicationRecord
  include Housekeeping
  include Shared::Citations
  include Shared::Notes
  include Shared::IsData
  include SoftValidation

  belongs_to :taxon_name, inverse_of: :taxon_name_classifications

  before_validation :validate_taxon_name_classification
  before_validation :validate_uniqueness_of_latinized
  validates_presence_of :taxon_name
  validates_presence_of :type
  validates_uniqueness_of :taxon_name_id, scope: [:type, :project_id]

  validate :nomenclature_code_matches

  scope :where_taxon_name, -> (taxon_name) {where(taxon_name_id: taxon_name)}
  scope :with_type_string, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "#{base_string}" ) }
  scope :with_type_base, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "#{base_string}%" ) }
  scope :with_type_array, -> (base_array) {where('taxon_name_classifications.type IN (?)', base_array ) }
  scope :with_type_contains, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "%#{base_string}%" ) }

  soft_validate(:sv_proper_classification,
                set: :proper_classification,
                fix: :sv_fix_proper_classification,
                name: 'Applicable status',
                description: 'Check the status applicability.' )

  soft_validate(:sv_proper_year,
                set: :proper_classification,
                name: 'Applicable protonym year',
                description: 'Check that the status is compatible with the year of publication of taxon.' )

  soft_validate(:sv_validate_disjoint_classes,
                set: :validate_disjoint_classes,
                name: 'Conflicting status',
                description: 'Taxon has two conflicting statuses' )

  soft_validate(:sv_not_specific_classes,
                set: :not_specific_classes,
                name: 'Not specific status',
                description: 'More specific statuses are preffered, for example: "Nomen nudum, no description" is better than "Nomen nudum".' )

  after_save :set_cached
  after_destroy :set_cached

  def nomenclature_code
    return :iczn if type.match(/::Iczn/)
    return :icnp if type.match(/::Icnp/)
    return :icvcn if type.match(/::Icvcn/)
    return :icn if type.match(/::Icn/)
    return nil
  end

  def self.label
    name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish
  end

  # @return class
  #   this method calls Module#module_parent
  def self.parent
    self.module_parent
  end

  # @return [String]
  #   the class name, "validated" against the known list of names
  def type_name
    r = self.type.to_s
    ::TAXON_NAME_CLASSIFICATION_NAMES.include?(r) ? r : nil
  end

  def type_class=(value)
    write_attribute(:type, value.to_s)
  end

  def type_class
    r = read_attribute(:type).to_s
    r = ::TAXON_NAME_CLASSIFICATION_NAMES.include?(r) ? r.safe_constantize : nil
  end

  # @return [String]
  #   a humanized class name, with code appended to differentiate
  #   !! explored idea of LABEL in individual subclasses, use this if this doesn't work
  #   this is helper-esqe, but also useful in validation, so here for now
  def classification_label
    return nil if type_name.nil?
    type_name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish #+
      #(nomenclature_code ? " [#{nomenclature_code}]" : '')
  end

  # @return [String]
  #   the NOMEN id for this classification
  def nomen_id
    self.class::NOMEN_URI.split('/').last
  end

  # Attributes can be overridden in descendants

  # @return [Integer]
  # the minimum year of applicability for this class, defaults to 1
  def self.code_applicability_start_year
    1
  end

  # @return [Integer]
  # the last year of applicability for this class, defaults to 9999
  def self.code_applicability_end_year
    9999
  end

  # @return [Array of Strings of NomenclaturalRank names]
  # nomenclatural ranks to which this class is applicable, that is, only {TaxonName}s of these {NomenclaturalRank}s may be classified as this class
  def self.applicable_ranks
    []
  end

  # @return [Array of Strings of TaxonNameClassification names]
  # the disjoint (inapplicable) {TaxonNameClassification}s for this class, that is, {TaxonName}s classified as this class can not be additionally classified under these classes
  def self.disjoint_taxon_name_classes
    []
  end

  # @return [String, nil]
  #  if applicable, a DWC gbif status for this class
  def self.gbif_status
    nil
  end

  def self.assignable
    false
  end

 #def self.common
 #  false
 #end

  # @todo Perhaps not inherit these three meaxonNameClassificationsHelper::descendants_collection( TaxonNameClassification::Latinized )thods?

  # @return [Array of Strings]
  #   the possible suffixes for a {TaxonName} name (species) classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  #   used to validate gender agreement of species name with a genus
  def self.possible_species_endings
    []
  end

  # @return [Array of Strings]
  #   the questionable suffixes for a {TaxonName} name classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  def self.questionable_species_endings
    []
  end

  # @return [Array of Strings]
  # the possible suffixes for a {TaxonName} name (genus) classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  def self.possible_genus_endings
    []
  end

  def self.nomen_uri
    const_defined?(:NOMEN_URI, false) ? self::NOMEN_URI : nil
  end

  def set_cached
    set_cached_names_for_taxon_names
  end

  # TODO: move these to individual classes?!
  def set_cached_names_for_taxon_names
    begin
      TaxonName.transaction_with_retry do
        t = taxon_name

        if type_name =~ /(Fossil|Hybrid|Candidatus)/
          n = t.get_full_name
          t.update_columns(
            cached: n,
            cached_html: t.get_full_name_html(n),
            cached_original_combination: t.get_original_combination,
            cached_original_combination_html: t.get_original_combination_html
          )
        elsif type_name =~ /Latinized::PartOfSpeech/
          n = t.get_full_name
          t.update_columns(
              cached: n,
              cached_html: t.get_full_name_html(n),
              cached_original_combination: t.get_original_combination,
              cached_original_combination_html: t.get_original_combination_html
          )

          TaxonNameRelationship::OriginalCombination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end

          TaxonNameRelationship::Combination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, t1.cached) if t1.verbatim_name.nil?
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end
        elsif type_name =~ /Latinized::Gender/
          t.descendants.with_same_cached_valid_id.each do |t1|
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end

          TaxonNameRelationship::OriginalCombination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end

          TaxonNameRelationship::Combination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, t1.cached) if t1.verbatim_name.nil?
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end
        elsif TAXON_NAME_CLASS_NAMES_VALID.include?(type_name)
#          TaxonName.where(cached_valid_taxon_name_id: t.cached_valid_taxon_name_id).each do |vn|
          #            vn.update_column(:cached_valid_taxon_name_id, vn.get_valid_taxon_name.id)  # update self too!
          #          end
          vn = t.get_valid_taxon_name
          vn.update_columns(
            cached_valid_taxon_name_id: vn.id,
            cached_is_valid: !vn.unavailable_or_invalid?) # Do not change!
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_columns(
              cached_valid_taxon_name_id: vn.id,
              cached_is_valid: false)
            s.combination_list_self.each do |c|
              c.update_columns(cached_valid_taxon_name_id: vn.id)
            end
          end
          t.combination_list_self.each do |c|
            c.update_columns(cached_valid_taxon_name_id: vn.id)
          end
        else
          t.update_columns(cached_is_valid: false)
        end
      end
    rescue ActiveRecord::RecordInvalid
      false
    end
    true
  end

  #region Validation
  def validate_uniqueness_of_latinized
    true # moved to subclasses
  end

  #endregion

  #region Soft validation

  def sv_proper_classification
    if TAXON_NAME_CLASSIFICATION_NAMES.include?(self.type)
      # self.type_class is a Class
      if not self.type_class.applicable_ranks.include?(self.taxon_name.rank_string)
        soft_validations.add(:type, "The status '#{self.classification_label}' is not applicable to the taxon #{self.taxon_name.cached_html} at the rank of #{self.taxon_name.rank_class.rank_name}", success_message: 'The status was deleted', failure_message:  'Fail to delete the status')
      end
    end
  end

  def sv_fix_proper_classification
    begin
      TaxonNameClassification.transaction do
        self.destroy
      end
      return true
    rescue
      return false
    end
  end

  def sv_proper_year
    y = self.taxon_name.year_of_publication
    if !y.nil? && (y > self.type_class.code_applicability_end_year || y < self.type_class.code_applicability_start_year)
      soft_validations.add(:type, "The status '#{self.classification_label}' is not applicable to the taxon #{self.taxon_name.cached_html} published in the year #{y}")
    end
  end

  def sv_validate_disjoint_classes
    classifications = TaxonNameClassification.where_taxon_name(self.taxon_name).not_self(self)
    classifications.each  do |i|
      soft_validations.add(:type, "The status  '#{self.classification_label}' conflicting with another status: '#{i.classification_label}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  def sv_not_specific_classes
    true # moved to subclasses
  end

  #endregion

  def self.annotates?
    true
  end

  def annotated_object
    taxon_name
  end

  private

  def nomenclature_code_matches
    if taxon_name && type && nomenclature_code
      tn = taxon_name.type == 'Combination' ? taxon_name.protonyms.last : taxon_name
      nc = tn.rank_class.nomenclatural_code
      errors.add(:taxon_name, "#{taxon_name.cached_html} belongs to #{taxon_name.rank_class.nomenclatural_code} nomenclatural code, but the status used from #{nomenclature_code} nomenclature code") if nomenclature_code != nc
    end
  end

  # TODO: unnecessary! Type handling will raise here
  def validate_taxon_name_classification
    errors.add(:type, 'Status not found') if !self.type.nil? and !TAXON_NAME_CLASSIFICATION_NAMES.include?(self.type.to_s)
  end


  # @todo move these to a shared library (see NomenclaturalRank too)
  def self.collect_to_s(*args)
    args.collect{|arg| arg.to_s}
  end

  # @todo move these to a shared library (see NomenclaturalRank too)
  # !! using this strongly suggests something can be optimized, meomized etc.
  def self.collect_descendants_to_s(*classes)
    ans = []
    classes.each do |klass|
      ans += klass.descendants.collect{|k| k.to_s}
    end
    ans
  end

  # @todo move these to a shared library (see NomenclaturalRank too)
  # !! using this strongly suggests something can be optimized, meomized etc.
  def self.collect_descendants_and_itself_to_s(*classes)
    classes.collect{|k| k.to_s} + self.collect_descendants_to_s(*classes)
  end

end

#taxon_name_idInteger

Returns the id of the TaxonName being classified.

Returns:

  • (Integer)

    the id of the TaxonName being classified



18
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
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
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'app/models/taxon_name_classification.rb', line 18

class TaxonNameClassification < ApplicationRecord
  include Housekeeping
  include Shared::Citations
  include Shared::Notes
  include Shared::IsData
  include SoftValidation

  belongs_to :taxon_name, inverse_of: :taxon_name_classifications

  before_validation :validate_taxon_name_classification
  before_validation :validate_uniqueness_of_latinized
  validates_presence_of :taxon_name
  validates_presence_of :type
  validates_uniqueness_of :taxon_name_id, scope: [:type, :project_id]

  validate :nomenclature_code_matches

  scope :where_taxon_name, -> (taxon_name) {where(taxon_name_id: taxon_name)}
  scope :with_type_string, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "#{base_string}" ) }
  scope :with_type_base, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "#{base_string}%" ) }
  scope :with_type_array, -> (base_array) {where('taxon_name_classifications.type IN (?)', base_array ) }
  scope :with_type_contains, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "%#{base_string}%" ) }

  soft_validate(:sv_proper_classification,
                set: :proper_classification,
                fix: :sv_fix_proper_classification,
                name: 'Applicable status',
                description: 'Check the status applicability.' )

  soft_validate(:sv_proper_year,
                set: :proper_classification,
                name: 'Applicable protonym year',
                description: 'Check that the status is compatible with the year of publication of taxon.' )

  soft_validate(:sv_validate_disjoint_classes,
                set: :validate_disjoint_classes,
                name: 'Conflicting status',
                description: 'Taxon has two conflicting statuses' )

  soft_validate(:sv_not_specific_classes,
                set: :not_specific_classes,
                name: 'Not specific status',
                description: 'More specific statuses are preffered, for example: "Nomen nudum, no description" is better than "Nomen nudum".' )

  after_save :set_cached
  after_destroy :set_cached

  def nomenclature_code
    return :iczn if type.match(/::Iczn/)
    return :icnp if type.match(/::Icnp/)
    return :icvcn if type.match(/::Icvcn/)
    return :icn if type.match(/::Icn/)
    return nil
  end

  def self.label
    name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish
  end

  # @return class
  #   this method calls Module#module_parent
  def self.parent
    self.module_parent
  end

  # @return [String]
  #   the class name, "validated" against the known list of names
  def type_name
    r = self.type.to_s
    ::TAXON_NAME_CLASSIFICATION_NAMES.include?(r) ? r : nil
  end

  def type_class=(value)
    write_attribute(:type, value.to_s)
  end

  def type_class
    r = read_attribute(:type).to_s
    r = ::TAXON_NAME_CLASSIFICATION_NAMES.include?(r) ? r.safe_constantize : nil
  end

  # @return [String]
  #   a humanized class name, with code appended to differentiate
  #   !! explored idea of LABEL in individual subclasses, use this if this doesn't work
  #   this is helper-esqe, but also useful in validation, so here for now
  def classification_label
    return nil if type_name.nil?
    type_name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish #+
      #(nomenclature_code ? " [#{nomenclature_code}]" : '')
  end

  # @return [String]
  #   the NOMEN id for this classification
  def nomen_id
    self.class::NOMEN_URI.split('/').last
  end

  # Attributes can be overridden in descendants

  # @return [Integer]
  # the minimum year of applicability for this class, defaults to 1
  def self.code_applicability_start_year
    1
  end

  # @return [Integer]
  # the last year of applicability for this class, defaults to 9999
  def self.code_applicability_end_year
    9999
  end

  # @return [Array of Strings of NomenclaturalRank names]
  # nomenclatural ranks to which this class is applicable, that is, only {TaxonName}s of these {NomenclaturalRank}s may be classified as this class
  def self.applicable_ranks
    []
  end

  # @return [Array of Strings of TaxonNameClassification names]
  # the disjoint (inapplicable) {TaxonNameClassification}s for this class, that is, {TaxonName}s classified as this class can not be additionally classified under these classes
  def self.disjoint_taxon_name_classes
    []
  end

  # @return [String, nil]
  #  if applicable, a DWC gbif status for this class
  def self.gbif_status
    nil
  end

  def self.assignable
    false
  end

 #def self.common
 #  false
 #end

  # @todo Perhaps not inherit these three meaxonNameClassificationsHelper::descendants_collection( TaxonNameClassification::Latinized )thods?

  # @return [Array of Strings]
  #   the possible suffixes for a {TaxonName} name (species) classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  #   used to validate gender agreement of species name with a genus
  def self.possible_species_endings
    []
  end

  # @return [Array of Strings]
  #   the questionable suffixes for a {TaxonName} name classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  def self.questionable_species_endings
    []
  end

  # @return [Array of Strings]
  # the possible suffixes for a {TaxonName} name (genus) classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  def self.possible_genus_endings
    []
  end

  def self.nomen_uri
    const_defined?(:NOMEN_URI, false) ? self::NOMEN_URI : nil
  end

  def set_cached
    set_cached_names_for_taxon_names
  end

  # TODO: move these to individual classes?!
  def set_cached_names_for_taxon_names
    begin
      TaxonName.transaction_with_retry do
        t = taxon_name

        if type_name =~ /(Fossil|Hybrid|Candidatus)/
          n = t.get_full_name
          t.update_columns(
            cached: n,
            cached_html: t.get_full_name_html(n),
            cached_original_combination: t.get_original_combination,
            cached_original_combination_html: t.get_original_combination_html
          )
        elsif type_name =~ /Latinized::PartOfSpeech/
          n = t.get_full_name
          t.update_columns(
              cached: n,
              cached_html: t.get_full_name_html(n),
              cached_original_combination: t.get_original_combination,
              cached_original_combination_html: t.get_original_combination_html
          )

          TaxonNameRelationship::OriginalCombination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end

          TaxonNameRelationship::Combination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, t1.cached) if t1.verbatim_name.nil?
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end
        elsif type_name =~ /Latinized::Gender/
          t.descendants.with_same_cached_valid_id.each do |t1|
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end

          TaxonNameRelationship::OriginalCombination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end

          TaxonNameRelationship::Combination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, t1.cached) if t1.verbatim_name.nil?
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end
        elsif TAXON_NAME_CLASS_NAMES_VALID.include?(type_name)
#          TaxonName.where(cached_valid_taxon_name_id: t.cached_valid_taxon_name_id).each do |vn|
          #            vn.update_column(:cached_valid_taxon_name_id, vn.get_valid_taxon_name.id)  # update self too!
          #          end
          vn = t.get_valid_taxon_name
          vn.update_columns(
            cached_valid_taxon_name_id: vn.id,
            cached_is_valid: !vn.unavailable_or_invalid?) # Do not change!
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_columns(
              cached_valid_taxon_name_id: vn.id,
              cached_is_valid: false)
            s.combination_list_self.each do |c|
              c.update_columns(cached_valid_taxon_name_id: vn.id)
            end
          end
          t.combination_list_self.each do |c|
            c.update_columns(cached_valid_taxon_name_id: vn.id)
          end
        else
          t.update_columns(cached_is_valid: false)
        end
      end
    rescue ActiveRecord::RecordInvalid
      false
    end
    true
  end

  #region Validation
  def validate_uniqueness_of_latinized
    true # moved to subclasses
  end

  #endregion

  #region Soft validation

  def sv_proper_classification
    if TAXON_NAME_CLASSIFICATION_NAMES.include?(self.type)
      # self.type_class is a Class
      if not self.type_class.applicable_ranks.include?(self.taxon_name.rank_string)
        soft_validations.add(:type, "The status '#{self.classification_label}' is not applicable to the taxon #{self.taxon_name.cached_html} at the rank of #{self.taxon_name.rank_class.rank_name}", success_message: 'The status was deleted', failure_message:  'Fail to delete the status')
      end
    end
  end

  def sv_fix_proper_classification
    begin
      TaxonNameClassification.transaction do
        self.destroy
      end
      return true
    rescue
      return false
    end
  end

  def sv_proper_year
    y = self.taxon_name.year_of_publication
    if !y.nil? && (y > self.type_class.code_applicability_end_year || y < self.type_class.code_applicability_start_year)
      soft_validations.add(:type, "The status '#{self.classification_label}' is not applicable to the taxon #{self.taxon_name.cached_html} published in the year #{y}")
    end
  end

  def sv_validate_disjoint_classes
    classifications = TaxonNameClassification.where_taxon_name(self.taxon_name).not_self(self)
    classifications.each  do |i|
      soft_validations.add(:type, "The status  '#{self.classification_label}' conflicting with another status: '#{i.classification_label}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  def sv_not_specific_classes
    true # moved to subclasses
  end

  #endregion

  def self.annotates?
    true
  end

  def annotated_object
    taxon_name
  end

  private

  def nomenclature_code_matches
    if taxon_name && type && nomenclature_code
      tn = taxon_name.type == 'Combination' ? taxon_name.protonyms.last : taxon_name
      nc = tn.rank_class.nomenclatural_code
      errors.add(:taxon_name, "#{taxon_name.cached_html} belongs to #{taxon_name.rank_class.nomenclatural_code} nomenclatural code, but the status used from #{nomenclature_code} nomenclature code") if nomenclature_code != nc
    end
  end

  # TODO: unnecessary! Type handling will raise here
  def validate_taxon_name_classification
    errors.add(:type, 'Status not found') if !self.type.nil? and !TAXON_NAME_CLASSIFICATION_NAMES.include?(self.type.to_s)
  end


  # @todo move these to a shared library (see NomenclaturalRank too)
  def self.collect_to_s(*args)
    args.collect{|arg| arg.to_s}
  end

  # @todo move these to a shared library (see NomenclaturalRank too)
  # !! using this strongly suggests something can be optimized, meomized etc.
  def self.collect_descendants_to_s(*classes)
    ans = []
    classes.each do |klass|
      ans += klass.descendants.collect{|k| k.to_s}
    end
    ans
  end

  # @todo move these to a shared library (see NomenclaturalRank too)
  # !! using this strongly suggests something can be optimized, meomized etc.
  def self.collect_descendants_and_itself_to_s(*classes)
    classes.collect{|k| k.to_s} + self.collect_descendants_to_s(*classes)
  end

end

#typeString

Returns the type of classifiction (Rails STI).

Returns:

  • (String)

    the type of classifiction (Rails STI)



18
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
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
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'app/models/taxon_name_classification.rb', line 18

class TaxonNameClassification < ApplicationRecord
  include Housekeeping
  include Shared::Citations
  include Shared::Notes
  include Shared::IsData
  include SoftValidation

  belongs_to :taxon_name, inverse_of: :taxon_name_classifications

  before_validation :validate_taxon_name_classification
  before_validation :validate_uniqueness_of_latinized
  validates_presence_of :taxon_name
  validates_presence_of :type
  validates_uniqueness_of :taxon_name_id, scope: [:type, :project_id]

  validate :nomenclature_code_matches

  scope :where_taxon_name, -> (taxon_name) {where(taxon_name_id: taxon_name)}
  scope :with_type_string, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "#{base_string}" ) }
  scope :with_type_base, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "#{base_string}%" ) }
  scope :with_type_array, -> (base_array) {where('taxon_name_classifications.type IN (?)', base_array ) }
  scope :with_type_contains, -> (base_string) {where('taxon_name_classifications.type LIKE ?', "%#{base_string}%" ) }

  soft_validate(:sv_proper_classification,
                set: :proper_classification,
                fix: :sv_fix_proper_classification,
                name: 'Applicable status',
                description: 'Check the status applicability.' )

  soft_validate(:sv_proper_year,
                set: :proper_classification,
                name: 'Applicable protonym year',
                description: 'Check that the status is compatible with the year of publication of taxon.' )

  soft_validate(:sv_validate_disjoint_classes,
                set: :validate_disjoint_classes,
                name: 'Conflicting status',
                description: 'Taxon has two conflicting statuses' )

  soft_validate(:sv_not_specific_classes,
                set: :not_specific_classes,
                name: 'Not specific status',
                description: 'More specific statuses are preffered, for example: "Nomen nudum, no description" is better than "Nomen nudum".' )

  after_save :set_cached
  after_destroy :set_cached

  def nomenclature_code
    return :iczn if type.match(/::Iczn/)
    return :icnp if type.match(/::Icnp/)
    return :icvcn if type.match(/::Icvcn/)
    return :icn if type.match(/::Icn/)
    return nil
  end

  def self.label
    name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish
  end

  # @return class
  #   this method calls Module#module_parent
  def self.parent
    self.module_parent
  end

  # @return [String]
  #   the class name, "validated" against the known list of names
  def type_name
    r = self.type.to_s
    ::TAXON_NAME_CLASSIFICATION_NAMES.include?(r) ? r : nil
  end

  def type_class=(value)
    write_attribute(:type, value.to_s)
  end

  def type_class
    r = read_attribute(:type).to_s
    r = ::TAXON_NAME_CLASSIFICATION_NAMES.include?(r) ? r.safe_constantize : nil
  end

  # @return [String]
  #   a humanized class name, with code appended to differentiate
  #   !! explored idea of LABEL in individual subclasses, use this if this doesn't work
  #   this is helper-esqe, but also useful in validation, so here for now
  def classification_label
    return nil if type_name.nil?
    type_name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish #+
      #(nomenclature_code ? " [#{nomenclature_code}]" : '')
  end

  # @return [String]
  #   the NOMEN id for this classification
  def nomen_id
    self.class::NOMEN_URI.split('/').last
  end

  # Attributes can be overridden in descendants

  # @return [Integer]
  # the minimum year of applicability for this class, defaults to 1
  def self.code_applicability_start_year
    1
  end

  # @return [Integer]
  # the last year of applicability for this class, defaults to 9999
  def self.code_applicability_end_year
    9999
  end

  # @return [Array of Strings of NomenclaturalRank names]
  # nomenclatural ranks to which this class is applicable, that is, only {TaxonName}s of these {NomenclaturalRank}s may be classified as this class
  def self.applicable_ranks
    []
  end

  # @return [Array of Strings of TaxonNameClassification names]
  # the disjoint (inapplicable) {TaxonNameClassification}s for this class, that is, {TaxonName}s classified as this class can not be additionally classified under these classes
  def self.disjoint_taxon_name_classes
    []
  end

  # @return [String, nil]
  #  if applicable, a DWC gbif status for this class
  def self.gbif_status
    nil
  end

  def self.assignable
    false
  end

 #def self.common
 #  false
 #end

  # @todo Perhaps not inherit these three meaxonNameClassificationsHelper::descendants_collection( TaxonNameClassification::Latinized )thods?

  # @return [Array of Strings]
  #   the possible suffixes for a {TaxonName} name (species) classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  #   used to validate gender agreement of species name with a genus
  def self.possible_species_endings
    []
  end

  # @return [Array of Strings]
  #   the questionable suffixes for a {TaxonName} name classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  def self.questionable_species_endings
    []
  end

  # @return [Array of Strings]
  # the possible suffixes for a {TaxonName} name (genus) classified as this class, for example see {TaxonNameClassification::Latinized::Gender::Masculine}
  def self.possible_genus_endings
    []
  end

  def self.nomen_uri
    const_defined?(:NOMEN_URI, false) ? self::NOMEN_URI : nil
  end

  def set_cached
    set_cached_names_for_taxon_names
  end

  # TODO: move these to individual classes?!
  def set_cached_names_for_taxon_names
    begin
      TaxonName.transaction_with_retry do
        t = taxon_name

        if type_name =~ /(Fossil|Hybrid|Candidatus)/
          n = t.get_full_name
          t.update_columns(
            cached: n,
            cached_html: t.get_full_name_html(n),
            cached_original_combination: t.get_original_combination,
            cached_original_combination_html: t.get_original_combination_html
          )
        elsif type_name =~ /Latinized::PartOfSpeech/
          n = t.get_full_name
          t.update_columns(
              cached: n,
              cached_html: t.get_full_name_html(n),
              cached_original_combination: t.get_original_combination,
              cached_original_combination_html: t.get_original_combination_html
          )

          TaxonNameRelationship::OriginalCombination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end

          TaxonNameRelationship::Combination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, t1.cached) if t1.verbatim_name.nil?
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end
        elsif type_name =~ /Latinized::Gender/
          t.descendants.with_same_cached_valid_id.each do |t1|
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end

          TaxonNameRelationship::OriginalCombination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end

          TaxonNameRelationship::Combination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, t1.cached) if t1.verbatim_name.nil?
            n = t1.get_full_name
            t1.update_columns(
                cached: n,
                cached_html: t1.get_full_name_html(n)
            )
          end
        elsif TAXON_NAME_CLASS_NAMES_VALID.include?(type_name)
#          TaxonName.where(cached_valid_taxon_name_id: t.cached_valid_taxon_name_id).each do |vn|
          #            vn.update_column(:cached_valid_taxon_name_id, vn.get_valid_taxon_name.id)  # update self too!
          #          end
          vn = t.get_valid_taxon_name
          vn.update_columns(
            cached_valid_taxon_name_id: vn.id,
            cached_is_valid: !vn.unavailable_or_invalid?) # Do not change!
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_columns(
              cached_valid_taxon_name_id: vn.id,
              cached_is_valid: false)
            s.combination_list_self.each do |c|
              c.update_columns(cached_valid_taxon_name_id: vn.id)
            end
          end
          t.combination_list_self.each do |c|
            c.update_columns(cached_valid_taxon_name_id: vn.id)
          end
        else
          t.update_columns(cached_is_valid: false)
        end
      end
    rescue ActiveRecord::RecordInvalid
      false
    end
    true
  end

  #region Validation
  def validate_uniqueness_of_latinized
    true # moved to subclasses
  end

  #endregion

  #region Soft validation

  def sv_proper_classification
    if TAXON_NAME_CLASSIFICATION_NAMES.include?(self.type)
      # self.type_class is a Class
      if not self.type_class.applicable_ranks.include?(self.taxon_name.rank_string)
        soft_validations.add(:type, "The status '#{self.classification_label}' is not applicable to the taxon #{self.taxon_name.cached_html} at the rank of #{self.taxon_name.rank_class.rank_name}", success_message: 'The status was deleted', failure_message:  'Fail to delete the status')
      end
    end
  end

  def sv_fix_proper_classification
    begin
      TaxonNameClassification.transaction do
        self.destroy
      end
      return true
    rescue
      return false
    end
  end

  def sv_proper_year
    y = self.taxon_name.year_of_publication
    if !y.nil? && (y > self.type_class.code_applicability_end_year || y < self.type_class.code_applicability_start_year)
      soft_validations.add(:type, "The status '#{self.classification_label}' is not applicable to the taxon #{self.taxon_name.cached_html} published in the year #{y}")
    end
  end

  def sv_validate_disjoint_classes
    classifications = TaxonNameClassification.where_taxon_name(self.taxon_name).not_self(self)
    classifications.each  do |i|
      soft_validations.add(:type, "The status  '#{self.classification_label}' conflicting with another status: '#{i.classification_label}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  def sv_not_specific_classes
    true # moved to subclasses
  end

  #endregion

  def self.annotates?
    true
  end

  def annotated_object
    taxon_name
  end

  private

  def nomenclature_code_matches
    if taxon_name && type && nomenclature_code
      tn = taxon_name.type == 'Combination' ? taxon_name.protonyms.last : taxon_name
      nc = tn.rank_class.nomenclatural_code
      errors.add(:taxon_name, "#{taxon_name.cached_html} belongs to #{taxon_name.rank_class.nomenclatural_code} nomenclatural code, but the status used from #{nomenclature_code} nomenclature code") if nomenclature_code != nc
    end
  end

  # TODO: unnecessary! Type handling will raise here
  def validate_taxon_name_classification
    errors.add(:type, 'Status not found') if !self.type.nil? and !TAXON_NAME_CLASSIFICATION_NAMES.include?(self.type.to_s)
  end


  # @todo move these to a shared library (see NomenclaturalRank too)
  def self.collect_to_s(*args)
    args.collect{|arg| arg.to_s}
  end

  # @todo move these to a shared library (see NomenclaturalRank too)
  # !! using this strongly suggests something can be optimized, meomized etc.
  def self.collect_descendants_to_s(*classes)
    ans = []
    classes.each do |klass|
      ans += klass.descendants.collect{|k| k.to_s}
    end
    ans
  end

  # @todo move these to a shared library (see NomenclaturalRank too)
  # !! using this strongly suggests something can be optimized, meomized etc.
  def self.collect_descendants_and_itself_to_s(*classes)
    classes.collect{|k| k.to_s} + self.collect_descendants_to_s(*classes)
  end

end

Class Method Details

.annotates?Boolean

endregion

Returns:

  • (Boolean)


318
319
320
# File 'app/models/taxon_name_classification.rb', line 318

def self.annotates?
  true
end

.applicable_ranksArray of Strings of NomenclaturalRank names

nomenclatural ranks to which this class is applicable, that is, only TaxonNames of these NomenclaturalRanks may be classified as this class

Returns:



131
132
133
# File 'app/models/taxon_name_classification.rb', line 131

def self.applicable_ranks
  []
end

.assignableObject



147
148
149
# File 'app/models/taxon_name_classification.rb', line 147

def self.assignable
  false
end

.code_applicability_end_yearInteger

the last year of applicability for this class, defaults to 9999

Returns:

  • (Integer)


125
126
127
# File 'app/models/taxon_name_classification.rb', line 125

def self.code_applicability_end_year
  9999
end

.code_applicability_start_yearInteger

the minimum year of applicability for this class, defaults to 1

Returns:

  • (Integer)


119
120
121
# File 'app/models/taxon_name_classification.rb', line 119

def self.code_applicability_start_year
  1
end

.collect_descendants_and_itself_to_s(*classes) ⇒ Object (private)

TODO:

move these to a shared library (see NomenclaturalRank too)

!! using this strongly suggests something can be optimized, meomized etc.



359
360
361
# File 'app/models/taxon_name_classification.rb', line 359

def self.collect_descendants_and_itself_to_s(*classes)
  classes.collect{|k| k.to_s} + self.collect_descendants_to_s(*classes)
end

.collect_descendants_to_s(*classes) ⇒ Object (private)

TODO:

move these to a shared library (see NomenclaturalRank too)

!! using this strongly suggests something can be optimized, meomized etc.



349
350
351
352
353
354
355
# File 'app/models/taxon_name_classification.rb', line 349

def self.collect_descendants_to_s(*classes)
  ans = []
  classes.each do |klass|
    ans += klass.descendants.collect{|k| k.to_s}
  end
  ans
end

.collect_to_s(*args) ⇒ Object (private)



343
344
345
# File 'app/models/taxon_name_classification.rb', line 343

def self.collect_to_s(*args)
  args.collect{|arg| arg.to_s}
end

.disjoint_taxon_name_classesArray of Strings of TaxonNameClassification names

the disjoint (inapplicable) TaxonNameClassifications for this class, that is, TaxonNames classified as this class can not be additionally classified under these classes

Returns:



137
138
139
# File 'app/models/taxon_name_classification.rb', line 137

def self.disjoint_taxon_name_classes
  []
end

.gbif_statusString?

Returns if applicable, a DWC gbif status for this class.

Returns:

  • (String, nil)

    if applicable, a DWC gbif status for this class



143
144
145
# File 'app/models/taxon_name_classification.rb', line 143

def self.gbif_status
  nil
end

.labelObject



73
74
75
# File 'app/models/taxon_name_classification.rb', line 73

def self.label
  name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish
end

.nomen_uriObject



176
177
178
# File 'app/models/taxon_name_classification.rb', line 176

def self.nomen_uri
  const_defined?(:NOMEN_URI, false) ? self::NOMEN_URI : nil
end

.parentObject

Returns class this method calls Module#module_parent.

Returns:

  • class this method calls Module#module_parent



79
80
81
# File 'app/models/taxon_name_classification.rb', line 79

def self.parent
  self.module_parent
end

.possible_genus_endingsArray of Strings

the possible suffixes for a TaxonName name (genus) classified as this class, for example see TaxonNameClassification::Latinized::Gender::Masculine

Returns:

  • (Array of Strings)


172
173
174
# File 'app/models/taxon_name_classification.rb', line 172

def self.possible_genus_endings
  []
end

.possible_species_endingsArray of Strings

Returns the possible suffixes for a TaxonName name (species) classified as this class, for example see TaxonNameClassification::Latinized::Gender::Masculine used to validate gender agreement of species name with a genus.

Returns:



160
161
162
# File 'app/models/taxon_name_classification.rb', line 160

def self.possible_species_endings
  []
end

.questionable_species_endingsArray of Strings

Returns the questionable suffixes for a TaxonName name classified as this class, for example see TaxonNameClassification::Latinized::Gender::Masculine.

Returns:



166
167
168
# File 'app/models/taxon_name_classification.rb', line 166

def self.questionable_species_endings
  []
end

Instance Method Details

#annotated_objectObject



322
323
324
# File 'app/models/taxon_name_classification.rb', line 322

def annotated_object
  taxon_name
end

#classification_labelString

Returns a humanized class name, with code appended to differentiate !! explored idea of LABEL in individual subclasses, use this if this doesn’t work this is helper-esqe, but also useful in validation, so here for now.

Returns:

  • (String)

    a humanized class name, with code appended to differentiate !! explored idea of LABEL in individual subclasses, use this if this doesn’t work this is helper-esqe, but also useful in validation, so here for now



103
104
105
106
107
# File 'app/models/taxon_name_classification.rb', line 103

def classification_label
  return nil if type_name.nil?
  type_name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish #+
    #(nomenclature_code ? " [#{nomenclature_code}]" : '')
end

#nomen_idString

Returns the NOMEN id for this classification.

Returns:

  • (String)

    the NOMEN id for this classification



111
112
113
# File 'app/models/taxon_name_classification.rb', line 111

def nomen_id
  self.class::NOMEN_URI.split('/').last
end

#nomenclature_codeObject



65
66
67
68
69
70
71
# File 'app/models/taxon_name_classification.rb', line 65

def nomenclature_code
  return :iczn if type.match(/::Iczn/)
  return :icnp if type.match(/::Icnp/)
  return :icvcn if type.match(/::Icvcn/)
  return :icn if type.match(/::Icn/)
  return nil
end

#nomenclature_code_matchesObject (private)



328
329
330
331
332
333
334
# File 'app/models/taxon_name_classification.rb', line 328

def nomenclature_code_matches
  if taxon_name && type && nomenclature_code
    tn = taxon_name.type == 'Combination' ? taxon_name.protonyms.last : taxon_name
    nc = tn.rank_class.nomenclatural_code
    errors.add(:taxon_name, "#{taxon_name.cached_html} belongs to #{taxon_name.rank_class.nomenclatural_code} nomenclatural code, but the status used from #{nomenclature_code} nomenclature code") if nomenclature_code != nc
  end
end

#set_cachedObject



180
181
182
# File 'app/models/taxon_name_classification.rb', line 180

def set_cached
  set_cached_names_for_taxon_names
end

#set_cached_names_for_taxon_namesObject

TODO: move these to individual classes?!



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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'app/models/taxon_name_classification.rb', line 185

def set_cached_names_for_taxon_names
  begin
    TaxonName.transaction_with_retry do
      t = taxon_name

      if type_name =~ /(Fossil|Hybrid|Candidatus)/
        n = t.get_full_name
        t.update_columns(
          cached: n,
          cached_html: t.get_full_name_html(n),
          cached_original_combination: t.get_original_combination,
          cached_original_combination_html: t.get_original_combination_html
        )
      elsif type_name =~ /Latinized::PartOfSpeech/
        n = t.get_full_name
        t.update_columns(
            cached: n,
            cached_html: t.get_full_name_html(n),
            cached_original_combination: t.get_original_combination,
            cached_original_combination_html: t.get_original_combination_html
        )

        TaxonNameRelationship::OriginalCombination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
          t1.update_cached_original_combinations
        end

        TaxonNameRelationship::Combination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
          t1.update_column(:verbatim_name, t1.cached) if t1.verbatim_name.nil?
          n = t1.get_full_name
          t1.update_columns(
              cached: n,
              cached_html: t1.get_full_name_html(n)
          )
        end
      elsif type_name =~ /Latinized::Gender/
        t.descendants.with_same_cached_valid_id.each do |t1|
          n = t1.get_full_name
          t1.update_columns(
              cached: n,
              cached_html: t1.get_full_name_html(n)
          )
        end

        TaxonNameRelationship::OriginalCombination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
          t1.update_cached_original_combinations
        end

        TaxonNameRelationship::Combination.where(subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
          t1.update_column(:verbatim_name, t1.cached) if t1.verbatim_name.nil?
          n = t1.get_full_name
          t1.update_columns(
              cached: n,
              cached_html: t1.get_full_name_html(n)
          )
        end
      elsif TAXON_NAME_CLASS_NAMES_VALID.include?(type_name)
#          TaxonName.where(cached_valid_taxon_name_id: t.cached_valid_taxon_name_id).each do |vn|
        #            vn.update_column(:cached_valid_taxon_name_id, vn.get_valid_taxon_name.id)  # update self too!
        #          end
        vn = t.get_valid_taxon_name
        vn.update_columns(
          cached_valid_taxon_name_id: vn.id,
          cached_is_valid: !vn.unavailable_or_invalid?) # Do not change!
        vn.list_of_invalid_taxon_names.each do |s|
          s.update_columns(
            cached_valid_taxon_name_id: vn.id,
            cached_is_valid: false)
          s.combination_list_self.each do |c|
            c.update_columns(cached_valid_taxon_name_id: vn.id)
          end
        end
        t.combination_list_self.each do |c|
          c.update_columns(cached_valid_taxon_name_id: vn.id)
        end
      else
        t.update_columns(cached_is_valid: false)
      end
    end
  rescue ActiveRecord::RecordInvalid
    false
  end
  true
end

#sv_fix_proper_classificationObject



287
288
289
290
291
292
293
294
295
296
# File 'app/models/taxon_name_classification.rb', line 287

def sv_fix_proper_classification
  begin
    TaxonNameClassification.transaction do
      self.destroy
    end
    return true
  rescue
    return false
  end
end

#sv_not_specific_classesObject



312
313
314
# File 'app/models/taxon_name_classification.rb', line 312

def sv_not_specific_classes
  true # moved to subclasses
end

#sv_proper_classificationObject

region Soft validation



278
279
280
281
282
283
284
285
# File 'app/models/taxon_name_classification.rb', line 278

def sv_proper_classification
  if TAXON_NAME_CLASSIFICATION_NAMES.include?(self.type)
    # self.type_class is a Class
    if not self.type_class.applicable_ranks.include?(self.taxon_name.rank_string)
      soft_validations.add(:type, "The status '#{self.classification_label}' is not applicable to the taxon #{self.taxon_name.cached_html} at the rank of #{self.taxon_name.rank_class.rank_name}", success_message: 'The status was deleted', failure_message:  'Fail to delete the status')
    end
  end
end

#sv_proper_yearObject



298
299
300
301
302
303
# File 'app/models/taxon_name_classification.rb', line 298

def sv_proper_year
  y = self.taxon_name.year_of_publication
  if !y.nil? && (y > self.type_class.code_applicability_end_year || y < self.type_class.code_applicability_start_year)
    soft_validations.add(:type, "The status '#{self.classification_label}' is not applicable to the taxon #{self.taxon_name.cached_html} published in the year #{y}")
  end
end

#sv_validate_disjoint_classesObject



305
306
307
308
309
310
# File 'app/models/taxon_name_classification.rb', line 305

def sv_validate_disjoint_classes
  classifications = TaxonNameClassification.where_taxon_name(self.taxon_name).not_self(self)
  classifications.each  do |i|
    soft_validations.add(:type, "The status  '#{self.classification_label}' conflicting with another status: '#{i.classification_label}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
  end
end

#type_classObject



94
95
96
97
# File 'app/models/taxon_name_classification.rb', line 94

def type_class
  r = read_attribute(:type).to_s
  r = ::TAXON_NAME_CLASSIFICATION_NAMES.include?(r) ? r.safe_constantize : nil
end

#type_class=(value) ⇒ Object



90
91
92
# File 'app/models/taxon_name_classification.rb', line 90

def type_class=(value)
  write_attribute(:type, value.to_s)
end

#type_nameString

Returns the class name, “validated” against the known list of names.

Returns:

  • (String)

    the class name, “validated” against the known list of names



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

def type_name
  r = self.type.to_s
  ::TAXON_NAME_CLASSIFICATION_NAMES.include?(r) ? r : nil
end

#validate_taxon_name_classificationObject (private)

TODO: unnecessary! Type handling will raise here



337
338
339
# File 'app/models/taxon_name_classification.rb', line 337

def validate_taxon_name_classification
  errors.add(:type, 'Status not found') if !self.type.nil? and !TAXON_NAME_CLASSIFICATION_NAMES.include?(self.type.to_s)
end

#validate_uniqueness_of_latinizedObject

region Validation



270
271
272
# File 'app/models/taxon_name_classification.rb', line 270

def validate_uniqueness_of_latinized
  true # moved to subclasses
end