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, Ictv, Iczn, Latinized

Defined Under Namespace

Classes: Icn, Icnp, Ictv, Iczn, Latinized

Constant Summary

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_soft_validations, #soft_fixed?, #soft_valid?, #soft_validate, #soft_validated?, #soft_validations

Methods included from Housekeeping

#has_polymorphic_relationship?

Instance Attribute Details

- (Integer) project_id

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
# 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, presence: true
  validates_presence_of :type, presence: true
  validates_uniqueness_of :taxon_name_id, scope: :type

  validate :nomenclature_code_matches

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

  soft_validate(:sv_proper_classification, set: :proper_classification, has_fix: false)
  soft_validate(:sv_proper_year, set: :proper_classification, has_fix: false)
  soft_validate(:sv_validate_disjoint_classes, set: :validate_disjoint_classes, has_fix: false)
  soft_validate(:sv_not_specific_classes, set: :not_specific_classes, has_fix: false)

  after_save :set_cached
  after_destroy :set_cached
  #  after_save :set_cached_names_for_taxon_names
  # after_destroy :set_cached_names_for_taxon_names

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

  # TODO: helper method 
  def self.label
    name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish
  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

  def set_cached_names_for_taxon_names
    begin
      TaxonName.transaction do
        t = taxon_name

        if type_name =~ /(Fossil|Hybrid|Candidatus)/
          t.update_columns(
            cached: t.get_full_name,
            cached_html: t.get_full_name_html,
            cached_original_combination: t.get_original_combination,
            cached_original_combination_html: t.get_original_combination_html
          )
        elsif type_name =~ /Latinized::Gender/
          t.descendants.select{|t| t.id == t.cached_valid_taxon_name_id}.uniq.each do |t1|
            t1.update_columns(
                cached: t1.get_full_name,
                cached_html: t1.get_full_name_html
            )
          end
          TaxonNameRelationship.where(type: 'TaxonNameRelationship::OriginalCombination::OriginalGenus', subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end
          TaxonNameRelationship.where(type: 'TaxonNameRelationship::Combination::Genus', subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, cached) if t1.verbatim_name.nil?
            t1.update_columns(
                cached: t1.get_full_name,
                cached_html: t1.get_full_name_html
            )
          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_column(:cached_valid_taxon_name_id, vn.id)  # update self too!
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_column(:cached_valid_taxon_name_id, vn.id)
          end
          t.combination_list_self.each do |c|
            c.update_column(:cached_valid_taxon_name_id, vn.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      # should return false here, right?
    end
#    false # TODO: why false, success == true?
  end

  #region Validation
  def validate_uniqueness_of_latinized
    true # moved to subclasses
#    if /Latinized/.match(self.type_name)
#      lat = TaxonNameClassification.where(taxon_name_id: self.taxon_name_id).with_type_contains('Latinized').not_self(self)
#      unless lat.empty?
#        if /Gender/.match(lat.first.type_name)
#          errors.add(:taxon_name_id, 'The Gender is already selected')
#        elsif /PartOfSpeech/.match(lat.first.type_name)
#          errors.add(:taxon_name_id, 'The Part of speech is already selected')
#        end
#      end
#    end
  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.type_class.label}' is unapplicable to the taxon #{self.taxon_name.cached_html} at the rank of #{self.taxon_name.rank_class.rank_name}")
      end
    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.type_class.label}' is unapplicable 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.find_each  do |i|
      soft_validations.add(:type, "The status  '#{self.type_class.label}' conflicting with another status: '#{i.type_class.label}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  def sv_not_specific_classes
    true # moved to subclasses
=begin
    case self.type_name
      when 'TaxonNameClassification::Iczn::Available'
        soft_validations.add(:type, 'Please specify if the name is Valid or Invalid')
      when 'TaxonNameClassification::Iczn::Unavailable'
        soft_validations.add(:type, 'Please specify the reasons for the name being Unavailable')
      when 'TaxonNameClassification::Iczn::Available::Invalid'
        soft_validations.add(:type, 'Although this status can be used, it is better to replace it with appropriate relationship (for example Synonym relationship)')
      when 'TaxonNameClassification::Iczn::Available::Invalid::Homonym'
        soft_validations.add(:type, 'Although this status can be used, it is better to replace it with with appropriate relationship (for example Primary Homonym)')
      when 'TaxonNameClassification::Iczn::Available::Valid'
        soft_validations.add(:type, 'This status should only be used when one or more conflicting invalidating relationships present in the database (for example, a taxon was used as a synonym in the past, but not now, and a synonym relationship is stored in the database for a historical record). Otherwise, this status should not be used. By default, any name which does not have invalidating relationship is a valid name')
      when 'TaxonNameClassification::Iczn::Unavailable::Suppressed'
        soft_validations.add(:type, 'Please specify the reasons for the name being Suppressed')
      when 'TaxonNameClassification::Iczn::Unavailable::Excluded'
        soft_validations.add(:type, 'Please specify the reasons for the name being Excluded')
      when 'TaxonNameClassification::Iczn::Unavailable::NomenNudum'
        soft_validations.add(:type, 'Please specify the reasons for the name being Nomen Nudum')
      when 'TaxonNameClassification::Iczn::Unavailable::NonBinomial'
        soft_validations.add(:type, 'Please specify the reasons for the name being Non Binomial')
      when 'TaxonNameClassification::Icn::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or Invalidly Published')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being Invalidly Published')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is Legitimate or Illegitimate')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Legitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Legitimate')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Illegitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Illegitimate')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or Invalidly Published')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being Invalidly Published')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is Legitimate or Illegitimate')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::ValidlyPublished::Legitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Legitimate')
      # when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Illegitimate'
      #   soft_validations.add(:type, 'Please specify the reasons for the name being Illegitimate')
      when 'TaxonNameClassification::Latinized::PartOfSpeech::Adjective' ||
           'TaxonNameClassification::Latinized::PartOfSpeech::Participle'
        t = taxon_name.name
        if !t.end_with?('us') &&
          !t.end_with?('a') &&
          !t.end_with?('um') &&
          !t.end_with?('is') &&
          !t.end_with?('e') &&
          !t.end_with?('or') &&
          !t.end_with?('er')
          soft_validations.add(:type, 'Adjective or participle name should end with one of the ' \
                                              'following endings: -us, -a, -um, -is, -e, -er, -or')
        end
    end
=end
  end

  #endregion

  def self.annotates?
    true
  end

  def annotated_object
    taxon_name
  end

  private

  def nomenclature_code_matches
    if taxon_name && type && nomenclature_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 != taxon_name.rank_class.nomenclatural_code
    end
  end

  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

- (Integer) taxon_name_id

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
# 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, presence: true
  validates_presence_of :type, presence: true
  validates_uniqueness_of :taxon_name_id, scope: :type

  validate :nomenclature_code_matches

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

  soft_validate(:sv_proper_classification, set: :proper_classification, has_fix: false)
  soft_validate(:sv_proper_year, set: :proper_classification, has_fix: false)
  soft_validate(:sv_validate_disjoint_classes, set: :validate_disjoint_classes, has_fix: false)
  soft_validate(:sv_not_specific_classes, set: :not_specific_classes, has_fix: false)

  after_save :set_cached
  after_destroy :set_cached
  #  after_save :set_cached_names_for_taxon_names
  # after_destroy :set_cached_names_for_taxon_names

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

  # TODO: helper method 
  def self.label
    name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish
  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

  def set_cached_names_for_taxon_names
    begin
      TaxonName.transaction do
        t = taxon_name

        if type_name =~ /(Fossil|Hybrid|Candidatus)/
          t.update_columns(
            cached: t.get_full_name,
            cached_html: t.get_full_name_html,
            cached_original_combination: t.get_original_combination,
            cached_original_combination_html: t.get_original_combination_html
          )
        elsif type_name =~ /Latinized::Gender/
          t.descendants.select{|t| t.id == t.cached_valid_taxon_name_id}.uniq.each do |t1|
            t1.update_columns(
                cached: t1.get_full_name,
                cached_html: t1.get_full_name_html
            )
          end
          TaxonNameRelationship.where(type: 'TaxonNameRelationship::OriginalCombination::OriginalGenus', subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end
          TaxonNameRelationship.where(type: 'TaxonNameRelationship::Combination::Genus', subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, cached) if t1.verbatim_name.nil?
            t1.update_columns(
                cached: t1.get_full_name,
                cached_html: t1.get_full_name_html
            )
          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_column(:cached_valid_taxon_name_id, vn.id)  # update self too!
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_column(:cached_valid_taxon_name_id, vn.id)
          end
          t.combination_list_self.each do |c|
            c.update_column(:cached_valid_taxon_name_id, vn.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      # should return false here, right?
    end
#    false # TODO: why false, success == true?
  end

  #region Validation
  def validate_uniqueness_of_latinized
    true # moved to subclasses
#    if /Latinized/.match(self.type_name)
#      lat = TaxonNameClassification.where(taxon_name_id: self.taxon_name_id).with_type_contains('Latinized').not_self(self)
#      unless lat.empty?
#        if /Gender/.match(lat.first.type_name)
#          errors.add(:taxon_name_id, 'The Gender is already selected')
#        elsif /PartOfSpeech/.match(lat.first.type_name)
#          errors.add(:taxon_name_id, 'The Part of speech is already selected')
#        end
#      end
#    end
  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.type_class.label}' is unapplicable to the taxon #{self.taxon_name.cached_html} at the rank of #{self.taxon_name.rank_class.rank_name}")
      end
    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.type_class.label}' is unapplicable 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.find_each  do |i|
      soft_validations.add(:type, "The status  '#{self.type_class.label}' conflicting with another status: '#{i.type_class.label}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  def sv_not_specific_classes
    true # moved to subclasses
=begin
    case self.type_name
      when 'TaxonNameClassification::Iczn::Available'
        soft_validations.add(:type, 'Please specify if the name is Valid or Invalid')
      when 'TaxonNameClassification::Iczn::Unavailable'
        soft_validations.add(:type, 'Please specify the reasons for the name being Unavailable')
      when 'TaxonNameClassification::Iczn::Available::Invalid'
        soft_validations.add(:type, 'Although this status can be used, it is better to replace it with appropriate relationship (for example Synonym relationship)')
      when 'TaxonNameClassification::Iczn::Available::Invalid::Homonym'
        soft_validations.add(:type, 'Although this status can be used, it is better to replace it with with appropriate relationship (for example Primary Homonym)')
      when 'TaxonNameClassification::Iczn::Available::Valid'
        soft_validations.add(:type, 'This status should only be used when one or more conflicting invalidating relationships present in the database (for example, a taxon was used as a synonym in the past, but not now, and a synonym relationship is stored in the database for a historical record). Otherwise, this status should not be used. By default, any name which does not have invalidating relationship is a valid name')
      when 'TaxonNameClassification::Iczn::Unavailable::Suppressed'
        soft_validations.add(:type, 'Please specify the reasons for the name being Suppressed')
      when 'TaxonNameClassification::Iczn::Unavailable::Excluded'
        soft_validations.add(:type, 'Please specify the reasons for the name being Excluded')
      when 'TaxonNameClassification::Iczn::Unavailable::NomenNudum'
        soft_validations.add(:type, 'Please specify the reasons for the name being Nomen Nudum')
      when 'TaxonNameClassification::Iczn::Unavailable::NonBinomial'
        soft_validations.add(:type, 'Please specify the reasons for the name being Non Binomial')
      when 'TaxonNameClassification::Icn::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or Invalidly Published')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being Invalidly Published')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is Legitimate or Illegitimate')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Legitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Legitimate')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Illegitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Illegitimate')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or Invalidly Published')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being Invalidly Published')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is Legitimate or Illegitimate')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::ValidlyPublished::Legitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Legitimate')
      # when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Illegitimate'
      #   soft_validations.add(:type, 'Please specify the reasons for the name being Illegitimate')
      when 'TaxonNameClassification::Latinized::PartOfSpeech::Adjective' ||
           'TaxonNameClassification::Latinized::PartOfSpeech::Participle'
        t = taxon_name.name
        if !t.end_with?('us') &&
          !t.end_with?('a') &&
          !t.end_with?('um') &&
          !t.end_with?('is') &&
          !t.end_with?('e') &&
          !t.end_with?('or') &&
          !t.end_with?('er')
          soft_validations.add(:type, 'Adjective or participle name should end with one of the ' \
                                              'following endings: -us, -a, -um, -is, -e, -er, -or')
        end
    end
=end
  end

  #endregion

  def self.annotates?
    true
  end

  def annotated_object
    taxon_name
  end

  private

  def nomenclature_code_matches
    if taxon_name && type && nomenclature_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 != taxon_name.rank_class.nomenclatural_code
    end
  end

  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

- (String) type

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
# 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, presence: true
  validates_presence_of :type, presence: true
  validates_uniqueness_of :taxon_name_id, scope: :type

  validate :nomenclature_code_matches

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

  soft_validate(:sv_proper_classification, set: :proper_classification, has_fix: false)
  soft_validate(:sv_proper_year, set: :proper_classification, has_fix: false)
  soft_validate(:sv_validate_disjoint_classes, set: :validate_disjoint_classes, has_fix: false)
  soft_validate(:sv_not_specific_classes, set: :not_specific_classes, has_fix: false)

  after_save :set_cached
  after_destroy :set_cached
  #  after_save :set_cached_names_for_taxon_names
  # after_destroy :set_cached_names_for_taxon_names

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

  # TODO: helper method 
  def self.label
    name.demodulize.underscore.humanize.downcase.gsub(/\d+/, ' \0 ').squish
  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

  def set_cached_names_for_taxon_names
    begin
      TaxonName.transaction do
        t = taxon_name

        if type_name =~ /(Fossil|Hybrid|Candidatus)/
          t.update_columns(
            cached: t.get_full_name,
            cached_html: t.get_full_name_html,
            cached_original_combination: t.get_original_combination,
            cached_original_combination_html: t.get_original_combination_html
          )
        elsif type_name =~ /Latinized::Gender/
          t.descendants.select{|t| t.id == t.cached_valid_taxon_name_id}.uniq.each do |t1|
            t1.update_columns(
                cached: t1.get_full_name,
                cached_html: t1.get_full_name_html
            )
          end
          TaxonNameRelationship.where(type: 'TaxonNameRelationship::OriginalCombination::OriginalGenus', subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_cached_original_combinations
          end
          TaxonNameRelationship.where(type: 'TaxonNameRelationship::Combination::Genus', subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
            t1.update_column(:verbatim_name, cached) if t1.verbatim_name.nil?
            t1.update_columns(
                cached: t1.get_full_name,
                cached_html: t1.get_full_name_html
            )
          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_column(:cached_valid_taxon_name_id, vn.id)  # update self too!
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_column(:cached_valid_taxon_name_id, vn.id)
          end
          t.combination_list_self.each do |c|
            c.update_column(:cached_valid_taxon_name_id, vn.id)
          end
        end
      end
    rescue ActiveRecord::RecordInvalid
      # should return false here, right?
    end
#    false # TODO: why false, success == true?
  end

  #region Validation
  def validate_uniqueness_of_latinized
    true # moved to subclasses
#    if /Latinized/.match(self.type_name)
#      lat = TaxonNameClassification.where(taxon_name_id: self.taxon_name_id).with_type_contains('Latinized').not_self(self)
#      unless lat.empty?
#        if /Gender/.match(lat.first.type_name)
#          errors.add(:taxon_name_id, 'The Gender is already selected')
#        elsif /PartOfSpeech/.match(lat.first.type_name)
#          errors.add(:taxon_name_id, 'The Part of speech is already selected')
#        end
#      end
#    end
  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.type_class.label}' is unapplicable to the taxon #{self.taxon_name.cached_html} at the rank of #{self.taxon_name.rank_class.rank_name}")
      end
    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.type_class.label}' is unapplicable 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.find_each  do |i|
      soft_validations.add(:type, "The status  '#{self.type_class.label}' conflicting with another status: '#{i.type_class.label}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  def sv_not_specific_classes
    true # moved to subclasses
=begin
    case self.type_name
      when 'TaxonNameClassification::Iczn::Available'
        soft_validations.add(:type, 'Please specify if the name is Valid or Invalid')
      when 'TaxonNameClassification::Iczn::Unavailable'
        soft_validations.add(:type, 'Please specify the reasons for the name being Unavailable')
      when 'TaxonNameClassification::Iczn::Available::Invalid'
        soft_validations.add(:type, 'Although this status can be used, it is better to replace it with appropriate relationship (for example Synonym relationship)')
      when 'TaxonNameClassification::Iczn::Available::Invalid::Homonym'
        soft_validations.add(:type, 'Although this status can be used, it is better to replace it with with appropriate relationship (for example Primary Homonym)')
      when 'TaxonNameClassification::Iczn::Available::Valid'
        soft_validations.add(:type, 'This status should only be used when one or more conflicting invalidating relationships present in the database (for example, a taxon was used as a synonym in the past, but not now, and a synonym relationship is stored in the database for a historical record). Otherwise, this status should not be used. By default, any name which does not have invalidating relationship is a valid name')
      when 'TaxonNameClassification::Iczn::Unavailable::Suppressed'
        soft_validations.add(:type, 'Please specify the reasons for the name being Suppressed')
      when 'TaxonNameClassification::Iczn::Unavailable::Excluded'
        soft_validations.add(:type, 'Please specify the reasons for the name being Excluded')
      when 'TaxonNameClassification::Iczn::Unavailable::NomenNudum'
        soft_validations.add(:type, 'Please specify the reasons for the name being Nomen Nudum')
      when 'TaxonNameClassification::Iczn::Unavailable::NonBinomial'
        soft_validations.add(:type, 'Please specify the reasons for the name being Non Binomial')
      when 'TaxonNameClassification::Icn::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or Invalidly Published')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being Invalidly Published')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is Legitimate or Illegitimate')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Legitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Legitimate')
      when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Illegitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Illegitimate')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or Invalidly Published')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being Invalidly Published')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is Legitimate or Illegitimate')
      when 'TaxonNameClassification::Icnp::EffectivelyPublished::ValidlyPublished::Legitimate'
        soft_validations.add(:type, 'Please specify the reasons for the name being Legitimate')
      # when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Illegitimate'
      #   soft_validations.add(:type, 'Please specify the reasons for the name being Illegitimate')
      when 'TaxonNameClassification::Latinized::PartOfSpeech::Adjective' ||
           'TaxonNameClassification::Latinized::PartOfSpeech::Participle'
        t = taxon_name.name
        if !t.end_with?('us') &&
          !t.end_with?('a') &&
          !t.end_with?('um') &&
          !t.end_with?('is') &&
          !t.end_with?('e') &&
          !t.end_with?('or') &&
          !t.end_with?('er')
          soft_validations.add(:type, 'Adjective or participle name should end with one of the ' \
                                              'following endings: -us, -a, -um, -is, -e, -er, -or')
        end
    end
=end
  end

  #endregion

  def self.annotates?
    true
  end

  def annotated_object
    taxon_name
  end

  private

  def nomenclature_code_matches
    if taxon_name && type && nomenclature_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 != taxon_name.rank_class.nomenclatural_code
    end
  end

  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

+ (Boolean) annotates?

endregion

Returns:

  • (Boolean)


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

def self.annotates?
  true
end

+ (Array of Strings of NomenclaturalRank names) applicable_ranks

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

Returns:



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

def self.applicable_ranks
  []
end

+ (Object) assignable



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

def self.assignable
  false
end

+ (Integer) code_applicability_end_year

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

Returns:

  • (Integer)


106
107
108
# File 'app/models/taxon_name_classification.rb', line 106

def self.code_applicability_end_year
  9999
end

+ (Integer) code_applicability_start_year

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

Returns:

  • (Integer)


100
101
102
# File 'app/models/taxon_name_classification.rb', line 100

def self.code_applicability_start_year
  1
end

+ (Object) collect_descendants_and_itself_to_s(*classes) (private)

TODO:

move these to a shared library (see NomenclaturalRank too)

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



355
356
357
# File 'app/models/taxon_name_classification.rb', line 355

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

+ (Object) collect_descendants_to_s(*classes) (private)

TODO:

move these to a shared library (see NomenclaturalRank too)

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



345
346
347
348
349
350
351
# File 'app/models/taxon_name_classification.rb', line 345

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

+ (Object) collect_to_s(*args) (private)

TODO:

move these to a shared library (see NomenclaturalRank too)



339
340
341
# File 'app/models/taxon_name_classification.rb', line 339

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

+ (Array of Strings of TaxonNameClassification names) disjoint_taxon_name_classes

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

Returns:



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

def self.disjoint_taxon_name_classes
  []
end

+ (String?) gbif_status

Returns if applicable, a DWC gbif status for this class

Returns:

  • (String, nil)

    if applicable, a DWC gbif status for this class



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

def self.gbif_status
  nil
end

+ (Object) label

TODO: helper method



60
61
62
# File 'app/models/taxon_name_classification.rb', line 60

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

+ (Object) nomen_uri



157
158
159
# File 'app/models/taxon_name_classification.rb', line 157

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

+ (Array of Strings) possible_genus_endings

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

Returns:

  • (Array of Strings)


153
154
155
# File 'app/models/taxon_name_classification.rb', line 153

def self.possible_genus_endings
  []
end

+ (Array of Strings) possible_species_endings

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:



141
142
143
# File 'app/models/taxon_name_classification.rb', line 141

def self.possible_species_endings
  []
end

+ (Array of Strings) questionable_species_endings

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

Returns:



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

def self.questionable_species_endings
  []
end

Instance Method Details

- (Object) annotated_object



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

def annotated_object
  taxon_name
end

- (String) classification_label

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



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

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

- (String) nomen_id

Returns the NOMEN id for this classification

Returns:

  • (String)

    the NOMEN id for this classification



92
93
94
# File 'app/models/taxon_name_classification.rb', line 92

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

- (Object) nomenclature_code

after_save :set_cached_names_for_taxon_names after_destroy :set_cached_names_for_taxon_names



51
52
53
54
55
56
57
# File 'app/models/taxon_name_classification.rb', line 51

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

- (Object) nomenclature_code_matches (private)



327
328
329
330
331
# File 'app/models/taxon_name_classification.rb', line 327

def nomenclature_code_matches
  if taxon_name && type && nomenclature_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 != taxon_name.rank_class.nomenclatural_code
  end
end

- (Object) set_cached



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

def set_cached
  set_cached_names_for_taxon_names
end

- (Object) set_cached_names_for_taxon_names



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
# File 'app/models/taxon_name_classification.rb', line 165

def set_cached_names_for_taxon_names
  begin
    TaxonName.transaction do
      t = taxon_name

      if type_name =~ /(Fossil|Hybrid|Candidatus)/
        t.update_columns(
          cached: t.get_full_name,
          cached_html: t.get_full_name_html,
          cached_original_combination: t.get_original_combination,
          cached_original_combination_html: t.get_original_combination_html
        )
      elsif type_name =~ /Latinized::Gender/
        t.descendants.select{|t| t.id == t.cached_valid_taxon_name_id}.uniq.each do |t1|
          t1.update_columns(
              cached: t1.get_full_name,
              cached_html: t1.get_full_name_html
          )
        end
        TaxonNameRelationship.where(type: 'TaxonNameRelationship::OriginalCombination::OriginalGenus', subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
          t1.update_cached_original_combinations
        end
        TaxonNameRelationship.where(type: 'TaxonNameRelationship::Combination::Genus', subject_taxon_name: t).collect{|i| i.object_taxon_name}.uniq.each do |t1|
          t1.update_column(:verbatim_name, cached) if t1.verbatim_name.nil?
          t1.update_columns(
              cached: t1.get_full_name,
              cached_html: t1.get_full_name_html
          )
        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_column(:cached_valid_taxon_name_id, vn.id)  # update self too!
        vn.list_of_invalid_taxon_names.each do |s|
          s.update_column(:cached_valid_taxon_name_id, vn.id)
        end
        t.combination_list_self.each do |c|
          c.update_column(:cached_valid_taxon_name_id, vn.id)
        end
      end
    end
  rescue ActiveRecord::RecordInvalid
    # should return false here, right?
  end
#    false # TODO: why false, success == true?
end

- (Object) sv_not_specific_classes



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
# File 'app/models/taxon_name_classification.rb', line 256

def sv_not_specific_classes
  true # moved to subclasses
=begin
  case self.type_name
    when 'TaxonNameClassification::Iczn::Available'
      soft_validations.add(:type, 'Please specify if the name is Valid or Invalid')
    when 'TaxonNameClassification::Iczn::Unavailable'
      soft_validations.add(:type, 'Please specify the reasons for the name being Unavailable')
    when 'TaxonNameClassification::Iczn::Available::Invalid'
      soft_validations.add(:type, 'Although this status can be used, it is better to replace it with appropriate relationship (for example Synonym relationship)')
    when 'TaxonNameClassification::Iczn::Available::Invalid::Homonym'
      soft_validations.add(:type, 'Although this status can be used, it is better to replace it with with appropriate relationship (for example Primary Homonym)')
    when 'TaxonNameClassification::Iczn::Available::Valid'
      soft_validations.add(:type, 'This status should only be used when one or more conflicting invalidating relationships present in the database (for example, a taxon was used as a synonym in the past, but not now, and a synonym relationship is stored in the database for a historical record). Otherwise, this status should not be used. By default, any name which does not have invalidating relationship is a valid name')
    when 'TaxonNameClassification::Iczn::Unavailable::Suppressed'
      soft_validations.add(:type, 'Please specify the reasons for the name being Suppressed')
    when 'TaxonNameClassification::Iczn::Unavailable::Excluded'
      soft_validations.add(:type, 'Please specify the reasons for the name being Excluded')
    when 'TaxonNameClassification::Iczn::Unavailable::NomenNudum'
      soft_validations.add(:type, 'Please specify the reasons for the name being Nomen Nudum')
    when 'TaxonNameClassification::Iczn::Unavailable::NonBinomial'
      soft_validations.add(:type, 'Please specify the reasons for the name being Non Binomial')
    when 'TaxonNameClassification::Icn::EffectivelyPublished'
      soft_validations.add(:type, 'Please specify if the name is validly or Invalidly Published')
    when 'TaxonNameClassification::Icn::EffectivelyPublished::InvalidlyPublished'
      soft_validations.add(:type, 'Please specify the reasons for the name being Invalidly Published')
    when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished'
      soft_validations.add(:type, 'Please specify if the name is Legitimate or Illegitimate')
    when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Legitimate'
      soft_validations.add(:type, 'Please specify the reasons for the name being Legitimate')
    when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Illegitimate'
      soft_validations.add(:type, 'Please specify the reasons for the name being Illegitimate')
    when 'TaxonNameClassification::Icnp::EffectivelyPublished'
      soft_validations.add(:type, 'Please specify if the name is validly or Invalidly Published')
    when 'TaxonNameClassification::Icnp::EffectivelyPublished::InvalidlyPublished'
      soft_validations.add(:type, 'Please specify the reasons for the name being Invalidly Published')
    when 'TaxonNameClassification::Icnp::EffectivelyPublished::ValidlyPublished'
      soft_validations.add(:type, 'Please specify if the name is Legitimate or Illegitimate')
    when 'TaxonNameClassification::Icnp::EffectivelyPublished::ValidlyPublished::Legitimate'
      soft_validations.add(:type, 'Please specify the reasons for the name being Legitimate')
    # when 'TaxonNameClassification::Icn::EffectivelyPublished::ValidlyPublished::Illegitimate'
    #   soft_validations.add(:type, 'Please specify the reasons for the name being Illegitimate')
    when 'TaxonNameClassification::Latinized::PartOfSpeech::Adjective' ||
         'TaxonNameClassification::Latinized::PartOfSpeech::Participle'
      t = taxon_name.name
      if !t.end_with?('us') &&
        !t.end_with?('a') &&
        !t.end_with?('um') &&
        !t.end_with?('is') &&
        !t.end_with?('e') &&
        !t.end_with?('or') &&
        !t.end_with?('er')
        soft_validations.add(:type, 'Adjective or participle name should end with one of the ' \
                                            'following endings: -us, -a, -um, -is, -e, -er, -or')
      end
  end
=end
end

- (Object) sv_proper_classification

region Soft validation



233
234
235
236
237
238
239
240
# File 'app/models/taxon_name_classification.rb', line 233

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.type_class.label}' is unapplicable to the taxon #{self.taxon_name.cached_html} at the rank of #{self.taxon_name.rank_class.rank_name}")
    end
  end
end

- (Object) sv_proper_year



242
243
244
245
246
247
# File 'app/models/taxon_name_classification.rb', line 242

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.type_class.label}' is unapplicable to the taxon #{self.taxon_name.cached_html} published in the year #{y}")
  end
end

- (Object) sv_validate_disjoint_classes



249
250
251
252
253
254
# File 'app/models/taxon_name_classification.rb', line 249

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

- (Object) type_class



75
76
77
78
# File 'app/models/taxon_name_classification.rb', line 75

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

- (Object) type_class=(value)



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

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

- (String) type_name

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

Returns:

  • (String)

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



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

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

- (Object) validate_taxon_name_classification (private)



333
334
335
# File 'app/models/taxon_name_classification.rb', line 333

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

- (Object) validate_uniqueness_of_latinized

region Validation



215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'app/models/taxon_name_classification.rb', line 215

def validate_uniqueness_of_latinized
  true # moved to subclasses
#    if /Latinized/.match(self.type_name)
#      lat = TaxonNameClassification.where(taxon_name_id: self.taxon_name_id).with_type_contains('Latinized').not_self(self)
#      unless lat.empty?
#        if /Gender/.match(lat.first.type_name)
#          errors.add(:taxon_name_id, 'The Gender is already selected')
#        elsif /PartOfSpeech/.match(lat.first.type_name)
#          errors.add(:taxon_name_id, 'The Part of speech is already selected')
#        end
#      end
#    end
end