Class: TaxonNameClassification

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
Housekeeping, Shared::Citable, Shared::IsData, SoftValidation
Defined in:
app/models/taxon_name_classification.rb

Overview

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

Direct Known Subclasses

Icn, Icnb, Iczn, Latinized

Defined Under Namespace

Classes: Icn, Icnb, 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)


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

class TaxonNameClassification < ActiveRecord::Base
  include Housekeeping
  include Shared::Citable
  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)
  soft_validate(:sv_validate_disjoint_classes, set: :validate_disjoint_classes)
  soft_validate(:sv_not_specific_classes, set: :not_specific_classes)

  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 :icnb if type.match(/::Icnb/)
    return :icn if type.match(/::Icn/)
    return nil
  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 + 
      (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

  # @todo Perhaps not inherit these three methods?
  
  # @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_names_for_taxon_names
    begin
      TaxonName.transaction do
        t = taxon_name
        if type_name =~ /Fossil|Hybrid/
          t.update_columns(cached: t.get_full_name,
                           cached_html: t.get_full_name_html)
        elsif type_name =~ /Adjective|Participle/ && t.masculine_name.blank? && t.feminine_name.blank? && t.neuter_name.blank?
          m_name, f_name, n_name = nil, nil, nil
          if t.name.end_with?('is')
            m_name, f_name, n_name = t.name, t.name, t.name[0..-3] + 'e'
          elsif t.name.end_with?('e')
            m_name, f_name, n_name = t.name[0..-2] + 'is', t.name = t.name[0..-2] + 'is', t.name
          elsif t.name.end_with?('us')
            m_name, f_name, n_name = t.name, t.name[0..-3] + 'a', t.name[0..-3] + 'um'
          elsif t.name.end_with?('er')
            m_name, f_name, n_name = t.name, t.name[0..-3] + 'ra', t.name[0..-3] + 'rum'
          elsif t.name.end_with?('um') && !t.name.end_with?('rum')
            m_name, f_name, n_name = t.name[0..-3] + 'us', t.name[0..-3] + 'a', t.name
          elsif t.name.end_with?('a') && !t.name.end_with?('ra')
            m_name, f_name, n_name = t.name[0..-3] + 'us', t.name, t.name[0..-3] + 'um'
          elsif t.name.end_with?('or')
          end
          t.update_columns(:masculine_name => m_name,
                           :feminine_name => f_name,
                           :neuter_name => n_name)
        elsif TAXON_NAME_CLASS_NAMES_VALID.include?(type_name)
          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
        end
      end
    rescue ActiveRecord::RecordInvalid 
    end
    false
  end

  #region Validation
  # @todo validate, that all the taxon_classes in the table could be linked to taxon_classes in classes (if those had changed)
  def validate_uniqueness_of_latinized
    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 is unapplicable to the name of ' + self.taxon_name.rank_class.rank_name + ' rank')
      end
    end
    y = self.taxon_name.year_of_publication
    if not y.nil?
      if y > self.type_class.code_applicability_end_year || y < self.type_class.code_applicability_start_year
        soft_validations.add(:type, 'The status is unapplicable to the name published in ' + y.to_s)
      end
    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, "Conflicting with another status: '#{i.type_name}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  # TODO: These soft validations should be added to individual classes!
  def sv_not_specific_classes
    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, 'Please replace with appropriate relationship')
      when 'TaxonNameClassification::Iczn::Available::Invalid::Homonym'
        soft_validations.add(:type, 'Please replace with appropriate relationship')
      when 'TaxonNameClassification::Iczn::Available::Valid'
        soft_validations.add(:type, 'Please replace with appropriate relationship')
      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::Icnb::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or invalidly published')
      when 'TaxonNameClassification::Icnb::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being invalidly published')
      when 'TaxonNameClassification::Icnb::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is legitimate or illegitimate')
      when 'TaxonNameClassification::Icnb::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

  #endregion

  def self.find_for_autocomplete(params)
    where(id: params[:term]).with_project_id(params[:project_id])
  end

  def self.generate_download(scope)
    CSV.generate do |csv|
      csv << column_names
      scope.order(id: :asc).find_each do |o|
        csv << o.attributes.values_at(*column_names).collect { |i|
          i.to_s.gsub(/\n/, '\n').gsub(/\t/, '\t')
        }
      end
    end
  end

  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, "missmatched with asserted 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)
  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)
  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



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

class TaxonNameClassification < ActiveRecord::Base
  include Housekeeping
  include Shared::Citable
  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)
  soft_validate(:sv_validate_disjoint_classes, set: :validate_disjoint_classes)
  soft_validate(:sv_not_specific_classes, set: :not_specific_classes)

  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 :icnb if type.match(/::Icnb/)
    return :icn if type.match(/::Icn/)
    return nil
  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 + 
      (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

  # @todo Perhaps not inherit these three methods?
  
  # @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_names_for_taxon_names
    begin
      TaxonName.transaction do
        t = taxon_name
        if type_name =~ /Fossil|Hybrid/
          t.update_columns(cached: t.get_full_name,
                           cached_html: t.get_full_name_html)
        elsif type_name =~ /Adjective|Participle/ && t.masculine_name.blank? && t.feminine_name.blank? && t.neuter_name.blank?
          m_name, f_name, n_name = nil, nil, nil
          if t.name.end_with?('is')
            m_name, f_name, n_name = t.name, t.name, t.name[0..-3] + 'e'
          elsif t.name.end_with?('e')
            m_name, f_name, n_name = t.name[0..-2] + 'is', t.name = t.name[0..-2] + 'is', t.name
          elsif t.name.end_with?('us')
            m_name, f_name, n_name = t.name, t.name[0..-3] + 'a', t.name[0..-3] + 'um'
          elsif t.name.end_with?('er')
            m_name, f_name, n_name = t.name, t.name[0..-3] + 'ra', t.name[0..-3] + 'rum'
          elsif t.name.end_with?('um') && !t.name.end_with?('rum')
            m_name, f_name, n_name = t.name[0..-3] + 'us', t.name[0..-3] + 'a', t.name
          elsif t.name.end_with?('a') && !t.name.end_with?('ra')
            m_name, f_name, n_name = t.name[0..-3] + 'us', t.name, t.name[0..-3] + 'um'
          elsif t.name.end_with?('or')
          end
          t.update_columns(:masculine_name => m_name,
                           :feminine_name => f_name,
                           :neuter_name => n_name)
        elsif TAXON_NAME_CLASS_NAMES_VALID.include?(type_name)
          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
        end
      end
    rescue ActiveRecord::RecordInvalid 
    end
    false
  end

  #region Validation
  # @todo validate, that all the taxon_classes in the table could be linked to taxon_classes in classes (if those had changed)
  def validate_uniqueness_of_latinized
    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 is unapplicable to the name of ' + self.taxon_name.rank_class.rank_name + ' rank')
      end
    end
    y = self.taxon_name.year_of_publication
    if not y.nil?
      if y > self.type_class.code_applicability_end_year || y < self.type_class.code_applicability_start_year
        soft_validations.add(:type, 'The status is unapplicable to the name published in ' + y.to_s)
      end
    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, "Conflicting with another status: '#{i.type_name}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  # TODO: These soft validations should be added to individual classes!
  def sv_not_specific_classes
    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, 'Please replace with appropriate relationship')
      when 'TaxonNameClassification::Iczn::Available::Invalid::Homonym'
        soft_validations.add(:type, 'Please replace with appropriate relationship')
      when 'TaxonNameClassification::Iczn::Available::Valid'
        soft_validations.add(:type, 'Please replace with appropriate relationship')
      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::Icnb::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or invalidly published')
      when 'TaxonNameClassification::Icnb::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being invalidly published')
      when 'TaxonNameClassification::Icnb::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is legitimate or illegitimate')
      when 'TaxonNameClassification::Icnb::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

  #endregion

  def self.find_for_autocomplete(params)
    where(id: params[:term]).with_project_id(params[:project_id])
  end

  def self.generate_download(scope)
    CSV.generate do |csv|
      csv << column_names
      scope.order(id: :asc).find_each do |o|
        csv << o.attributes.values_at(*column_names).collect { |i|
          i.to_s.gsub(/\n/, '\n').gsub(/\t/, '\t')
        }
      end
    end
  end

  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, "missmatched with asserted 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)
  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)
  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)



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

class TaxonNameClassification < ActiveRecord::Base
  include Housekeeping
  include Shared::Citable
  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)
  soft_validate(:sv_validate_disjoint_classes, set: :validate_disjoint_classes)
  soft_validate(:sv_not_specific_classes, set: :not_specific_classes)

  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 :icnb if type.match(/::Icnb/)
    return :icn if type.match(/::Icn/)
    return nil
  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 + 
      (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

  # @todo Perhaps not inherit these three methods?
  
  # @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_names_for_taxon_names
    begin
      TaxonName.transaction do
        t = taxon_name
        if type_name =~ /Fossil|Hybrid/
          t.update_columns(cached: t.get_full_name,
                           cached_html: t.get_full_name_html)
        elsif type_name =~ /Adjective|Participle/ && t.masculine_name.blank? && t.feminine_name.blank? && t.neuter_name.blank?
          m_name, f_name, n_name = nil, nil, nil
          if t.name.end_with?('is')
            m_name, f_name, n_name = t.name, t.name, t.name[0..-3] + 'e'
          elsif t.name.end_with?('e')
            m_name, f_name, n_name = t.name[0..-2] + 'is', t.name = t.name[0..-2] + 'is', t.name
          elsif t.name.end_with?('us')
            m_name, f_name, n_name = t.name, t.name[0..-3] + 'a', t.name[0..-3] + 'um'
          elsif t.name.end_with?('er')
            m_name, f_name, n_name = t.name, t.name[0..-3] + 'ra', t.name[0..-3] + 'rum'
          elsif t.name.end_with?('um') && !t.name.end_with?('rum')
            m_name, f_name, n_name = t.name[0..-3] + 'us', t.name[0..-3] + 'a', t.name
          elsif t.name.end_with?('a') && !t.name.end_with?('ra')
            m_name, f_name, n_name = t.name[0..-3] + 'us', t.name, t.name[0..-3] + 'um'
          elsif t.name.end_with?('or')
          end
          t.update_columns(:masculine_name => m_name,
                           :feminine_name => f_name,
                           :neuter_name => n_name)
        elsif TAXON_NAME_CLASS_NAMES_VALID.include?(type_name)
          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
        end
      end
    rescue ActiveRecord::RecordInvalid 
    end
    false
  end

  #region Validation
  # @todo validate, that all the taxon_classes in the table could be linked to taxon_classes in classes (if those had changed)
  def validate_uniqueness_of_latinized
    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 is unapplicable to the name of ' + self.taxon_name.rank_class.rank_name + ' rank')
      end
    end
    y = self.taxon_name.year_of_publication
    if not y.nil?
      if y > self.type_class.code_applicability_end_year || y < self.type_class.code_applicability_start_year
        soft_validations.add(:type, 'The status is unapplicable to the name published in ' + y.to_s)
      end
    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, "Conflicting with another status: '#{i.type_name}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
    end
  end

  # TODO: These soft validations should be added to individual classes!
  def sv_not_specific_classes
    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, 'Please replace with appropriate relationship')
      when 'TaxonNameClassification::Iczn::Available::Invalid::Homonym'
        soft_validations.add(:type, 'Please replace with appropriate relationship')
      when 'TaxonNameClassification::Iczn::Available::Valid'
        soft_validations.add(:type, 'Please replace with appropriate relationship')
      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::Icnb::EffectivelyPublished'
        soft_validations.add(:type, 'Please specify if the name is validly or invalidly published')
      when 'TaxonNameClassification::Icnb::EffectivelyPublished::InvalidlyPublished'
        soft_validations.add(:type, 'Please specify the reasons for the name being invalidly published')
      when 'TaxonNameClassification::Icnb::EffectivelyPublished::ValidlyPublished'
        soft_validations.add(:type, 'Please specify if the name is legitimate or illegitimate')
      when 'TaxonNameClassification::Icnb::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

  #endregion

  def self.find_for_autocomplete(params)
    where(id: params[:term]).with_project_id(params[:project_id])
  end

  def self.generate_download(scope)
    CSV.generate do |csv|
      csv << column_names
      scope.order(id: :asc).find_each do |o|
        csv << o.attributes.values_at(*column_names).collect { |i|
          i.to_s.gsub(/\n/, '\n').gsub(/\t/, '\t')
        }
      end
    end
  end

  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, "missmatched with asserted 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)
  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)
  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?

Returns:

  • (Boolean)


286
287
288
# File 'app/models/taxon_name_classification.rb', line 286

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:



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

def self.applicable_ranks
  []
end

+ (Integer) code_applicability_end_year

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

Returns:

  • (Integer)


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

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)


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

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)



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

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)



313
314
315
316
317
318
319
# File 'app/models/taxon_name_classification.rb', line 313

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)



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

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:



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

def self.disjoint_taxon_name_classes
  []
end

+ (Object) find_for_autocomplete(params)

endregion



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

def self.find_for_autocomplete(params)
  where(id: params[:term]).with_project_id(params[:project_id])
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



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

def self.gbif_status
  nil
end

+ (Object) generate_download(scope)



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

def self.generate_download(scope)
  CSV.generate do |csv|
    csv << column_names
    scope.order(id: :asc).find_each do |o|
      csv << o.attributes.values_at(*column_names).collect { |i|
        i.to_s.gsub(/\n/, '\n').gsub(/\t/, '\t')
      }
    end
  end
end

+ (Object) nomen_uri



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

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)


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

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:



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

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:



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

def self.questionable_species_endings
  []
end

Instance Method Details

- (Object) annotated_object



290
291
292
# File 'app/models/taxon_name_classification.rb', line 290

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



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

def classification_label
  return nil if type_name.nil?
  type_name.demodulize.underscore.humanize.downcase + 
    (nomenclature_code ? " [#{nomenclature_code}]" : '')
end

- (String) nomen_id

Returns the NOMEN id for this classification

Returns:

  • (String)

    the NOMEN id for this classification



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

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

- (Object) nomenclature_code



44
45
46
47
48
49
# File 'app/models/taxon_name_classification.rb', line 44

def nomenclature_code
  return :iczn if type.match(/::Iczn/)
  return :icnb if type.match(/::Icnb/)
  return :icn if type.match(/::Icn/)
  return nil
end

- (Object) nomenclature_code_matches (private)



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

def nomenclature_code_matches
  if taxon_name && type && nomenclature_code
    errors.add(:taxon_name, "missmatched with asserted nomenclature code") if nomenclature_code != taxon_name.rank_class.nomenclatural_code
  end
end

- (Object) set_cached_names_for_taxon_names



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

def set_cached_names_for_taxon_names
  begin
    TaxonName.transaction do
      t = taxon_name
      if type_name =~ /Fossil|Hybrid/
        t.update_columns(cached: t.get_full_name,
                         cached_html: t.get_full_name_html)
      elsif type_name =~ /Adjective|Participle/ && t.masculine_name.blank? && t.feminine_name.blank? && t.neuter_name.blank?
        m_name, f_name, n_name = nil, nil, nil
        if t.name.end_with?('is')
          m_name, f_name, n_name = t.name, t.name, t.name[0..-3] + 'e'
        elsif t.name.end_with?('e')
          m_name, f_name, n_name = t.name[0..-2] + 'is', t.name = t.name[0..-2] + 'is', t.name
        elsif t.name.end_with?('us')
          m_name, f_name, n_name = t.name, t.name[0..-3] + 'a', t.name[0..-3] + 'um'
        elsif t.name.end_with?('er')
          m_name, f_name, n_name = t.name, t.name[0..-3] + 'ra', t.name[0..-3] + 'rum'
        elsif t.name.end_with?('um') && !t.name.end_with?('rum')
          m_name, f_name, n_name = t.name[0..-3] + 'us', t.name[0..-3] + 'a', t.name
        elsif t.name.end_with?('a') && !t.name.end_with?('ra')
          m_name, f_name, n_name = t.name[0..-3] + 'us', t.name, t.name[0..-3] + 'um'
        elsif t.name.end_with?('or')
        end
        t.update_columns(:masculine_name => m_name,
                         :feminine_name => f_name,
                         :neuter_name => n_name)
      elsif TAXON_NAME_CLASS_NAMES_VALID.include?(type_name)
        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
      end
    end
  rescue ActiveRecord::RecordInvalid 
  end
  false
end

- (Object) sv_not_specific_classes

TODO: These soft validations should be added to individual classes!



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 221

def sv_not_specific_classes
  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, 'Please replace with appropriate relationship')
    when 'TaxonNameClassification::Iczn::Available::Invalid::Homonym'
      soft_validations.add(:type, 'Please replace with appropriate relationship')
    when 'TaxonNameClassification::Iczn::Available::Valid'
      soft_validations.add(:type, 'Please replace with appropriate relationship')
    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::Icnb::EffectivelyPublished'
      soft_validations.add(:type, 'Please specify if the name is validly or invalidly published')
    when 'TaxonNameClassification::Icnb::EffectivelyPublished::InvalidlyPublished'
      soft_validations.add(:type, 'Please specify the reasons for the name being invalidly published')
    when 'TaxonNameClassification::Icnb::EffectivelyPublished::ValidlyPublished'
      soft_validations.add(:type, 'Please specify if the name is legitimate or illegitimate')
    when 'TaxonNameClassification::Icnb::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

- (Object) sv_proper_classification

region Soft validation



198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'app/models/taxon_name_classification.rb', line 198

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 is unapplicable to the name of ' + self.taxon_name.rank_class.rank_name + ' rank')
    end
  end
  y = self.taxon_name.year_of_publication
  if not y.nil?
    if y > self.type_class.code_applicability_end_year || y < self.type_class.code_applicability_start_year
      soft_validations.add(:type, 'The status is unapplicable to the name published in ' + y.to_s)
    end
  end
end

- (Object) sv_validate_disjoint_classes



213
214
215
216
217
218
# File 'app/models/taxon_name_classification.rb', line 213

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, "Conflicting with another status: '#{i.type_name}'") if self.type_class.disjoint_taxon_name_classes.include?(i.type_name)
  end
end

- (Object) type_class



62
63
64
65
# File 'app/models/taxon_name_classification.rb', line 62

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)



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

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



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

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

- (Object) validate_taxon_name_classification (private)



303
304
305
# File 'app/models/taxon_name_classification.rb', line 303

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

TODO:

validate, that all the taxon_classes in the table could be linked to taxon_classes in classes (if those had changed)

region Validation



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

def validate_uniqueness_of_latinized
  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