Class: TaxonNameRelationship

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

Overview

A NOMEN github.com/SpeciesFileGroup/nomen relationship between two Protonyms.

Unless otherwise noted relationships read left to right, and can be interpreted by inserting “of” after the class name.

For example the triple:

Subject: Aus aus - TaxonNameRelationship::Iczn::Invalidating::Synonym - Object: Bus bus

Can be read as:

  aus synonym OF bus

Note that not all relationships are reflective.  We can't say, for the example above we can't say

  bus valid_name OF aus

Note that we can not say that all names that are subjects are valid, for instance, this is determined on a case by case basis.

TaxonNameRelationships have a domain (attributes on the subject) and range (attributes on the object). So if you use a relatinship you may be asserting a TaxonNameClassification also exists for the subject or object.

Direct Known Subclasses

Combination, Hybrid, Icn, Icnb, Iczn, OriginalCombination, SourceClassifiedAs, Typification

Defined Under Namespace

Classes: Combination, Hybrid, Icn, Icnb, Iczn, OriginalCombination, SourceClassifiedAs, Typification

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

- (Boolean?) no_cached

Returns When true, cached values are not built

Returns:

  • (Boolean, nil)

    When true, cached values are not built



47
48
49
# File 'app/models/taxon_name_relationship.rb', line 47

def no_cached
  @no_cached
end

- (Integer) object_taxon_name_id

Returns the object taxon name

Returns:

  • (Integer)

    the object taxon name



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
# File 'app/models/taxon_name_relationship.rb', line 39

class TaxonNameRelationship < ActiveRecord::Base
  include Housekeeping
  include Shared::Citable
  include Shared::IsData
  include SoftValidation

  # @return [Boolean, nil]
  #   When true, cached values are not built
  attr_accessor :no_cached

  belongs_to :subject_taxon_name, class_name: 'TaxonName', foreign_key: :subject_taxon_name_id, inverse_of: :taxon_name_relationships  # left side
  belongs_to :object_taxon_name, class_name: 'TaxonName', foreign_key: :object_taxon_name_id, inverse_of: :related_taxon_name_relationships    # right side

  after_save :set_cached_names_for_taxon_names, unless: 'self.no_cached'
  after_destroy :set_cached_names_for_taxon_names, unless: 'self.no_cached'

  validates_presence_of :type, message: 'Relationship type should be specified'
  validates_presence_of :subject_taxon_name, message: 'missing taxon name on left side'
  validates_presence_of :object_taxon_name, message: 'missing taxon name on right side'

  # TODO: these are likely not speced!  May have to change them to reference object rather than id
  validates_uniqueness_of :object_taxon_name_id, scope: :type, if: :is_combination?
  validates_uniqueness_of :object_taxon_name_id, scope: [:type, :subject_taxon_name_id], unless: :is_combination?

  validate :validate_type, :validate_subject_and_object_are_not_identical

  with_options unless: '!subject_taxon_name || !object_taxon_name' do |v|
    v.validate :validate_subject_and_object_share_code,
      :validate_uniqueness_of_typification_object,
      #:validate_uniqueness_of_synonym_subject,
      :validate_object_must_equal_subject_for_uncertain_placement,
      :validate_subject_and_object_ranks,
      :validate_rank_group
  end

  soft_validate(:sv_validate_required_relationships, set: :validate_required_relationships)
  soft_validate(:sv_validate_disjoint_relationships, set: :validate_disjoint_relationships)
  soft_validate(:sv_validate_disjoint_object, set: :validate_disjoint_object)
  soft_validate(:sv_validate_disjoint_subject, set: :validate_disjoint_subject)
  soft_validate(:sv_specific_relationship, set: :specific_relationship)
  soft_validate(:sv_objective_synonym_relationship, set: :objective_synonym_relationship)
  soft_validate(:sv_synonym_relationship, set: :synonym_relationship)
  soft_validate(:sv_not_specific_relationship, set: :not_specific_relationship)
  soft_validate(:sv_synonym_linked_to_valid_name, set: :synonym_linked_to_valid_name)
  soft_validate(:sv_matching_type_genus, set: :matching_type_genus)
  soft_validate(:sv_validate_priority, set: :validate_priority)

  soft_validate(:sv_coordinated_taxa, set: :coordinated_taxa)

  scope :where_subject_is_taxon_name, -> (taxon_name) {where(subject_taxon_name_id: taxon_name)}
  scope :where_object_is_taxon_name, -> (taxon_name) {where(object_taxon_name_id: taxon_name)}
  scope :where_object_in_taxon_names, -> (taxon_name_array) {where('"taxon_name_relationships"."object_taxon_name_id" IN (?)', taxon_name_array)}
#  scope :with_type_string, -> (type_string) {where('"taxon_name_relationships"."type" LIKE ?', "#{type_string}" ) }

  scope :with_type_string, -> (type_string) { where(sanitize_sql_array(["taxon_name_relationships.type = '%s'", type_string])) } #   #{?type_string}"where('"taxon_name_relationships"."type" LIKE ?', "#{type_string}" ) }

  scope :with_type_base,     -> (base_string) {where('"taxon_name_relationships"."type" LIKE ?', "#{base_string}%" ) } 
  scope :with_type_contains, -> (base_string) {where('"taxon_name_relationships"."type" LIKE ?', "%#{base_string}%" ) } 

  scope :with_two_type_bases, -> (base_string1, base_string2) {where("taxon_name_relationships.type LIKE '#{base_string1}%' OR taxon_name_relationships.type LIKE '#{base_string2}%'" ) }
  scope :with_type_array, -> (base_array) {where('taxon_name_relationships.type IN (?)', base_array ) }

  # @return [Array of TaxonNameClassification]
  #  the inferable TaxonNameClassification(s) added to the subject when this relationship is used
  #  !! Not implemented 
  def subject_properties
    []
  end

  # @return [Array of TaxonNameClassification]
  #  the inferable TaxonNameClassification(s) added to the subject when this relationship is used
  #  !! Not implmented
  def object_properties
    []
  end

  # @return [Array of NomenclatureRank]
  #   the valid ranks to which the subject name can belong, set in subclasses. (left side)
  def self.valid_subject_ranks
    []
  end


  # @return [Array of NomenclatureRank]
  #   the valid ranks to which the object name can belong, set in subclasses. (right side)
  def self.valid_object_ranks
    []
  end

  # @return [Array of TaxonNameRelationships]
  #   if this relationships is set for the subject, then others in this array should not be used for that subject
  def self.disjoint_taxon_name_relationships
    []
  end

  # TODO: why isn't this disjoint?
  # disjoint relationships for the taxon as a object
  def self.required_taxon_name_relationships
    []
  end

  def self.disjoint_subject_classes
    []
  end

  def self.disjoint_object_classes
    []
  end

  def self.gbif_status_of_subject
    nil
  end

  def self.gbif_status_of_object
    nil
  end

  def self.assignable
    false
  end

  # @return [Symbol]
  #   determine the relative age of subject and object
  #   :direct - subject is younger than object
  #   :reverse - object is younger than subject
  def self.nomenclatural_priority
    nil 
  end

  # @return [String]
  #    the status inferred by the relationship to the object name 
  def object_status
    self.type_name.demodulize.underscore.humanize.downcase
  end

  # @return [String]
  #    the status inferred by the relationship to the subject name 
  def subject_status
    self.type_name.demodulize.underscore.humanize.downcase
  end

  # @return [String]
  #    the connecting word in the relationship from the subject name to object name
  def subject_status_connector_to_object
    ' of'
  end

  # @return [String]
  #    the connecting word in the relationship from the object name to subject name
  def object_status_connector_to_subject
    ' of'
  end

  # @return [String]
  #   a readable fragement combining status and connector
  def subject_status_tag
    subject_status + subject_status_connector_to_object
  end

  # @return [String]
  #   a readable fragement combining status and connector
  def object_status_tag
    object_status + object_status_connector_to_subject
  end

  # @return [String, nil]
  #   the type of this relationship, IF the type is a valid name, else nil
  def type_name
    r = self.type
    TAXON_NAME_RELATIONSHIP_NAMES.include?(r) ? r : nil
  end

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

  # @return [TaxonNameRelationship, String]
  #    the type as a class, if legal, else a string  ! Strangeish
  def type_class
    r = read_attribute(:type).to_s
    TAXON_NAME_RELATIONSHIP_NAMES.include?(r) ? r.safe_constantize : r
  end

  # TODO: match on either name
  def self.find_for_autocomplete(params)
    where(id: params[:term]).with_project_id(params[:project_id])
  end

  # @return [String, nil]
  #   the NOMEN uri for this type
  def self.nomen_uri
    const_defined?(:NOMEN_URI, false) ? self::NOMEN_URI : nil
  end

  # @return [Time]
  #   effective date of publication, used to determine nomenclatural priorities
  def nomenclature_date
    self.source ? (self.source.cached_nomenclature_date ? self.source.cached_nomenclature_date.to_time : Time.now) : Time.now
  end

  # @todo SourceClassifiedAs is not really Combination in the other sense
  def is_combination?
    !!/TaxonNameRelationship::(OriginalCombination|Combination)/.match(self.type.to_s)
  end

  protected

  #region Validation

  def validate_type
    unless TAXON_NAME_RELATIONSHIP_NAMES.include?(type)
      errors.add(:type, "'#{type}' is not a valid taxon name relationship")
    end

    if object_taxon_name.class.to_s == 'Protonym' || object_taxon_name.class.to_s == 'Hybrid'
      errors.add(:type, "'#{type}' is not a valid taxon name relationship") if /TaxonNameRelationship::Combination::/.match(self.type)
    end

    if object_taxon_name.class.to_s == 'Combination'
      errors.add(:type, "'#{type}' is not a valid taxon name relationship") unless /TaxonNameRelationship::Combination::/.match(self.type)
    end
  end

  # @todo validate, that all the relationships in the table could be linked to relationships in classes (if those had changed)

  def validate_subject_and_object_share_code
    if object_taxon_name.type  == 'Protonym' && subject_taxon_name.type == 'Protonym'
      errors.add(:object_taxon_name_id, 'The related taxon is from different potentially_validating code') if subject_taxon_name.rank_class.nomenclatural_code != object_taxon_name.rank_class.nomenclatural_code
    end
  end

  def validate_subject_and_object_are_not_identical
    if self.object_taxon_name == self.subject_taxon_name
      errors.add(:object_taxon_name, 'Taxon should not refer to itself') unless self.type =~ /OriginalCombination/
    end
  end

  def validate_object_must_equal_subject_for_uncertain_placement
    if self.object_taxon_name_id != self.subject_taxon_name.parent_id && self.type_name == 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      errors.add(:object_taxon_name_id, 'The parent and related taxon should match')
    end
  end

  def validate_subject_and_object_ranks
    tname = self.type_name

    if tname =~ /TaxonNameRelationship::(Icnb|Icn|Iczn)/ && tname != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      rank_group = self.subject_taxon_name.rank_class.parent
      unless rank_group == self.object_taxon_name.rank_class.parent
        errors.add(:object_taxon_name_id, "Rank of related taxon should be in the #{rank_group.rank_name}")
      end
    end

    unless self.type_class.blank? # only validate if it is set
      if object_taxon_name
        if object_taxon_name.type == 'Protonym' || object_taxon_name.type == 'Hybrid'
          unless self.type_class.valid_object_ranks.include?(self.object_taxon_name.rank_string)
            errors.add(:object_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
            errors.add(:type, 'Not compatible with the rank of object taxon')
          end
        end
      end

      if subject_taxon_name
        if subject_taxon_name.type == 'Protonym' || subject_taxon_name.type == 'Hybrid'
          unless self.type_class.valid_subject_ranks.include?(self.subject_taxon_name.parent.rank_string)
            soft_validations.add(:subject_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
            soft_validations.add(:type, 'Not compatible with the rank of subject taxon')
          end
        end
      end
    end
  end

  #def validate_uniqueness_of_synonym_subject ##### Protonym historically could be listed as a synonym to different taxa
  #  if !self.type.nil? && /Synonym/.match(self.type_name) && !TaxonNameRelationship.where(subject_taxon_name_id: self.subject_taxon_name_id).with_type_contains('Synonym').not_self(self).empty?
  #    errors.add(:subject_taxon_name_id, 'Only one synonym relationship is allowed')
  #  end
  #end

  def validate_uniqueness_of_typification_object
    if /Typification/.match(self.type_name) && !TaxonNameRelationship.where(object_taxon_name_id: self.object_taxon_name_id).with_type_contains('Typification').not_self(self).empty?
      errors.add(:object_taxon_name_id, 'Only one type relationship is allowed')
    end
  end

  def validate_rank_group
    if self.type =~ /Hybrid/ && self.subject_taxon_name && self.object_taxon_name
      if self.subject_taxon_name.rank_class.parent != self.object_taxon_name.rank_class.parent
        errors.add(:subject_taxon_name_id, "Rank of taxon (#{self.subject_taxon_name.rank_class.rank_name}) is not compatible with the rank of hybrid (#{self.object_taxon_name.rank_class.rank_name})")
      end
    end
  end

  def set_cached_names_for_taxon_names
    dependants = []
    begin
      TaxonName.transaction do

       #if self.type_name =~/OriginalCombination/
       #  t = self.object_taxon_name
       #  t.update_columns(:cached_original_combination => t.get_original_combination,
       #                   :cached_primary_homonym => t.get_genus_species(:original, :self),
       #                   :cached_primary_homonym_alternative_spelling => t.get_genus_species(:original, :alternative))
        if self.is_combination?
          t = self.object_taxon_name
          t.update_columns(:cached_original_combination => t.get_original_combination,
                           :cached => t.get_full_name,
                           :cached_html => t.get_full_name_html,
                           :cached_author_year => t.get_author_and_year,
                           :cached_valid_taxon_name_id => t.get_valid_taxon_name.id)
#        elsif self.type_name =~/Misspelling/
#          t = self.subject_taxon_name
#          t.update_column(:cached_misspelling, t.get_cached_misspelling)
        elsif self.type_name =~/TaxonNameRelationship::Hybrid/
          t = self.object_taxon_name
          t.update_columns(:cached => t.get_full_name,
                           :cached_html => t.get_full_name_html)
        elsif self.type_name =~/SourceClassifiedAs/
          t = self.subject_taxon_name
          t.update_column(:cached_classified_as, t.get_cached_classified_as)
        elsif TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
          t = self.subject_taxon_name
          if self.type_name =~/Misspelling/
            t.update_column(:cached_misspelling, t.get_cached_misspelling)
          end
          t.update_columns(:cached => t.get_full_name,
                           :cached_html => t.get_full_name_html)
          vn = t.get_valid_taxon_name
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_column(:cached_valid_taxon_name_id, vn.id)
            s.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end
          end
        end
      end

    # no point in rescuing and not returning somthing
    rescue
      raise
    end
    false
  end

  #endregion

  #region Soft Validation

  def sv_validate_required_relationships
    object_relationships = TaxonNameRelationship.where_object_is_taxon_name(self.object_taxon_name).not_self(self).collect{|r| r.type}
    required = self.type_class.required_taxon_name_relationships - object_relationships
    required.each do |r|
      soft_validations.add(:type, " Presence of #{self.subject_status} requires selection of #{r.demodulize.underscore.humanize.downcase}")
    end
  end

  def sv_validate_disjoint_relationships
    subject_relationships = TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).not_self(self)
    subject_relationships.find_each  do |i|
      if self.type_class.disjoint_taxon_name_relationships.include?(i.type_name)
        soft_validations.add(:type, "Conflicting with another relationship: '#{i.subject_status}'")
      end
    end
  end

  def sv_validate_disjoint_object
    classifications = self.object_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
    disjoint_object_classes = self.type_class.disjoint_object_classes
    compare = disjoint_object_classes & classifications
    compare.each do |i|
      c = i.demodulize.underscore.humanize.downcase
      soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
      soft_validations.add(:object_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
    end
  end

  def sv_validate_disjoint_subject
    classifications = self.subject_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
    disjoint_subject_classes = self.type_class.disjoint_subject_classes
    compare = disjoint_subject_classes & classifications
    compare.each do |i|
      c = i.demodulize.underscore.humanize.downcase
      soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
      soft_validations.add(:subject_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
    end
  end

  # waaaaay to long - individual validations should be called in subclasses?
  def sv_specific_relationship
    s = subject_taxon_name
    o = object_taxon_name
    case type # self.type_name
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective' || 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
        if (s.type_taxon_name == o.type_taxon_name && !s.type_taxon_name.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
          soft_validations.add(:type, 'Subjective synonyms should not have the same type')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
        soft_validations.add(:type, 'Names are not similar enough to be homonyms') unless s.cached_primary_homonym_alternative_spelling == o.cached_primary_homonym_alternative_spelling
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Suppressed'
        if s.original_genus != o.original_genus
          soft_validations.add(:type, 'Primary homonyms should have the same original genus')
        elsif s.cached_primary_homonym_alternative_spelling != o.cached_primary_homonym_alternative_spelling
          soft_validations.add(:type, 'Names are not similar enough to be homonyms')
        end
        if type == 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' && s.year_of_publication > 1899
          soft_validations.add(:type, 'Taxon was not described after 1899')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'

        if s.original_genus == o.original_genus && !s.original_genus.nil?
          soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'")
        elsif s.get_valid_taxon_name.ancestor_at_rank('genus') != o.get_valid_taxon_name.ancestor_at_rank('genus')
          soft_validations.add(:type, "Secondary homonyms should be placed in the same genus, the homonymy should be deleted or changed to 'secondary homonym replaced before 1961'")
        elsif s.cached_secondary_homonym_alternative_spelling != o.cached_secondary_homonym_alternative_spelling
          soft_validations.add(:type, 'Names are not similar enough to be homonyms')
        end

        if (s.all_generic_placements & o.all_generic_placements).empty?
          soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
        end

      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary::Secondary1961'
       
        soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
        soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'") if s.original_genus == o.original_genus && !s.original_genus.nil?
        soft_validations.add(:base, 'Source is not selected') unless source 
       
        soft_validations.add(:base, 'Taxon should be treated a homonym before 1961') if self.source && self.source.year > 1960

        if (s.all_generic_placements & o.all_generic_placements).empty?
          soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
        end

      when 'TaxonNameRelationship::Iczn::PotentiallyValidating::FamilyBefore1961'
        soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
        soft_validations.add(:base, 'Taxon should be accepted as a replacement name before 1961') if self.source && self.source.year > 1960

      when 'TaxonNameRelationship::Typification::Genus::SubsequentDesignation'
        soft_validations.add(:type, 'Genus described after 1930 is nomen nudum, if type was not designated in the original publication') if o.year_of_publication > 1930
      when 'TaxonNameRelationship::Typification::Genus::Monotypy::Original'
        # @todo Check if more than one species associated with the genus in the original paper
    end
  end

  def sv_objective_synonym_relationship
    if self.type_name =~ /TaxonNameRelationship::(Iczn::Invalidating::Synonym::Objective|Icn::Unaccepting::Synonym::Homotypic|Icnb::Unaccepting::Synonym::Objective)/
      s = self.subject_taxon_name
      o = self.object_taxon_name
      if (s.type_taxon_name != o.type_taxon_name ) || !s.has_same_primary_type(o)
        soft_validations.add(:type, 'Objective synonyms should have the same type')
      end
    end
  end

  def sv_synonym_relationship
    relationships = TAXON_NAME_RELATIONSHIP_NAMES_INVALID +
        TaxonNameRelationship.collect_to_s(TaxonNameRelationship::Typification::Genus::SubsequentDesignation,
            TaxonNameRelationship::Typification::Genus::RulingByCommission)
    if relationships.include?(self.type_name)
      if self.source
        date1 = self.source.cached_nomenclature_date.to_time
        date2 = self.subject_taxon_name.nomenclature_date
        if !!date1 && !!date2
          soft_validations.add(:base, 'Taxon was not described at the time of citation') if date2 > date1
        end
      else
        soft_validations.add(:base, 'Source is not selected')
      end
    end
  end

  def sv_not_specific_relationship
    case self.type_name
      when 'TaxonNameRelationship::Typification::Genus'
        soft_validations.add(:type, 'Please specify if the type designation is original or subsequent')
      when 'TaxonNameRelationship::Typification::Genus::Monotypy'
        soft_validations.add(:type, 'Please specify if the monotypy is original or subsequent')
      when 'TaxonNameRelationship::Typification::Genus::Tautonomy'
        soft_validations.add(:type, 'Please specify if the tautonomy is absolute or Linnaean')
      when 'TaxonNameRelationship::Icn::Unaccepting'
        soft_validations.add(:type, 'Please specify the reasons for the name being Unaccepted')
      when 'TaxonNameRelationship::Icn::Unaccepting::Synonym'
        soft_validations.add(:type, 'Please specify if this is a homotypic or heterotypic synonym',
            fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being homotypic or heterotypic')
      when 'TaxonNameRelationship::Icnb::Unaccepting::Synonym'
        soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
                             fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
      when 'TaxonNameRelationship::Iczn::Invalidating'
        soft_validations.add(:type, 'Please specify the reason for the name being Invalid') unless self.subject_taxon_name.classification_invalid_or_unavailable?
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
        if NomenclaturalRank::Iczn::SpeciesGroup.descendants.collect{|t| t.to_s}.include?(self.subject_taxon_name.rank_string)
          soft_validations.add(:type, 'Please specify if this is a primary or secondary homonym',
              fix: :sv_fix_specify_homonymy_type, success_message: 'Homonym updated to being primary or secondary')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
            fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Suppression'
        soft_validations.add(:type, 'Please specify if this is a total, partial, or conditional suppression')
    end
  end

  def sv_fix_specify_synonymy_type
    s = self.subject_taxon_name
    o = self.object_taxon_name
    subject_type = s.type_taxon_name
    object_type = o.type_taxon_name
    new_relationship_name = self.type_name
    if (subject_type == object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
      if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Objective'
      else
        new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Homotypic'
      end
    elsif (subject_type != object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && !o.get_primary_type.empty? && !s.has_same_primary_type(o))
      if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective'
      else
        new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
      end
    end
    if self.type_name != new_relationship_name
      self.type = new_relationship_name
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end

    false
  end

  def sv_fix_specify_homonymy_type
    subject_original_genus = self.subject_taxon_name.original_genus
    object_original_genus = self.object_taxon_name.original_genus
    subject_genus = self.subject_taxon_name.ancestor_at_rank('genus')
    object_genus = self.subject_taxon_name.ancestor_at_rank('genus')
    new_relationship_name = 'nil'
    if subject_original_genus == object_original_genus && !subject_original_genus.nil?
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary'
    elsif subject_genus != object_genus && !subject_genus.nil?
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'
    end
    if self.type_name != new_relationship_name
      self.type = new_relationship_name
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end

  def sv_synonym_linked_to_valid_name
    #synonyms and misspellings should be linked to valid names
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      subj = self.subject_taxon_name
      if obj.get_valid_taxon_name != obj
        soft_validations.add(:object_taxon_name_id, "The #{self.subject_status} should be associated with a valid name",
                             fix: :sv_fix_synonym_linked_to_valid_name, success_message: 'The associated taxon was updated')
      elsif obj.parent_id != subj.parent_id
        soft_validations.add(:subject_taxon_name_id, "The #{self.subject_status} should have the same parent with the associated taxon",
                             fix: :sv_fix_subject_parent_update, success_message: 'The parent was updated')
      end
    end
  end

  def sv_fix_synonym_linked_to_valid_name
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      unless obj.get_valid_taxon_name == obj
        self.object_taxon_name = obj.get_valid_taxon_name
        begin
          TaxonName.transaction do
            self.save
            return true
          end
        rescue
        end
      end
    end
    false
  end

  def sv_fix_subject_parent_update
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      subj = self.subject_taxon_name
      unless obj.parent_id == subj.parent_id
        subj.parent_id = obj.parent_id
        begin
          TaxonName.transaction do
            subj.save
            return true
          end
        rescue
        end
      end
    end
    false
  end

  def sv_matching_type_genus
    if self.type_name == 'TaxonNameRelationship::Typification::Family'
      if self.object_taxon_name.name.slice(0, 1) != self.subject_taxon_name.name.slice(0, 1)
        soft_validations.add(:object_taxon_name_id, 'Type genus should have the same initial letters as the family-group name')
      end
    end
  end

  def sv_validate_priority
    unless self.type_class.nomenclatural_priority.nil?
      date1 = self.subject_taxon_name.nomenclature_date
      date2 = self.object_taxon_name.nomenclature_date
     if !!date1 and !!date2
        invalid_statuses = TAXON_NAME_CLASS_NAMES_UNAVAILABLE_AND_INVALID & self.subject_taxon_name.taxon_name_classifications.collect{|c| c.type_class.to_s}
        case self.type_class.nomenclatural_priority
          when :direct
            if date2 > date1 && invalid_statuses.empty?
              if self.type_name =~ /TaxonNameRelationship::Iczn::Invalidating::Homonym/
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon")
              elsif self.type_name =~ /::Iczn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Iczn::Invalidating::Homonym', 'TaxonNameRelationship::Iczn::Validating').not_self(self).empty?
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also a homonym or conserved")
              elsif self.type_name =~ /::Icn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Icn::Accepting::Conserved', 'TaxonNameRelationship::Icn::Accepting::Sanctioned').not_self(self).empty?
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also conserved or sanctioned")
              end
            end
          when :reverse
            if date1 > date2 && invalid_statuses.empty?
              if self.type_name =~ /TaxonNameRelationship::(Typification|Combination|OriginalCombination)/ && self.type_name != 'TaxonNameRelationship::Typification::Genus::RulingByCommission'
                soft_validations.add(:subject_taxon_name_id, "#{self.subject_status.capitalize} should not be younger than the taxon")
              else
                soft_validations.add(:type, "#{self.subject_status.capitalize} should not be younger than related taxon")
              end
            end
        end
      end
    end
  end



  def sv_coordinated_taxa
    s = self.subject_taxon_name
    o = self.object_taxon_name
    if self.type_name =~ /TaxonNameRelationship::(Iczn|Icnb|Icn)/
      s_new = s.lowest_rank_coordinated_taxon
      o_new = o.lowest_rank_coordinated_taxon


      if o != o_new && self.subject_taxon_name != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
        soft_validations.add(:object_taxon_name_id, "Relationship should move from #{o.rank_class.rank_name} to #{o_new.rank_class.rank_name}",
                             fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{o_new.rank_class.rank_name}")
      end
      if s != s_new
        soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                             fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
      end
    
    elsif self.type_name =~ /TaxonNameRelationship::(OriginalCombination|Combination|SourceClassifiedAs)/

      list = s.list_of_coordinated_names + [s]
      if s.rank_string =~ /Species/ # species group
        s_new =  list.detect{|t| t.rank_class.rank_name == 'species'}
      elsif s.rank_string =~ /Genus/
        s_new =  list.detect{|t| t.rank_class.rank_name == 'genus'}
      else
        s_new = s
      end

      # TODO: Dima fix
      return if s_new.nil?


      if s != s_new
        soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                             fix: :sv_fix_combination_relationship, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
      end
    end
  end

  def sv_fix_coordinated_taxa
    s = self.subject_taxon_name
    o = self.object_taxon_name
    s_new = s.lowest_rank_coordinated_taxon
    o_new = o.lowest_rank_coordinated_taxon
    if o != o_new || s != s_new
      self.object_taxon_name = o_new
      self.subject_taxon_name = s_new
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end

  def sv_fix_combination_relationship
    s = self.subject_taxon_name
    list = s.list_of_coordinated_names + [s]
    if s.rank_string =~ /Species/
      s_new = list.detect{|t| t.rank_class.rank_name == 'species'}
    elsif s.rank_string =~ /Genus/
      s_new = list.detect{|t| t.rank_class.rank_name == 'genus'}
    else
      s_new = s
    end
    if s != s_new && !s.nil?
      self.subject_taxon_name = s_new
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end


  #endregion

  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

  private

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

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

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

- (Integer) project_id

the project ID

Returns:

  • (Integer)


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
# File 'app/models/taxon_name_relationship.rb', line 39

class TaxonNameRelationship < ActiveRecord::Base
  include Housekeeping
  include Shared::Citable
  include Shared::IsData
  include SoftValidation

  # @return [Boolean, nil]
  #   When true, cached values are not built
  attr_accessor :no_cached

  belongs_to :subject_taxon_name, class_name: 'TaxonName', foreign_key: :subject_taxon_name_id, inverse_of: :taxon_name_relationships  # left side
  belongs_to :object_taxon_name, class_name: 'TaxonName', foreign_key: :object_taxon_name_id, inverse_of: :related_taxon_name_relationships    # right side

  after_save :set_cached_names_for_taxon_names, unless: 'self.no_cached'
  after_destroy :set_cached_names_for_taxon_names, unless: 'self.no_cached'

  validates_presence_of :type, message: 'Relationship type should be specified'
  validates_presence_of :subject_taxon_name, message: 'missing taxon name on left side'
  validates_presence_of :object_taxon_name, message: 'missing taxon name on right side'

  # TODO: these are likely not speced!  May have to change them to reference object rather than id
  validates_uniqueness_of :object_taxon_name_id, scope: :type, if: :is_combination?
  validates_uniqueness_of :object_taxon_name_id, scope: [:type, :subject_taxon_name_id], unless: :is_combination?

  validate :validate_type, :validate_subject_and_object_are_not_identical

  with_options unless: '!subject_taxon_name || !object_taxon_name' do |v|
    v.validate :validate_subject_and_object_share_code,
      :validate_uniqueness_of_typification_object,
      #:validate_uniqueness_of_synonym_subject,
      :validate_object_must_equal_subject_for_uncertain_placement,
      :validate_subject_and_object_ranks,
      :validate_rank_group
  end

  soft_validate(:sv_validate_required_relationships, set: :validate_required_relationships)
  soft_validate(:sv_validate_disjoint_relationships, set: :validate_disjoint_relationships)
  soft_validate(:sv_validate_disjoint_object, set: :validate_disjoint_object)
  soft_validate(:sv_validate_disjoint_subject, set: :validate_disjoint_subject)
  soft_validate(:sv_specific_relationship, set: :specific_relationship)
  soft_validate(:sv_objective_synonym_relationship, set: :objective_synonym_relationship)
  soft_validate(:sv_synonym_relationship, set: :synonym_relationship)
  soft_validate(:sv_not_specific_relationship, set: :not_specific_relationship)
  soft_validate(:sv_synonym_linked_to_valid_name, set: :synonym_linked_to_valid_name)
  soft_validate(:sv_matching_type_genus, set: :matching_type_genus)
  soft_validate(:sv_validate_priority, set: :validate_priority)

  soft_validate(:sv_coordinated_taxa, set: :coordinated_taxa)

  scope :where_subject_is_taxon_name, -> (taxon_name) {where(subject_taxon_name_id: taxon_name)}
  scope :where_object_is_taxon_name, -> (taxon_name) {where(object_taxon_name_id: taxon_name)}
  scope :where_object_in_taxon_names, -> (taxon_name_array) {where('"taxon_name_relationships"."object_taxon_name_id" IN (?)', taxon_name_array)}
#  scope :with_type_string, -> (type_string) {where('"taxon_name_relationships"."type" LIKE ?', "#{type_string}" ) }

  scope :with_type_string, -> (type_string) { where(sanitize_sql_array(["taxon_name_relationships.type = '%s'", type_string])) } #   #{?type_string}"where('"taxon_name_relationships"."type" LIKE ?', "#{type_string}" ) }

  scope :with_type_base,     -> (base_string) {where('"taxon_name_relationships"."type" LIKE ?', "#{base_string}%" ) } 
  scope :with_type_contains, -> (base_string) {where('"taxon_name_relationships"."type" LIKE ?', "%#{base_string}%" ) } 

  scope :with_two_type_bases, -> (base_string1, base_string2) {where("taxon_name_relationships.type LIKE '#{base_string1}%' OR taxon_name_relationships.type LIKE '#{base_string2}%'" ) }
  scope :with_type_array, -> (base_array) {where('taxon_name_relationships.type IN (?)', base_array ) }

  # @return [Array of TaxonNameClassification]
  #  the inferable TaxonNameClassification(s) added to the subject when this relationship is used
  #  !! Not implemented 
  def subject_properties
    []
  end

  # @return [Array of TaxonNameClassification]
  #  the inferable TaxonNameClassification(s) added to the subject when this relationship is used
  #  !! Not implmented
  def object_properties
    []
  end

  # @return [Array of NomenclatureRank]
  #   the valid ranks to which the subject name can belong, set in subclasses. (left side)
  def self.valid_subject_ranks
    []
  end


  # @return [Array of NomenclatureRank]
  #   the valid ranks to which the object name can belong, set in subclasses. (right side)
  def self.valid_object_ranks
    []
  end

  # @return [Array of TaxonNameRelationships]
  #   if this relationships is set for the subject, then others in this array should not be used for that subject
  def self.disjoint_taxon_name_relationships
    []
  end

  # TODO: why isn't this disjoint?
  # disjoint relationships for the taxon as a object
  def self.required_taxon_name_relationships
    []
  end

  def self.disjoint_subject_classes
    []
  end

  def self.disjoint_object_classes
    []
  end

  def self.gbif_status_of_subject
    nil
  end

  def self.gbif_status_of_object
    nil
  end

  def self.assignable
    false
  end

  # @return [Symbol]
  #   determine the relative age of subject and object
  #   :direct - subject is younger than object
  #   :reverse - object is younger than subject
  def self.nomenclatural_priority
    nil 
  end

  # @return [String]
  #    the status inferred by the relationship to the object name 
  def object_status
    self.type_name.demodulize.underscore.humanize.downcase
  end

  # @return [String]
  #    the status inferred by the relationship to the subject name 
  def subject_status
    self.type_name.demodulize.underscore.humanize.downcase
  end

  # @return [String]
  #    the connecting word in the relationship from the subject name to object name
  def subject_status_connector_to_object
    ' of'
  end

  # @return [String]
  #    the connecting word in the relationship from the object name to subject name
  def object_status_connector_to_subject
    ' of'
  end

  # @return [String]
  #   a readable fragement combining status and connector
  def subject_status_tag
    subject_status + subject_status_connector_to_object
  end

  # @return [String]
  #   a readable fragement combining status and connector
  def object_status_tag
    object_status + object_status_connector_to_subject
  end

  # @return [String, nil]
  #   the type of this relationship, IF the type is a valid name, else nil
  def type_name
    r = self.type
    TAXON_NAME_RELATIONSHIP_NAMES.include?(r) ? r : nil
  end

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

  # @return [TaxonNameRelationship, String]
  #    the type as a class, if legal, else a string  ! Strangeish
  def type_class
    r = read_attribute(:type).to_s
    TAXON_NAME_RELATIONSHIP_NAMES.include?(r) ? r.safe_constantize : r
  end

  # TODO: match on either name
  def self.find_for_autocomplete(params)
    where(id: params[:term]).with_project_id(params[:project_id])
  end

  # @return [String, nil]
  #   the NOMEN uri for this type
  def self.nomen_uri
    const_defined?(:NOMEN_URI, false) ? self::NOMEN_URI : nil
  end

  # @return [Time]
  #   effective date of publication, used to determine nomenclatural priorities
  def nomenclature_date
    self.source ? (self.source.cached_nomenclature_date ? self.source.cached_nomenclature_date.to_time : Time.now) : Time.now
  end

  # @todo SourceClassifiedAs is not really Combination in the other sense
  def is_combination?
    !!/TaxonNameRelationship::(OriginalCombination|Combination)/.match(self.type.to_s)
  end

  protected

  #region Validation

  def validate_type
    unless TAXON_NAME_RELATIONSHIP_NAMES.include?(type)
      errors.add(:type, "'#{type}' is not a valid taxon name relationship")
    end

    if object_taxon_name.class.to_s == 'Protonym' || object_taxon_name.class.to_s == 'Hybrid'
      errors.add(:type, "'#{type}' is not a valid taxon name relationship") if /TaxonNameRelationship::Combination::/.match(self.type)
    end

    if object_taxon_name.class.to_s == 'Combination'
      errors.add(:type, "'#{type}' is not a valid taxon name relationship") unless /TaxonNameRelationship::Combination::/.match(self.type)
    end
  end

  # @todo validate, that all the relationships in the table could be linked to relationships in classes (if those had changed)

  def validate_subject_and_object_share_code
    if object_taxon_name.type  == 'Protonym' && subject_taxon_name.type == 'Protonym'
      errors.add(:object_taxon_name_id, 'The related taxon is from different potentially_validating code') if subject_taxon_name.rank_class.nomenclatural_code != object_taxon_name.rank_class.nomenclatural_code
    end
  end

  def validate_subject_and_object_are_not_identical
    if self.object_taxon_name == self.subject_taxon_name
      errors.add(:object_taxon_name, 'Taxon should not refer to itself') unless self.type =~ /OriginalCombination/
    end
  end

  def validate_object_must_equal_subject_for_uncertain_placement
    if self.object_taxon_name_id != self.subject_taxon_name.parent_id && self.type_name == 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      errors.add(:object_taxon_name_id, 'The parent and related taxon should match')
    end
  end

  def validate_subject_and_object_ranks
    tname = self.type_name

    if tname =~ /TaxonNameRelationship::(Icnb|Icn|Iczn)/ && tname != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      rank_group = self.subject_taxon_name.rank_class.parent
      unless rank_group == self.object_taxon_name.rank_class.parent
        errors.add(:object_taxon_name_id, "Rank of related taxon should be in the #{rank_group.rank_name}")
      end
    end

    unless self.type_class.blank? # only validate if it is set
      if object_taxon_name
        if object_taxon_name.type == 'Protonym' || object_taxon_name.type == 'Hybrid'
          unless self.type_class.valid_object_ranks.include?(self.object_taxon_name.rank_string)
            errors.add(:object_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
            errors.add(:type, 'Not compatible with the rank of object taxon')
          end
        end
      end

      if subject_taxon_name
        if subject_taxon_name.type == 'Protonym' || subject_taxon_name.type == 'Hybrid'
          unless self.type_class.valid_subject_ranks.include?(self.subject_taxon_name.parent.rank_string)
            soft_validations.add(:subject_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
            soft_validations.add(:type, 'Not compatible with the rank of subject taxon')
          end
        end
      end
    end
  end

  #def validate_uniqueness_of_synonym_subject ##### Protonym historically could be listed as a synonym to different taxa
  #  if !self.type.nil? && /Synonym/.match(self.type_name) && !TaxonNameRelationship.where(subject_taxon_name_id: self.subject_taxon_name_id).with_type_contains('Synonym').not_self(self).empty?
  #    errors.add(:subject_taxon_name_id, 'Only one synonym relationship is allowed')
  #  end
  #end

  def validate_uniqueness_of_typification_object
    if /Typification/.match(self.type_name) && !TaxonNameRelationship.where(object_taxon_name_id: self.object_taxon_name_id).with_type_contains('Typification').not_self(self).empty?
      errors.add(:object_taxon_name_id, 'Only one type relationship is allowed')
    end
  end

  def validate_rank_group
    if self.type =~ /Hybrid/ && self.subject_taxon_name && self.object_taxon_name
      if self.subject_taxon_name.rank_class.parent != self.object_taxon_name.rank_class.parent
        errors.add(:subject_taxon_name_id, "Rank of taxon (#{self.subject_taxon_name.rank_class.rank_name}) is not compatible with the rank of hybrid (#{self.object_taxon_name.rank_class.rank_name})")
      end
    end
  end

  def set_cached_names_for_taxon_names
    dependants = []
    begin
      TaxonName.transaction do

       #if self.type_name =~/OriginalCombination/
       #  t = self.object_taxon_name
       #  t.update_columns(:cached_original_combination => t.get_original_combination,
       #                   :cached_primary_homonym => t.get_genus_species(:original, :self),
       #                   :cached_primary_homonym_alternative_spelling => t.get_genus_species(:original, :alternative))
        if self.is_combination?
          t = self.object_taxon_name
          t.update_columns(:cached_original_combination => t.get_original_combination,
                           :cached => t.get_full_name,
                           :cached_html => t.get_full_name_html,
                           :cached_author_year => t.get_author_and_year,
                           :cached_valid_taxon_name_id => t.get_valid_taxon_name.id)
#        elsif self.type_name =~/Misspelling/
#          t = self.subject_taxon_name
#          t.update_column(:cached_misspelling, t.get_cached_misspelling)
        elsif self.type_name =~/TaxonNameRelationship::Hybrid/
          t = self.object_taxon_name
          t.update_columns(:cached => t.get_full_name,
                           :cached_html => t.get_full_name_html)
        elsif self.type_name =~/SourceClassifiedAs/
          t = self.subject_taxon_name
          t.update_column(:cached_classified_as, t.get_cached_classified_as)
        elsif TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
          t = self.subject_taxon_name
          if self.type_name =~/Misspelling/
            t.update_column(:cached_misspelling, t.get_cached_misspelling)
          end
          t.update_columns(:cached => t.get_full_name,
                           :cached_html => t.get_full_name_html)
          vn = t.get_valid_taxon_name
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_column(:cached_valid_taxon_name_id, vn.id)
            s.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end
          end
        end
      end

    # no point in rescuing and not returning somthing
    rescue
      raise
    end
    false
  end

  #endregion

  #region Soft Validation

  def sv_validate_required_relationships
    object_relationships = TaxonNameRelationship.where_object_is_taxon_name(self.object_taxon_name).not_self(self).collect{|r| r.type}
    required = self.type_class.required_taxon_name_relationships - object_relationships
    required.each do |r|
      soft_validations.add(:type, " Presence of #{self.subject_status} requires selection of #{r.demodulize.underscore.humanize.downcase}")
    end
  end

  def sv_validate_disjoint_relationships
    subject_relationships = TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).not_self(self)
    subject_relationships.find_each  do |i|
      if self.type_class.disjoint_taxon_name_relationships.include?(i.type_name)
        soft_validations.add(:type, "Conflicting with another relationship: '#{i.subject_status}'")
      end
    end
  end

  def sv_validate_disjoint_object
    classifications = self.object_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
    disjoint_object_classes = self.type_class.disjoint_object_classes
    compare = disjoint_object_classes & classifications
    compare.each do |i|
      c = i.demodulize.underscore.humanize.downcase
      soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
      soft_validations.add(:object_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
    end
  end

  def sv_validate_disjoint_subject
    classifications = self.subject_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
    disjoint_subject_classes = self.type_class.disjoint_subject_classes
    compare = disjoint_subject_classes & classifications
    compare.each do |i|
      c = i.demodulize.underscore.humanize.downcase
      soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
      soft_validations.add(:subject_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
    end
  end

  # waaaaay to long - individual validations should be called in subclasses?
  def sv_specific_relationship
    s = subject_taxon_name
    o = object_taxon_name
    case type # self.type_name
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective' || 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
        if (s.type_taxon_name == o.type_taxon_name && !s.type_taxon_name.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
          soft_validations.add(:type, 'Subjective synonyms should not have the same type')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
        soft_validations.add(:type, 'Names are not similar enough to be homonyms') unless s.cached_primary_homonym_alternative_spelling == o.cached_primary_homonym_alternative_spelling
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Suppressed'
        if s.original_genus != o.original_genus
          soft_validations.add(:type, 'Primary homonyms should have the same original genus')
        elsif s.cached_primary_homonym_alternative_spelling != o.cached_primary_homonym_alternative_spelling
          soft_validations.add(:type, 'Names are not similar enough to be homonyms')
        end
        if type == 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' && s.year_of_publication > 1899
          soft_validations.add(:type, 'Taxon was not described after 1899')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'

        if s.original_genus == o.original_genus && !s.original_genus.nil?
          soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'")
        elsif s.get_valid_taxon_name.ancestor_at_rank('genus') != o.get_valid_taxon_name.ancestor_at_rank('genus')
          soft_validations.add(:type, "Secondary homonyms should be placed in the same genus, the homonymy should be deleted or changed to 'secondary homonym replaced before 1961'")
        elsif s.cached_secondary_homonym_alternative_spelling != o.cached_secondary_homonym_alternative_spelling
          soft_validations.add(:type, 'Names are not similar enough to be homonyms')
        end

        if (s.all_generic_placements & o.all_generic_placements).empty?
          soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
        end

      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary::Secondary1961'
       
        soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
        soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'") if s.original_genus == o.original_genus && !s.original_genus.nil?
        soft_validations.add(:base, 'Source is not selected') unless source 
       
        soft_validations.add(:base, 'Taxon should be treated a homonym before 1961') if self.source && self.source.year > 1960

        if (s.all_generic_placements & o.all_generic_placements).empty?
          soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
        end

      when 'TaxonNameRelationship::Iczn::PotentiallyValidating::FamilyBefore1961'
        soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
        soft_validations.add(:base, 'Taxon should be accepted as a replacement name before 1961') if self.source && self.source.year > 1960

      when 'TaxonNameRelationship::Typification::Genus::SubsequentDesignation'
        soft_validations.add(:type, 'Genus described after 1930 is nomen nudum, if type was not designated in the original publication') if o.year_of_publication > 1930
      when 'TaxonNameRelationship::Typification::Genus::Monotypy::Original'
        # @todo Check if more than one species associated with the genus in the original paper
    end
  end

  def sv_objective_synonym_relationship
    if self.type_name =~ /TaxonNameRelationship::(Iczn::Invalidating::Synonym::Objective|Icn::Unaccepting::Synonym::Homotypic|Icnb::Unaccepting::Synonym::Objective)/
      s = self.subject_taxon_name
      o = self.object_taxon_name
      if (s.type_taxon_name != o.type_taxon_name ) || !s.has_same_primary_type(o)
        soft_validations.add(:type, 'Objective synonyms should have the same type')
      end
    end
  end

  def sv_synonym_relationship
    relationships = TAXON_NAME_RELATIONSHIP_NAMES_INVALID +
        TaxonNameRelationship.collect_to_s(TaxonNameRelationship::Typification::Genus::SubsequentDesignation,
            TaxonNameRelationship::Typification::Genus::RulingByCommission)
    if relationships.include?(self.type_name)
      if self.source
        date1 = self.source.cached_nomenclature_date.to_time
        date2 = self.subject_taxon_name.nomenclature_date
        if !!date1 && !!date2
          soft_validations.add(:base, 'Taxon was not described at the time of citation') if date2 > date1
        end
      else
        soft_validations.add(:base, 'Source is not selected')
      end
    end
  end

  def sv_not_specific_relationship
    case self.type_name
      when 'TaxonNameRelationship::Typification::Genus'
        soft_validations.add(:type, 'Please specify if the type designation is original or subsequent')
      when 'TaxonNameRelationship::Typification::Genus::Monotypy'
        soft_validations.add(:type, 'Please specify if the monotypy is original or subsequent')
      when 'TaxonNameRelationship::Typification::Genus::Tautonomy'
        soft_validations.add(:type, 'Please specify if the tautonomy is absolute or Linnaean')
      when 'TaxonNameRelationship::Icn::Unaccepting'
        soft_validations.add(:type, 'Please specify the reasons for the name being Unaccepted')
      when 'TaxonNameRelationship::Icn::Unaccepting::Synonym'
        soft_validations.add(:type, 'Please specify if this is a homotypic or heterotypic synonym',
            fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being homotypic or heterotypic')
      when 'TaxonNameRelationship::Icnb::Unaccepting::Synonym'
        soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
                             fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
      when 'TaxonNameRelationship::Iczn::Invalidating'
        soft_validations.add(:type, 'Please specify the reason for the name being Invalid') unless self.subject_taxon_name.classification_invalid_or_unavailable?
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
        if NomenclaturalRank::Iczn::SpeciesGroup.descendants.collect{|t| t.to_s}.include?(self.subject_taxon_name.rank_string)
          soft_validations.add(:type, 'Please specify if this is a primary or secondary homonym',
              fix: :sv_fix_specify_homonymy_type, success_message: 'Homonym updated to being primary or secondary')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
            fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Suppression'
        soft_validations.add(:type, 'Please specify if this is a total, partial, or conditional suppression')
    end
  end

  def sv_fix_specify_synonymy_type
    s = self.subject_taxon_name
    o = self.object_taxon_name
    subject_type = s.type_taxon_name
    object_type = o.type_taxon_name
    new_relationship_name = self.type_name
    if (subject_type == object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
      if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Objective'
      else
        new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Homotypic'
      end
    elsif (subject_type != object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && !o.get_primary_type.empty? && !s.has_same_primary_type(o))
      if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective'
      else
        new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
      end
    end
    if self.type_name != new_relationship_name
      self.type = new_relationship_name
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end

    false
  end

  def sv_fix_specify_homonymy_type
    subject_original_genus = self.subject_taxon_name.original_genus
    object_original_genus = self.object_taxon_name.original_genus
    subject_genus = self.subject_taxon_name.ancestor_at_rank('genus')
    object_genus = self.subject_taxon_name.ancestor_at_rank('genus')
    new_relationship_name = 'nil'
    if subject_original_genus == object_original_genus && !subject_original_genus.nil?
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary'
    elsif subject_genus != object_genus && !subject_genus.nil?
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'
    end
    if self.type_name != new_relationship_name
      self.type = new_relationship_name
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end

  def sv_synonym_linked_to_valid_name
    #synonyms and misspellings should be linked to valid names
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      subj = self.subject_taxon_name
      if obj.get_valid_taxon_name != obj
        soft_validations.add(:object_taxon_name_id, "The #{self.subject_status} should be associated with a valid name",
                             fix: :sv_fix_synonym_linked_to_valid_name, success_message: 'The associated taxon was updated')
      elsif obj.parent_id != subj.parent_id
        soft_validations.add(:subject_taxon_name_id, "The #{self.subject_status} should have the same parent with the associated taxon",
                             fix: :sv_fix_subject_parent_update, success_message: 'The parent was updated')
      end
    end
  end

  def sv_fix_synonym_linked_to_valid_name
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      unless obj.get_valid_taxon_name == obj
        self.object_taxon_name = obj.get_valid_taxon_name
        begin
          TaxonName.transaction do
            self.save
            return true
          end
        rescue
        end
      end
    end
    false
  end

  def sv_fix_subject_parent_update
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      subj = self.subject_taxon_name
      unless obj.parent_id == subj.parent_id
        subj.parent_id = obj.parent_id
        begin
          TaxonName.transaction do
            subj.save
            return true
          end
        rescue
        end
      end
    end
    false
  end

  def sv_matching_type_genus
    if self.type_name == 'TaxonNameRelationship::Typification::Family'
      if self.object_taxon_name.name.slice(0, 1) != self.subject_taxon_name.name.slice(0, 1)
        soft_validations.add(:object_taxon_name_id, 'Type genus should have the same initial letters as the family-group name')
      end
    end
  end

  def sv_validate_priority
    unless self.type_class.nomenclatural_priority.nil?
      date1 = self.subject_taxon_name.nomenclature_date
      date2 = self.object_taxon_name.nomenclature_date
     if !!date1 and !!date2
        invalid_statuses = TAXON_NAME_CLASS_NAMES_UNAVAILABLE_AND_INVALID & self.subject_taxon_name.taxon_name_classifications.collect{|c| c.type_class.to_s}
        case self.type_class.nomenclatural_priority
          when :direct
            if date2 > date1 && invalid_statuses.empty?
              if self.type_name =~ /TaxonNameRelationship::Iczn::Invalidating::Homonym/
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon")
              elsif self.type_name =~ /::Iczn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Iczn::Invalidating::Homonym', 'TaxonNameRelationship::Iczn::Validating').not_self(self).empty?
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also a homonym or conserved")
              elsif self.type_name =~ /::Icn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Icn::Accepting::Conserved', 'TaxonNameRelationship::Icn::Accepting::Sanctioned').not_self(self).empty?
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also conserved or sanctioned")
              end
            end
          when :reverse
            if date1 > date2 && invalid_statuses.empty?
              if self.type_name =~ /TaxonNameRelationship::(Typification|Combination|OriginalCombination)/ && self.type_name != 'TaxonNameRelationship::Typification::Genus::RulingByCommission'
                soft_validations.add(:subject_taxon_name_id, "#{self.subject_status.capitalize} should not be younger than the taxon")
              else
                soft_validations.add(:type, "#{self.subject_status.capitalize} should not be younger than related taxon")
              end
            end
        end
      end
    end
  end



  def sv_coordinated_taxa
    s = self.subject_taxon_name
    o = self.object_taxon_name
    if self.type_name =~ /TaxonNameRelationship::(Iczn|Icnb|Icn)/
      s_new = s.lowest_rank_coordinated_taxon
      o_new = o.lowest_rank_coordinated_taxon


      if o != o_new && self.subject_taxon_name != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
        soft_validations.add(:object_taxon_name_id, "Relationship should move from #{o.rank_class.rank_name} to #{o_new.rank_class.rank_name}",
                             fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{o_new.rank_class.rank_name}")
      end
      if s != s_new
        soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                             fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
      end
    
    elsif self.type_name =~ /TaxonNameRelationship::(OriginalCombination|Combination|SourceClassifiedAs)/

      list = s.list_of_coordinated_names + [s]
      if s.rank_string =~ /Species/ # species group
        s_new =  list.detect{|t| t.rank_class.rank_name == 'species'}
      elsif s.rank_string =~ /Genus/
        s_new =  list.detect{|t| t.rank_class.rank_name == 'genus'}
      else
        s_new = s
      end

      # TODO: Dima fix
      return if s_new.nil?


      if s != s_new
        soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                             fix: :sv_fix_combination_relationship, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
      end
    end
  end

  def sv_fix_coordinated_taxa
    s = self.subject_taxon_name
    o = self.object_taxon_name
    s_new = s.lowest_rank_coordinated_taxon
    o_new = o.lowest_rank_coordinated_taxon
    if o != o_new || s != s_new
      self.object_taxon_name = o_new
      self.subject_taxon_name = s_new
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end

  def sv_fix_combination_relationship
    s = self.subject_taxon_name
    list = s.list_of_coordinated_names + [s]
    if s.rank_string =~ /Species/
      s_new = list.detect{|t| t.rank_class.rank_name == 'species'}
    elsif s.rank_string =~ /Genus/
      s_new = list.detect{|t| t.rank_class.rank_name == 'genus'}
    else
      s_new = s
    end
    if s != s_new && !s.nil?
      self.subject_taxon_name = s_new
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end


  #endregion

  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

  private

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

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

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

- (Integer) subject_taxon_name_id

Returns the subject taxon name

Returns:

  • (Integer)

    the subject taxon name



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
# File 'app/models/taxon_name_relationship.rb', line 39

class TaxonNameRelationship < ActiveRecord::Base
  include Housekeeping
  include Shared::Citable
  include Shared::IsData
  include SoftValidation

  # @return [Boolean, nil]
  #   When true, cached values are not built
  attr_accessor :no_cached

  belongs_to :subject_taxon_name, class_name: 'TaxonName', foreign_key: :subject_taxon_name_id, inverse_of: :taxon_name_relationships  # left side
  belongs_to :object_taxon_name, class_name: 'TaxonName', foreign_key: :object_taxon_name_id, inverse_of: :related_taxon_name_relationships    # right side

  after_save :set_cached_names_for_taxon_names, unless: 'self.no_cached'
  after_destroy :set_cached_names_for_taxon_names, unless: 'self.no_cached'

  validates_presence_of :type, message: 'Relationship type should be specified'
  validates_presence_of :subject_taxon_name, message: 'missing taxon name on left side'
  validates_presence_of :object_taxon_name, message: 'missing taxon name on right side'

  # TODO: these are likely not speced!  May have to change them to reference object rather than id
  validates_uniqueness_of :object_taxon_name_id, scope: :type, if: :is_combination?
  validates_uniqueness_of :object_taxon_name_id, scope: [:type, :subject_taxon_name_id], unless: :is_combination?

  validate :validate_type, :validate_subject_and_object_are_not_identical

  with_options unless: '!subject_taxon_name || !object_taxon_name' do |v|
    v.validate :validate_subject_and_object_share_code,
      :validate_uniqueness_of_typification_object,
      #:validate_uniqueness_of_synonym_subject,
      :validate_object_must_equal_subject_for_uncertain_placement,
      :validate_subject_and_object_ranks,
      :validate_rank_group
  end

  soft_validate(:sv_validate_required_relationships, set: :validate_required_relationships)
  soft_validate(:sv_validate_disjoint_relationships, set: :validate_disjoint_relationships)
  soft_validate(:sv_validate_disjoint_object, set: :validate_disjoint_object)
  soft_validate(:sv_validate_disjoint_subject, set: :validate_disjoint_subject)
  soft_validate(:sv_specific_relationship, set: :specific_relationship)
  soft_validate(:sv_objective_synonym_relationship, set: :objective_synonym_relationship)
  soft_validate(:sv_synonym_relationship, set: :synonym_relationship)
  soft_validate(:sv_not_specific_relationship, set: :not_specific_relationship)
  soft_validate(:sv_synonym_linked_to_valid_name, set: :synonym_linked_to_valid_name)
  soft_validate(:sv_matching_type_genus, set: :matching_type_genus)
  soft_validate(:sv_validate_priority, set: :validate_priority)

  soft_validate(:sv_coordinated_taxa, set: :coordinated_taxa)

  scope :where_subject_is_taxon_name, -> (taxon_name) {where(subject_taxon_name_id: taxon_name)}
  scope :where_object_is_taxon_name, -> (taxon_name) {where(object_taxon_name_id: taxon_name)}
  scope :where_object_in_taxon_names, -> (taxon_name_array) {where('"taxon_name_relationships"."object_taxon_name_id" IN (?)', taxon_name_array)}
#  scope :with_type_string, -> (type_string) {where('"taxon_name_relationships"."type" LIKE ?', "#{type_string}" ) }

  scope :with_type_string, -> (type_string) { where(sanitize_sql_array(["taxon_name_relationships.type = '%s'", type_string])) } #   #{?type_string}"where('"taxon_name_relationships"."type" LIKE ?', "#{type_string}" ) }

  scope :with_type_base,     -> (base_string) {where('"taxon_name_relationships"."type" LIKE ?', "#{base_string}%" ) } 
  scope :with_type_contains, -> (base_string) {where('"taxon_name_relationships"."type" LIKE ?', "%#{base_string}%" ) } 

  scope :with_two_type_bases, -> (base_string1, base_string2) {where("taxon_name_relationships.type LIKE '#{base_string1}%' OR taxon_name_relationships.type LIKE '#{base_string2}%'" ) }
  scope :with_type_array, -> (base_array) {where('taxon_name_relationships.type IN (?)', base_array ) }

  # @return [Array of TaxonNameClassification]
  #  the inferable TaxonNameClassification(s) added to the subject when this relationship is used
  #  !! Not implemented 
  def subject_properties
    []
  end

  # @return [Array of TaxonNameClassification]
  #  the inferable TaxonNameClassification(s) added to the subject when this relationship is used
  #  !! Not implmented
  def object_properties
    []
  end

  # @return [Array of NomenclatureRank]
  #   the valid ranks to which the subject name can belong, set in subclasses. (left side)
  def self.valid_subject_ranks
    []
  end


  # @return [Array of NomenclatureRank]
  #   the valid ranks to which the object name can belong, set in subclasses. (right side)
  def self.valid_object_ranks
    []
  end

  # @return [Array of TaxonNameRelationships]
  #   if this relationships is set for the subject, then others in this array should not be used for that subject
  def self.disjoint_taxon_name_relationships
    []
  end

  # TODO: why isn't this disjoint?
  # disjoint relationships for the taxon as a object
  def self.required_taxon_name_relationships
    []
  end

  def self.disjoint_subject_classes
    []
  end

  def self.disjoint_object_classes
    []
  end

  def self.gbif_status_of_subject
    nil
  end

  def self.gbif_status_of_object
    nil
  end

  def self.assignable
    false
  end

  # @return [Symbol]
  #   determine the relative age of subject and object
  #   :direct - subject is younger than object
  #   :reverse - object is younger than subject
  def self.nomenclatural_priority
    nil 
  end

  # @return [String]
  #    the status inferred by the relationship to the object name 
  def object_status
    self.type_name.demodulize.underscore.humanize.downcase
  end

  # @return [String]
  #    the status inferred by the relationship to the subject name 
  def subject_status
    self.type_name.demodulize.underscore.humanize.downcase
  end

  # @return [String]
  #    the connecting word in the relationship from the subject name to object name
  def subject_status_connector_to_object
    ' of'
  end

  # @return [String]
  #    the connecting word in the relationship from the object name to subject name
  def object_status_connector_to_subject
    ' of'
  end

  # @return [String]
  #   a readable fragement combining status and connector
  def subject_status_tag
    subject_status + subject_status_connector_to_object
  end

  # @return [String]
  #   a readable fragement combining status and connector
  def object_status_tag
    object_status + object_status_connector_to_subject
  end

  # @return [String, nil]
  #   the type of this relationship, IF the type is a valid name, else nil
  def type_name
    r = self.type
    TAXON_NAME_RELATIONSHIP_NAMES.include?(r) ? r : nil
  end

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

  # @return [TaxonNameRelationship, String]
  #    the type as a class, if legal, else a string  ! Strangeish
  def type_class
    r = read_attribute(:type).to_s
    TAXON_NAME_RELATIONSHIP_NAMES.include?(r) ? r.safe_constantize : r
  end

  # TODO: match on either name
  def self.find_for_autocomplete(params)
    where(id: params[:term]).with_project_id(params[:project_id])
  end

  # @return [String, nil]
  #   the NOMEN uri for this type
  def self.nomen_uri
    const_defined?(:NOMEN_URI, false) ? self::NOMEN_URI : nil
  end

  # @return [Time]
  #   effective date of publication, used to determine nomenclatural priorities
  def nomenclature_date
    self.source ? (self.source.cached_nomenclature_date ? self.source.cached_nomenclature_date.to_time : Time.now) : Time.now
  end

  # @todo SourceClassifiedAs is not really Combination in the other sense
  def is_combination?
    !!/TaxonNameRelationship::(OriginalCombination|Combination)/.match(self.type.to_s)
  end

  protected

  #region Validation

  def validate_type
    unless TAXON_NAME_RELATIONSHIP_NAMES.include?(type)
      errors.add(:type, "'#{type}' is not a valid taxon name relationship")
    end

    if object_taxon_name.class.to_s == 'Protonym' || object_taxon_name.class.to_s == 'Hybrid'
      errors.add(:type, "'#{type}' is not a valid taxon name relationship") if /TaxonNameRelationship::Combination::/.match(self.type)
    end

    if object_taxon_name.class.to_s == 'Combination'
      errors.add(:type, "'#{type}' is not a valid taxon name relationship") unless /TaxonNameRelationship::Combination::/.match(self.type)
    end
  end

  # @todo validate, that all the relationships in the table could be linked to relationships in classes (if those had changed)

  def validate_subject_and_object_share_code
    if object_taxon_name.type  == 'Protonym' && subject_taxon_name.type == 'Protonym'
      errors.add(:object_taxon_name_id, 'The related taxon is from different potentially_validating code') if subject_taxon_name.rank_class.nomenclatural_code != object_taxon_name.rank_class.nomenclatural_code
    end
  end

  def validate_subject_and_object_are_not_identical
    if self.object_taxon_name == self.subject_taxon_name
      errors.add(:object_taxon_name, 'Taxon should not refer to itself') unless self.type =~ /OriginalCombination/
    end
  end

  def validate_object_must_equal_subject_for_uncertain_placement
    if self.object_taxon_name_id != self.subject_taxon_name.parent_id && self.type_name == 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      errors.add(:object_taxon_name_id, 'The parent and related taxon should match')
    end
  end

  def validate_subject_and_object_ranks
    tname = self.type_name

    if tname =~ /TaxonNameRelationship::(Icnb|Icn|Iczn)/ && tname != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      rank_group = self.subject_taxon_name.rank_class.parent
      unless rank_group == self.object_taxon_name.rank_class.parent
        errors.add(:object_taxon_name_id, "Rank of related taxon should be in the #{rank_group.rank_name}")
      end
    end

    unless self.type_class.blank? # only validate if it is set
      if object_taxon_name
        if object_taxon_name.type == 'Protonym' || object_taxon_name.type == 'Hybrid'
          unless self.type_class.valid_object_ranks.include?(self.object_taxon_name.rank_string)
            errors.add(:object_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
            errors.add(:type, 'Not compatible with the rank of object taxon')
          end
        end
      end

      if subject_taxon_name
        if subject_taxon_name.type == 'Protonym' || subject_taxon_name.type == 'Hybrid'
          unless self.type_class.valid_subject_ranks.include?(self.subject_taxon_name.parent.rank_string)
            soft_validations.add(:subject_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
            soft_validations.add(:type, 'Not compatible with the rank of subject taxon')
          end
        end
      end
    end
  end

  #def validate_uniqueness_of_synonym_subject ##### Protonym historically could be listed as a synonym to different taxa
  #  if !self.type.nil? && /Synonym/.match(self.type_name) && !TaxonNameRelationship.where(subject_taxon_name_id: self.subject_taxon_name_id).with_type_contains('Synonym').not_self(self).empty?
  #    errors.add(:subject_taxon_name_id, 'Only one synonym relationship is allowed')
  #  end
  #end

  def validate_uniqueness_of_typification_object
    if /Typification/.match(self.type_name) && !TaxonNameRelationship.where(object_taxon_name_id: self.object_taxon_name_id).with_type_contains('Typification').not_self(self).empty?
      errors.add(:object_taxon_name_id, 'Only one type relationship is allowed')
    end
  end

  def validate_rank_group
    if self.type =~ /Hybrid/ && self.subject_taxon_name && self.object_taxon_name
      if self.subject_taxon_name.rank_class.parent != self.object_taxon_name.rank_class.parent
        errors.add(:subject_taxon_name_id, "Rank of taxon (#{self.subject_taxon_name.rank_class.rank_name}) is not compatible with the rank of hybrid (#{self.object_taxon_name.rank_class.rank_name})")
      end
    end
  end

  def set_cached_names_for_taxon_names
    dependants = []
    begin
      TaxonName.transaction do

       #if self.type_name =~/OriginalCombination/
       #  t = self.object_taxon_name
       #  t.update_columns(:cached_original_combination => t.get_original_combination,
       #                   :cached_primary_homonym => t.get_genus_species(:original, :self),
       #                   :cached_primary_homonym_alternative_spelling => t.get_genus_species(:original, :alternative))
        if self.is_combination?
          t = self.object_taxon_name
          t.update_columns(:cached_original_combination => t.get_original_combination,
                           :cached => t.get_full_name,
                           :cached_html => t.get_full_name_html,
                           :cached_author_year => t.get_author_and_year,
                           :cached_valid_taxon_name_id => t.get_valid_taxon_name.id)
#        elsif self.type_name =~/Misspelling/
#          t = self.subject_taxon_name
#          t.update_column(:cached_misspelling, t.get_cached_misspelling)
        elsif self.type_name =~/TaxonNameRelationship::Hybrid/
          t = self.object_taxon_name
          t.update_columns(:cached => t.get_full_name,
                           :cached_html => t.get_full_name_html)
        elsif self.type_name =~/SourceClassifiedAs/
          t = self.subject_taxon_name
          t.update_column(:cached_classified_as, t.get_cached_classified_as)
        elsif TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
          t = self.subject_taxon_name
          if self.type_name =~/Misspelling/
            t.update_column(:cached_misspelling, t.get_cached_misspelling)
          end
          t.update_columns(:cached => t.get_full_name,
                           :cached_html => t.get_full_name_html)
          vn = t.get_valid_taxon_name
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_column(:cached_valid_taxon_name_id, vn.id)
            s.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end
          end
        end
      end

    # no point in rescuing and not returning somthing
    rescue
      raise
    end
    false
  end

  #endregion

  #region Soft Validation

  def sv_validate_required_relationships
    object_relationships = TaxonNameRelationship.where_object_is_taxon_name(self.object_taxon_name).not_self(self).collect{|r| r.type}
    required = self.type_class.required_taxon_name_relationships - object_relationships
    required.each do |r|
      soft_validations.add(:type, " Presence of #{self.subject_status} requires selection of #{r.demodulize.underscore.humanize.downcase}")
    end
  end

  def sv_validate_disjoint_relationships
    subject_relationships = TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).not_self(self)
    subject_relationships.find_each  do |i|
      if self.type_class.disjoint_taxon_name_relationships.include?(i.type_name)
        soft_validations.add(:type, "Conflicting with another relationship: '#{i.subject_status}'")
      end
    end
  end

  def sv_validate_disjoint_object
    classifications = self.object_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
    disjoint_object_classes = self.type_class.disjoint_object_classes
    compare = disjoint_object_classes & classifications
    compare.each do |i|
      c = i.demodulize.underscore.humanize.downcase
      soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
      soft_validations.add(:object_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
    end
  end

  def sv_validate_disjoint_subject
    classifications = self.subject_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
    disjoint_subject_classes = self.type_class.disjoint_subject_classes
    compare = disjoint_subject_classes & classifications
    compare.each do |i|
      c = i.demodulize.underscore.humanize.downcase
      soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
      soft_validations.add(:subject_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
    end
  end

  # waaaaay to long - individual validations should be called in subclasses?
  def sv_specific_relationship
    s = subject_taxon_name
    o = object_taxon_name
    case type # self.type_name
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective' || 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
        if (s.type_taxon_name == o.type_taxon_name && !s.type_taxon_name.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
          soft_validations.add(:type, 'Subjective synonyms should not have the same type')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
        soft_validations.add(:type, 'Names are not similar enough to be homonyms') unless s.cached_primary_homonym_alternative_spelling == o.cached_primary_homonym_alternative_spelling
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Suppressed'
        if s.original_genus != o.original_genus
          soft_validations.add(:type, 'Primary homonyms should have the same original genus')
        elsif s.cached_primary_homonym_alternative_spelling != o.cached_primary_homonym_alternative_spelling
          soft_validations.add(:type, 'Names are not similar enough to be homonyms')
        end
        if type == 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' && s.year_of_publication > 1899
          soft_validations.add(:type, 'Taxon was not described after 1899')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'

        if s.original_genus == o.original_genus && !s.original_genus.nil?
          soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'")
        elsif s.get_valid_taxon_name.ancestor_at_rank('genus') != o.get_valid_taxon_name.ancestor_at_rank('genus')
          soft_validations.add(:type, "Secondary homonyms should be placed in the same genus, the homonymy should be deleted or changed to 'secondary homonym replaced before 1961'")
        elsif s.cached_secondary_homonym_alternative_spelling != o.cached_secondary_homonym_alternative_spelling
          soft_validations.add(:type, 'Names are not similar enough to be homonyms')
        end

        if (s.all_generic_placements & o.all_generic_placements).empty?
          soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
        end

      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary::Secondary1961'
       
        soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
        soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'") if s.original_genus == o.original_genus && !s.original_genus.nil?
        soft_validations.add(:base, 'Source is not selected') unless source 
       
        soft_validations.add(:base, 'Taxon should be treated a homonym before 1961') if self.source && self.source.year > 1960

        if (s.all_generic_placements & o.all_generic_placements).empty?
          soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
        end

      when 'TaxonNameRelationship::Iczn::PotentiallyValidating::FamilyBefore1961'
        soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
        soft_validations.add(:base, 'Taxon should be accepted as a replacement name before 1961') if self.source && self.source.year > 1960

      when 'TaxonNameRelationship::Typification::Genus::SubsequentDesignation'
        soft_validations.add(:type, 'Genus described after 1930 is nomen nudum, if type was not designated in the original publication') if o.year_of_publication > 1930
      when 'TaxonNameRelationship::Typification::Genus::Monotypy::Original'
        # @todo Check if more than one species associated with the genus in the original paper
    end
  end

  def sv_objective_synonym_relationship
    if self.type_name =~ /TaxonNameRelationship::(Iczn::Invalidating::Synonym::Objective|Icn::Unaccepting::Synonym::Homotypic|Icnb::Unaccepting::Synonym::Objective)/
      s = self.subject_taxon_name
      o = self.object_taxon_name
      if (s.type_taxon_name != o.type_taxon_name ) || !s.has_same_primary_type(o)
        soft_validations.add(:type, 'Objective synonyms should have the same type')
      end
    end
  end

  def sv_synonym_relationship
    relationships = TAXON_NAME_RELATIONSHIP_NAMES_INVALID +
        TaxonNameRelationship.collect_to_s(TaxonNameRelationship::Typification::Genus::SubsequentDesignation,
            TaxonNameRelationship::Typification::Genus::RulingByCommission)
    if relationships.include?(self.type_name)
      if self.source
        date1 = self.source.cached_nomenclature_date.to_time
        date2 = self.subject_taxon_name.nomenclature_date
        if !!date1 && !!date2
          soft_validations.add(:base, 'Taxon was not described at the time of citation') if date2 > date1
        end
      else
        soft_validations.add(:base, 'Source is not selected')
      end
    end
  end

  def sv_not_specific_relationship
    case self.type_name
      when 'TaxonNameRelationship::Typification::Genus'
        soft_validations.add(:type, 'Please specify if the type designation is original or subsequent')
      when 'TaxonNameRelationship::Typification::Genus::Monotypy'
        soft_validations.add(:type, 'Please specify if the monotypy is original or subsequent')
      when 'TaxonNameRelationship::Typification::Genus::Tautonomy'
        soft_validations.add(:type, 'Please specify if the tautonomy is absolute or Linnaean')
      when 'TaxonNameRelationship::Icn::Unaccepting'
        soft_validations.add(:type, 'Please specify the reasons for the name being Unaccepted')
      when 'TaxonNameRelationship::Icn::Unaccepting::Synonym'
        soft_validations.add(:type, 'Please specify if this is a homotypic or heterotypic synonym',
            fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being homotypic or heterotypic')
      when 'TaxonNameRelationship::Icnb::Unaccepting::Synonym'
        soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
                             fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
      when 'TaxonNameRelationship::Iczn::Invalidating'
        soft_validations.add(:type, 'Please specify the reason for the name being Invalid') unless self.subject_taxon_name.classification_invalid_or_unavailable?
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
        if NomenclaturalRank::Iczn::SpeciesGroup.descendants.collect{|t| t.to_s}.include?(self.subject_taxon_name.rank_string)
          soft_validations.add(:type, 'Please specify if this is a primary or secondary homonym',
              fix: :sv_fix_specify_homonymy_type, success_message: 'Homonym updated to being primary or secondary')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
            fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Suppression'
        soft_validations.add(:type, 'Please specify if this is a total, partial, or conditional suppression')
    end
  end

  def sv_fix_specify_synonymy_type
    s = self.subject_taxon_name
    o = self.object_taxon_name
    subject_type = s.type_taxon_name
    object_type = o.type_taxon_name
    new_relationship_name = self.type_name
    if (subject_type == object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
      if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Objective'
      else
        new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Homotypic'
      end
    elsif (subject_type != object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && !o.get_primary_type.empty? && !s.has_same_primary_type(o))
      if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective'
      else
        new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
      end
    end
    if self.type_name != new_relationship_name
      self.type = new_relationship_name
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end

    false
  end

  def sv_fix_specify_homonymy_type
    subject_original_genus = self.subject_taxon_name.original_genus
    object_original_genus = self.object_taxon_name.original_genus
    subject_genus = self.subject_taxon_name.ancestor_at_rank('genus')
    object_genus = self.subject_taxon_name.ancestor_at_rank('genus')
    new_relationship_name = 'nil'
    if subject_original_genus == object_original_genus && !subject_original_genus.nil?
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary'
    elsif subject_genus != object_genus && !subject_genus.nil?
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'
    end
    if self.type_name != new_relationship_name
      self.type = new_relationship_name
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end

  def sv_synonym_linked_to_valid_name
    #synonyms and misspellings should be linked to valid names
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      subj = self.subject_taxon_name
      if obj.get_valid_taxon_name != obj
        soft_validations.add(:object_taxon_name_id, "The #{self.subject_status} should be associated with a valid name",
                             fix: :sv_fix_synonym_linked_to_valid_name, success_message: 'The associated taxon was updated')
      elsif obj.parent_id != subj.parent_id
        soft_validations.add(:subject_taxon_name_id, "The #{self.subject_status} should have the same parent with the associated taxon",
                             fix: :sv_fix_subject_parent_update, success_message: 'The parent was updated')
      end
    end
  end

  def sv_fix_synonym_linked_to_valid_name
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      unless obj.get_valid_taxon_name == obj
        self.object_taxon_name = obj.get_valid_taxon_name
        begin
          TaxonName.transaction do
            self.save
            return true
          end
        rescue
        end
      end
    end
    false
  end

  def sv_fix_subject_parent_update
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      subj = self.subject_taxon_name
      unless obj.parent_id == subj.parent_id
        subj.parent_id = obj.parent_id
        begin
          TaxonName.transaction do
            subj.save
            return true
          end
        rescue
        end
      end
    end
    false
  end

  def sv_matching_type_genus
    if self.type_name == 'TaxonNameRelationship::Typification::Family'
      if self.object_taxon_name.name.slice(0, 1) != self.subject_taxon_name.name.slice(0, 1)
        soft_validations.add(:object_taxon_name_id, 'Type genus should have the same initial letters as the family-group name')
      end
    end
  end

  def sv_validate_priority
    unless self.type_class.nomenclatural_priority.nil?
      date1 = self.subject_taxon_name.nomenclature_date
      date2 = self.object_taxon_name.nomenclature_date
     if !!date1 and !!date2
        invalid_statuses = TAXON_NAME_CLASS_NAMES_UNAVAILABLE_AND_INVALID & self.subject_taxon_name.taxon_name_classifications.collect{|c| c.type_class.to_s}
        case self.type_class.nomenclatural_priority
          when :direct
            if date2 > date1 && invalid_statuses.empty?
              if self.type_name =~ /TaxonNameRelationship::Iczn::Invalidating::Homonym/
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon")
              elsif self.type_name =~ /::Iczn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Iczn::Invalidating::Homonym', 'TaxonNameRelationship::Iczn::Validating').not_self(self).empty?
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also a homonym or conserved")
              elsif self.type_name =~ /::Icn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Icn::Accepting::Conserved', 'TaxonNameRelationship::Icn::Accepting::Sanctioned').not_self(self).empty?
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also conserved or sanctioned")
              end
            end
          when :reverse
            if date1 > date2 && invalid_statuses.empty?
              if self.type_name =~ /TaxonNameRelationship::(Typification|Combination|OriginalCombination)/ && self.type_name != 'TaxonNameRelationship::Typification::Genus::RulingByCommission'
                soft_validations.add(:subject_taxon_name_id, "#{self.subject_status.capitalize} should not be younger than the taxon")
              else
                soft_validations.add(:type, "#{self.subject_status.capitalize} should not be younger than related taxon")
              end
            end
        end
      end
    end
  end



  def sv_coordinated_taxa
    s = self.subject_taxon_name
    o = self.object_taxon_name
    if self.type_name =~ /TaxonNameRelationship::(Iczn|Icnb|Icn)/
      s_new = s.lowest_rank_coordinated_taxon
      o_new = o.lowest_rank_coordinated_taxon


      if o != o_new && self.subject_taxon_name != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
        soft_validations.add(:object_taxon_name_id, "Relationship should move from #{o.rank_class.rank_name} to #{o_new.rank_class.rank_name}",
                             fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{o_new.rank_class.rank_name}")
      end
      if s != s_new
        soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                             fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
      end
    
    elsif self.type_name =~ /TaxonNameRelationship::(OriginalCombination|Combination|SourceClassifiedAs)/

      list = s.list_of_coordinated_names + [s]
      if s.rank_string =~ /Species/ # species group
        s_new =  list.detect{|t| t.rank_class.rank_name == 'species'}
      elsif s.rank_string =~ /Genus/
        s_new =  list.detect{|t| t.rank_class.rank_name == 'genus'}
      else
        s_new = s
      end

      # TODO: Dima fix
      return if s_new.nil?


      if s != s_new
        soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                             fix: :sv_fix_combination_relationship, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
      end
    end
  end

  def sv_fix_coordinated_taxa
    s = self.subject_taxon_name
    o = self.object_taxon_name
    s_new = s.lowest_rank_coordinated_taxon
    o_new = o.lowest_rank_coordinated_taxon
    if o != o_new || s != s_new
      self.object_taxon_name = o_new
      self.subject_taxon_name = s_new
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end

  def sv_fix_combination_relationship
    s = self.subject_taxon_name
    list = s.list_of_coordinated_names + [s]
    if s.rank_string =~ /Species/
      s_new = list.detect{|t| t.rank_class.rank_name == 'species'}
    elsif s.rank_string =~ /Genus/
      s_new = list.detect{|t| t.rank_class.rank_name == 'genus'}
    else
      s_new = s
    end
    if s != s_new && !s.nil?
      self.subject_taxon_name = s_new
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end


  #endregion

  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

  private

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

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

  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 relationship/“predicate” between the subject and object taxon names

Returns:

  • (String)

    the relationship/“predicate” between the subject and object taxon names



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
# File 'app/models/taxon_name_relationship.rb', line 39

class TaxonNameRelationship < ActiveRecord::Base
  include Housekeeping
  include Shared::Citable
  include Shared::IsData
  include SoftValidation

  # @return [Boolean, nil]
  #   When true, cached values are not built
  attr_accessor :no_cached

  belongs_to :subject_taxon_name, class_name: 'TaxonName', foreign_key: :subject_taxon_name_id, inverse_of: :taxon_name_relationships  # left side
  belongs_to :object_taxon_name, class_name: 'TaxonName', foreign_key: :object_taxon_name_id, inverse_of: :related_taxon_name_relationships    # right side

  after_save :set_cached_names_for_taxon_names, unless: 'self.no_cached'
  after_destroy :set_cached_names_for_taxon_names, unless: 'self.no_cached'

  validates_presence_of :type, message: 'Relationship type should be specified'
  validates_presence_of :subject_taxon_name, message: 'missing taxon name on left side'
  validates_presence_of :object_taxon_name, message: 'missing taxon name on right side'

  # TODO: these are likely not speced!  May have to change them to reference object rather than id
  validates_uniqueness_of :object_taxon_name_id, scope: :type, if: :is_combination?
  validates_uniqueness_of :object_taxon_name_id, scope: [:type, :subject_taxon_name_id], unless: :is_combination?

  validate :validate_type, :validate_subject_and_object_are_not_identical

  with_options unless: '!subject_taxon_name || !object_taxon_name' do |v|
    v.validate :validate_subject_and_object_share_code,
      :validate_uniqueness_of_typification_object,
      #:validate_uniqueness_of_synonym_subject,
      :validate_object_must_equal_subject_for_uncertain_placement,
      :validate_subject_and_object_ranks,
      :validate_rank_group
  end

  soft_validate(:sv_validate_required_relationships, set: :validate_required_relationships)
  soft_validate(:sv_validate_disjoint_relationships, set: :validate_disjoint_relationships)
  soft_validate(:sv_validate_disjoint_object, set: :validate_disjoint_object)
  soft_validate(:sv_validate_disjoint_subject, set: :validate_disjoint_subject)
  soft_validate(:sv_specific_relationship, set: :specific_relationship)
  soft_validate(:sv_objective_synonym_relationship, set: :objective_synonym_relationship)
  soft_validate(:sv_synonym_relationship, set: :synonym_relationship)
  soft_validate(:sv_not_specific_relationship, set: :not_specific_relationship)
  soft_validate(:sv_synonym_linked_to_valid_name, set: :synonym_linked_to_valid_name)
  soft_validate(:sv_matching_type_genus, set: :matching_type_genus)
  soft_validate(:sv_validate_priority, set: :validate_priority)

  soft_validate(:sv_coordinated_taxa, set: :coordinated_taxa)

  scope :where_subject_is_taxon_name, -> (taxon_name) {where(subject_taxon_name_id: taxon_name)}
  scope :where_object_is_taxon_name, -> (taxon_name) {where(object_taxon_name_id: taxon_name)}
  scope :where_object_in_taxon_names, -> (taxon_name_array) {where('"taxon_name_relationships"."object_taxon_name_id" IN (?)', taxon_name_array)}
#  scope :with_type_string, -> (type_string) {where('"taxon_name_relationships"."type" LIKE ?', "#{type_string}" ) }

  scope :with_type_string, -> (type_string) { where(sanitize_sql_array(["taxon_name_relationships.type = '%s'", type_string])) } #   #{?type_string}"where('"taxon_name_relationships"."type" LIKE ?', "#{type_string}" ) }

  scope :with_type_base,     -> (base_string) {where('"taxon_name_relationships"."type" LIKE ?', "#{base_string}%" ) } 
  scope :with_type_contains, -> (base_string) {where('"taxon_name_relationships"."type" LIKE ?', "%#{base_string}%" ) } 

  scope :with_two_type_bases, -> (base_string1, base_string2) {where("taxon_name_relationships.type LIKE '#{base_string1}%' OR taxon_name_relationships.type LIKE '#{base_string2}%'" ) }
  scope :with_type_array, -> (base_array) {where('taxon_name_relationships.type IN (?)', base_array ) }

  # @return [Array of TaxonNameClassification]
  #  the inferable TaxonNameClassification(s) added to the subject when this relationship is used
  #  !! Not implemented 
  def subject_properties
    []
  end

  # @return [Array of TaxonNameClassification]
  #  the inferable TaxonNameClassification(s) added to the subject when this relationship is used
  #  !! Not implmented
  def object_properties
    []
  end

  # @return [Array of NomenclatureRank]
  #   the valid ranks to which the subject name can belong, set in subclasses. (left side)
  def self.valid_subject_ranks
    []
  end


  # @return [Array of NomenclatureRank]
  #   the valid ranks to which the object name can belong, set in subclasses. (right side)
  def self.valid_object_ranks
    []
  end

  # @return [Array of TaxonNameRelationships]
  #   if this relationships is set for the subject, then others in this array should not be used for that subject
  def self.disjoint_taxon_name_relationships
    []
  end

  # TODO: why isn't this disjoint?
  # disjoint relationships for the taxon as a object
  def self.required_taxon_name_relationships
    []
  end

  def self.disjoint_subject_classes
    []
  end

  def self.disjoint_object_classes
    []
  end

  def self.gbif_status_of_subject
    nil
  end

  def self.gbif_status_of_object
    nil
  end

  def self.assignable
    false
  end

  # @return [Symbol]
  #   determine the relative age of subject and object
  #   :direct - subject is younger than object
  #   :reverse - object is younger than subject
  def self.nomenclatural_priority
    nil 
  end

  # @return [String]
  #    the status inferred by the relationship to the object name 
  def object_status
    self.type_name.demodulize.underscore.humanize.downcase
  end

  # @return [String]
  #    the status inferred by the relationship to the subject name 
  def subject_status
    self.type_name.demodulize.underscore.humanize.downcase
  end

  # @return [String]
  #    the connecting word in the relationship from the subject name to object name
  def subject_status_connector_to_object
    ' of'
  end

  # @return [String]
  #    the connecting word in the relationship from the object name to subject name
  def object_status_connector_to_subject
    ' of'
  end

  # @return [String]
  #   a readable fragement combining status and connector
  def subject_status_tag
    subject_status + subject_status_connector_to_object
  end

  # @return [String]
  #   a readable fragement combining status and connector
  def object_status_tag
    object_status + object_status_connector_to_subject
  end

  # @return [String, nil]
  #   the type of this relationship, IF the type is a valid name, else nil
  def type_name
    r = self.type
    TAXON_NAME_RELATIONSHIP_NAMES.include?(r) ? r : nil
  end

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

  # @return [TaxonNameRelationship, String]
  #    the type as a class, if legal, else a string  ! Strangeish
  def type_class
    r = read_attribute(:type).to_s
    TAXON_NAME_RELATIONSHIP_NAMES.include?(r) ? r.safe_constantize : r
  end

  # TODO: match on either name
  def self.find_for_autocomplete(params)
    where(id: params[:term]).with_project_id(params[:project_id])
  end

  # @return [String, nil]
  #   the NOMEN uri for this type
  def self.nomen_uri
    const_defined?(:NOMEN_URI, false) ? self::NOMEN_URI : nil
  end

  # @return [Time]
  #   effective date of publication, used to determine nomenclatural priorities
  def nomenclature_date
    self.source ? (self.source.cached_nomenclature_date ? self.source.cached_nomenclature_date.to_time : Time.now) : Time.now
  end

  # @todo SourceClassifiedAs is not really Combination in the other sense
  def is_combination?
    !!/TaxonNameRelationship::(OriginalCombination|Combination)/.match(self.type.to_s)
  end

  protected

  #region Validation

  def validate_type
    unless TAXON_NAME_RELATIONSHIP_NAMES.include?(type)
      errors.add(:type, "'#{type}' is not a valid taxon name relationship")
    end

    if object_taxon_name.class.to_s == 'Protonym' || object_taxon_name.class.to_s == 'Hybrid'
      errors.add(:type, "'#{type}' is not a valid taxon name relationship") if /TaxonNameRelationship::Combination::/.match(self.type)
    end

    if object_taxon_name.class.to_s == 'Combination'
      errors.add(:type, "'#{type}' is not a valid taxon name relationship") unless /TaxonNameRelationship::Combination::/.match(self.type)
    end
  end

  # @todo validate, that all the relationships in the table could be linked to relationships in classes (if those had changed)

  def validate_subject_and_object_share_code
    if object_taxon_name.type  == 'Protonym' && subject_taxon_name.type == 'Protonym'
      errors.add(:object_taxon_name_id, 'The related taxon is from different potentially_validating code') if subject_taxon_name.rank_class.nomenclatural_code != object_taxon_name.rank_class.nomenclatural_code
    end
  end

  def validate_subject_and_object_are_not_identical
    if self.object_taxon_name == self.subject_taxon_name
      errors.add(:object_taxon_name, 'Taxon should not refer to itself') unless self.type =~ /OriginalCombination/
    end
  end

  def validate_object_must_equal_subject_for_uncertain_placement
    if self.object_taxon_name_id != self.subject_taxon_name.parent_id && self.type_name == 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      errors.add(:object_taxon_name_id, 'The parent and related taxon should match')
    end
  end

  def validate_subject_and_object_ranks
    tname = self.type_name

    if tname =~ /TaxonNameRelationship::(Icnb|Icn|Iczn)/ && tname != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      rank_group = self.subject_taxon_name.rank_class.parent
      unless rank_group == self.object_taxon_name.rank_class.parent
        errors.add(:object_taxon_name_id, "Rank of related taxon should be in the #{rank_group.rank_name}")
      end
    end

    unless self.type_class.blank? # only validate if it is set
      if object_taxon_name
        if object_taxon_name.type == 'Protonym' || object_taxon_name.type == 'Hybrid'
          unless self.type_class.valid_object_ranks.include?(self.object_taxon_name.rank_string)
            errors.add(:object_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
            errors.add(:type, 'Not compatible with the rank of object taxon')
          end
        end
      end

      if subject_taxon_name
        if subject_taxon_name.type == 'Protonym' || subject_taxon_name.type == 'Hybrid'
          unless self.type_class.valid_subject_ranks.include?(self.subject_taxon_name.parent.rank_string)
            soft_validations.add(:subject_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
            soft_validations.add(:type, 'Not compatible with the rank of subject taxon')
          end
        end
      end
    end
  end

  #def validate_uniqueness_of_synonym_subject ##### Protonym historically could be listed as a synonym to different taxa
  #  if !self.type.nil? && /Synonym/.match(self.type_name) && !TaxonNameRelationship.where(subject_taxon_name_id: self.subject_taxon_name_id).with_type_contains('Synonym').not_self(self).empty?
  #    errors.add(:subject_taxon_name_id, 'Only one synonym relationship is allowed')
  #  end
  #end

  def validate_uniqueness_of_typification_object
    if /Typification/.match(self.type_name) && !TaxonNameRelationship.where(object_taxon_name_id: self.object_taxon_name_id).with_type_contains('Typification').not_self(self).empty?
      errors.add(:object_taxon_name_id, 'Only one type relationship is allowed')
    end
  end

  def validate_rank_group
    if self.type =~ /Hybrid/ && self.subject_taxon_name && self.object_taxon_name
      if self.subject_taxon_name.rank_class.parent != self.object_taxon_name.rank_class.parent
        errors.add(:subject_taxon_name_id, "Rank of taxon (#{self.subject_taxon_name.rank_class.rank_name}) is not compatible with the rank of hybrid (#{self.object_taxon_name.rank_class.rank_name})")
      end
    end
  end

  def set_cached_names_for_taxon_names
    dependants = []
    begin
      TaxonName.transaction do

       #if self.type_name =~/OriginalCombination/
       #  t = self.object_taxon_name
       #  t.update_columns(:cached_original_combination => t.get_original_combination,
       #                   :cached_primary_homonym => t.get_genus_species(:original, :self),
       #                   :cached_primary_homonym_alternative_spelling => t.get_genus_species(:original, :alternative))
        if self.is_combination?
          t = self.object_taxon_name
          t.update_columns(:cached_original_combination => t.get_original_combination,
                           :cached => t.get_full_name,
                           :cached_html => t.get_full_name_html,
                           :cached_author_year => t.get_author_and_year,
                           :cached_valid_taxon_name_id => t.get_valid_taxon_name.id)
#        elsif self.type_name =~/Misspelling/
#          t = self.subject_taxon_name
#          t.update_column(:cached_misspelling, t.get_cached_misspelling)
        elsif self.type_name =~/TaxonNameRelationship::Hybrid/
          t = self.object_taxon_name
          t.update_columns(:cached => t.get_full_name,
                           :cached_html => t.get_full_name_html)
        elsif self.type_name =~/SourceClassifiedAs/
          t = self.subject_taxon_name
          t.update_column(:cached_classified_as, t.get_cached_classified_as)
        elsif TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
          t = self.subject_taxon_name
          if self.type_name =~/Misspelling/
            t.update_column(:cached_misspelling, t.get_cached_misspelling)
          end
          t.update_columns(:cached => t.get_full_name,
                           :cached_html => t.get_full_name_html)
          vn = t.get_valid_taxon_name
          vn.list_of_invalid_taxon_names.each do |s|
            s.update_column(:cached_valid_taxon_name_id, vn.id)
            s.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end
          end
        end
      end

    # no point in rescuing and not returning somthing
    rescue
      raise
    end
    false
  end

  #endregion

  #region Soft Validation

  def sv_validate_required_relationships
    object_relationships = TaxonNameRelationship.where_object_is_taxon_name(self.object_taxon_name).not_self(self).collect{|r| r.type}
    required = self.type_class.required_taxon_name_relationships - object_relationships
    required.each do |r|
      soft_validations.add(:type, " Presence of #{self.subject_status} requires selection of #{r.demodulize.underscore.humanize.downcase}")
    end
  end

  def sv_validate_disjoint_relationships
    subject_relationships = TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).not_self(self)
    subject_relationships.find_each  do |i|
      if self.type_class.disjoint_taxon_name_relationships.include?(i.type_name)
        soft_validations.add(:type, "Conflicting with another relationship: '#{i.subject_status}'")
      end
    end
  end

  def sv_validate_disjoint_object
    classifications = self.object_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
    disjoint_object_classes = self.type_class.disjoint_object_classes
    compare = disjoint_object_classes & classifications
    compare.each do |i|
      c = i.demodulize.underscore.humanize.downcase
      soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
      soft_validations.add(:object_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
    end
  end

  def sv_validate_disjoint_subject
    classifications = self.subject_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
    disjoint_subject_classes = self.type_class.disjoint_subject_classes
    compare = disjoint_subject_classes & classifications
    compare.each do |i|
      c = i.demodulize.underscore.humanize.downcase
      soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
      soft_validations.add(:subject_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
    end
  end

  # waaaaay to long - individual validations should be called in subclasses?
  def sv_specific_relationship
    s = subject_taxon_name
    o = object_taxon_name
    case type # self.type_name
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective' || 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
        if (s.type_taxon_name == o.type_taxon_name && !s.type_taxon_name.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
          soft_validations.add(:type, 'Subjective synonyms should not have the same type')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
        soft_validations.add(:type, 'Names are not similar enough to be homonyms') unless s.cached_primary_homonym_alternative_spelling == o.cached_primary_homonym_alternative_spelling
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Suppressed'
        if s.original_genus != o.original_genus
          soft_validations.add(:type, 'Primary homonyms should have the same original genus')
        elsif s.cached_primary_homonym_alternative_spelling != o.cached_primary_homonym_alternative_spelling
          soft_validations.add(:type, 'Names are not similar enough to be homonyms')
        end
        if type == 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' && s.year_of_publication > 1899
          soft_validations.add(:type, 'Taxon was not described after 1899')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'

        if s.original_genus == o.original_genus && !s.original_genus.nil?
          soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'")
        elsif s.get_valid_taxon_name.ancestor_at_rank('genus') != o.get_valid_taxon_name.ancestor_at_rank('genus')
          soft_validations.add(:type, "Secondary homonyms should be placed in the same genus, the homonymy should be deleted or changed to 'secondary homonym replaced before 1961'")
        elsif s.cached_secondary_homonym_alternative_spelling != o.cached_secondary_homonym_alternative_spelling
          soft_validations.add(:type, 'Names are not similar enough to be homonyms')
        end

        if (s.all_generic_placements & o.all_generic_placements).empty?
          soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
        end

      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary::Secondary1961'
       
        soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
        soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'") if s.original_genus == o.original_genus && !s.original_genus.nil?
        soft_validations.add(:base, 'Source is not selected') unless source 
       
        soft_validations.add(:base, 'Taxon should be treated a homonym before 1961') if self.source && self.source.year > 1960

        if (s.all_generic_placements & o.all_generic_placements).empty?
          soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
        end

      when 'TaxonNameRelationship::Iczn::PotentiallyValidating::FamilyBefore1961'
        soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
        soft_validations.add(:base, 'Taxon should be accepted as a replacement name before 1961') if self.source && self.source.year > 1960

      when 'TaxonNameRelationship::Typification::Genus::SubsequentDesignation'
        soft_validations.add(:type, 'Genus described after 1930 is nomen nudum, if type was not designated in the original publication') if o.year_of_publication > 1930
      when 'TaxonNameRelationship::Typification::Genus::Monotypy::Original'
        # @todo Check if more than one species associated with the genus in the original paper
    end
  end

  def sv_objective_synonym_relationship
    if self.type_name =~ /TaxonNameRelationship::(Iczn::Invalidating::Synonym::Objective|Icn::Unaccepting::Synonym::Homotypic|Icnb::Unaccepting::Synonym::Objective)/
      s = self.subject_taxon_name
      o = self.object_taxon_name
      if (s.type_taxon_name != o.type_taxon_name ) || !s.has_same_primary_type(o)
        soft_validations.add(:type, 'Objective synonyms should have the same type')
      end
    end
  end

  def sv_synonym_relationship
    relationships = TAXON_NAME_RELATIONSHIP_NAMES_INVALID +
        TaxonNameRelationship.collect_to_s(TaxonNameRelationship::Typification::Genus::SubsequentDesignation,
            TaxonNameRelationship::Typification::Genus::RulingByCommission)
    if relationships.include?(self.type_name)
      if self.source
        date1 = self.source.cached_nomenclature_date.to_time
        date2 = self.subject_taxon_name.nomenclature_date
        if !!date1 && !!date2
          soft_validations.add(:base, 'Taxon was not described at the time of citation') if date2 > date1
        end
      else
        soft_validations.add(:base, 'Source is not selected')
      end
    end
  end

  def sv_not_specific_relationship
    case self.type_name
      when 'TaxonNameRelationship::Typification::Genus'
        soft_validations.add(:type, 'Please specify if the type designation is original or subsequent')
      when 'TaxonNameRelationship::Typification::Genus::Monotypy'
        soft_validations.add(:type, 'Please specify if the monotypy is original or subsequent')
      when 'TaxonNameRelationship::Typification::Genus::Tautonomy'
        soft_validations.add(:type, 'Please specify if the tautonomy is absolute or Linnaean')
      when 'TaxonNameRelationship::Icn::Unaccepting'
        soft_validations.add(:type, 'Please specify the reasons for the name being Unaccepted')
      when 'TaxonNameRelationship::Icn::Unaccepting::Synonym'
        soft_validations.add(:type, 'Please specify if this is a homotypic or heterotypic synonym',
            fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being homotypic or heterotypic')
      when 'TaxonNameRelationship::Icnb::Unaccepting::Synonym'
        soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
                             fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
      when 'TaxonNameRelationship::Iczn::Invalidating'
        soft_validations.add(:type, 'Please specify the reason for the name being Invalid') unless self.subject_taxon_name.classification_invalid_or_unavailable?
      when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
        if NomenclaturalRank::Iczn::SpeciesGroup.descendants.collect{|t| t.to_s}.include?(self.subject_taxon_name.rank_string)
          soft_validations.add(:type, 'Please specify if this is a primary or secondary homonym',
              fix: :sv_fix_specify_homonymy_type, success_message: 'Homonym updated to being primary or secondary')
        end
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
            fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
      when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Suppression'
        soft_validations.add(:type, 'Please specify if this is a total, partial, or conditional suppression')
    end
  end

  def sv_fix_specify_synonymy_type
    s = self.subject_taxon_name
    o = self.object_taxon_name
    subject_type = s.type_taxon_name
    object_type = o.type_taxon_name
    new_relationship_name = self.type_name
    if (subject_type == object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
      if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Objective'
      else
        new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Homotypic'
      end
    elsif (subject_type != object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && !o.get_primary_type.empty? && !s.has_same_primary_type(o))
      if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
        new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective'
      else
        new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
      end
    end
    if self.type_name != new_relationship_name
      self.type = new_relationship_name
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end

    false
  end

  def sv_fix_specify_homonymy_type
    subject_original_genus = self.subject_taxon_name.original_genus
    object_original_genus = self.object_taxon_name.original_genus
    subject_genus = self.subject_taxon_name.ancestor_at_rank('genus')
    object_genus = self.subject_taxon_name.ancestor_at_rank('genus')
    new_relationship_name = 'nil'
    if subject_original_genus == object_original_genus && !subject_original_genus.nil?
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary'
    elsif subject_genus != object_genus && !subject_genus.nil?
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'
    end
    if self.type_name != new_relationship_name
      self.type = new_relationship_name
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end

  def sv_synonym_linked_to_valid_name
    #synonyms and misspellings should be linked to valid names
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      subj = self.subject_taxon_name
      if obj.get_valid_taxon_name != obj
        soft_validations.add(:object_taxon_name_id, "The #{self.subject_status} should be associated with a valid name",
                             fix: :sv_fix_synonym_linked_to_valid_name, success_message: 'The associated taxon was updated')
      elsif obj.parent_id != subj.parent_id
        soft_validations.add(:subject_taxon_name_id, "The #{self.subject_status} should have the same parent with the associated taxon",
                             fix: :sv_fix_subject_parent_update, success_message: 'The parent was updated')
      end
    end
  end

  def sv_fix_synonym_linked_to_valid_name
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      unless obj.get_valid_taxon_name == obj
        self.object_taxon_name = obj.get_valid_taxon_name
        begin
          TaxonName.transaction do
            self.save
            return true
          end
        rescue
        end
      end
    end
    false
  end

  def sv_fix_subject_parent_update
    if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
      obj = self.object_taxon_name
      subj = self.subject_taxon_name
      unless obj.parent_id == subj.parent_id
        subj.parent_id = obj.parent_id
        begin
          TaxonName.transaction do
            subj.save
            return true
          end
        rescue
        end
      end
    end
    false
  end

  def sv_matching_type_genus
    if self.type_name == 'TaxonNameRelationship::Typification::Family'
      if self.object_taxon_name.name.slice(0, 1) != self.subject_taxon_name.name.slice(0, 1)
        soft_validations.add(:object_taxon_name_id, 'Type genus should have the same initial letters as the family-group name')
      end
    end
  end

  def sv_validate_priority
    unless self.type_class.nomenclatural_priority.nil?
      date1 = self.subject_taxon_name.nomenclature_date
      date2 = self.object_taxon_name.nomenclature_date
     if !!date1 and !!date2
        invalid_statuses = TAXON_NAME_CLASS_NAMES_UNAVAILABLE_AND_INVALID & self.subject_taxon_name.taxon_name_classifications.collect{|c| c.type_class.to_s}
        case self.type_class.nomenclatural_priority
          when :direct
            if date2 > date1 && invalid_statuses.empty?
              if self.type_name =~ /TaxonNameRelationship::Iczn::Invalidating::Homonym/
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon")
              elsif self.type_name =~ /::Iczn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Iczn::Invalidating::Homonym', 'TaxonNameRelationship::Iczn::Validating').not_self(self).empty?
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also a homonym or conserved")
              elsif self.type_name =~ /::Icn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Icn::Accepting::Conserved', 'TaxonNameRelationship::Icn::Accepting::Sanctioned').not_self(self).empty?
                soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also conserved or sanctioned")
              end
            end
          when :reverse
            if date1 > date2 && invalid_statuses.empty?
              if self.type_name =~ /TaxonNameRelationship::(Typification|Combination|OriginalCombination)/ && self.type_name != 'TaxonNameRelationship::Typification::Genus::RulingByCommission'
                soft_validations.add(:subject_taxon_name_id, "#{self.subject_status.capitalize} should not be younger than the taxon")
              else
                soft_validations.add(:type, "#{self.subject_status.capitalize} should not be younger than related taxon")
              end
            end
        end
      end
    end
  end



  def sv_coordinated_taxa
    s = self.subject_taxon_name
    o = self.object_taxon_name
    if self.type_name =~ /TaxonNameRelationship::(Iczn|Icnb|Icn)/
      s_new = s.lowest_rank_coordinated_taxon
      o_new = o.lowest_rank_coordinated_taxon


      if o != o_new && self.subject_taxon_name != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
        soft_validations.add(:object_taxon_name_id, "Relationship should move from #{o.rank_class.rank_name} to #{o_new.rank_class.rank_name}",
                             fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{o_new.rank_class.rank_name}")
      end
      if s != s_new
        soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                             fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
      end
    
    elsif self.type_name =~ /TaxonNameRelationship::(OriginalCombination|Combination|SourceClassifiedAs)/

      list = s.list_of_coordinated_names + [s]
      if s.rank_string =~ /Species/ # species group
        s_new =  list.detect{|t| t.rank_class.rank_name == 'species'}
      elsif s.rank_string =~ /Genus/
        s_new =  list.detect{|t| t.rank_class.rank_name == 'genus'}
      else
        s_new = s
      end

      # TODO: Dima fix
      return if s_new.nil?


      if s != s_new
        soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                             fix: :sv_fix_combination_relationship, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
      end
    end
  end

  def sv_fix_coordinated_taxa
    s = self.subject_taxon_name
    o = self.object_taxon_name
    s_new = s.lowest_rank_coordinated_taxon
    o_new = o.lowest_rank_coordinated_taxon
    if o != o_new || s != s_new
      self.object_taxon_name = o_new
      self.subject_taxon_name = s_new
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end

  def sv_fix_combination_relationship
    s = self.subject_taxon_name
    list = s.list_of_coordinated_names + [s]
    if s.rank_string =~ /Species/
      s_new = list.detect{|t| t.rank_class.rank_name == 'species'}
    elsif s.rank_string =~ /Genus/
      s_new = list.detect{|t| t.rank_class.rank_name == 'genus'}
    else
      s_new = s
    end
    if s != s_new && !s.nil?
      self.subject_taxon_name = s_new
      begin
        TaxonNameRelationship.transaction do
          self.save
          return true
        end
      rescue
      end
    end
    false
  end


  #endregion

  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

  private

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

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

  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

+ (Object) assignable



156
157
158
# File 'app/models/taxon_name_relationship.rb', line 156

def self.assignable
  false
end

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



798
799
800
# File 'app/models/taxon_name_relationship.rb', line 798

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)



790
791
792
793
794
795
796
# File 'app/models/taxon_name_relationship.rb', line 790

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)



786
787
788
# File 'app/models/taxon_name_relationship.rb', line 786

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

+ (Object) disjoint_object_classes



144
145
146
# File 'app/models/taxon_name_relationship.rb', line 144

def self.disjoint_object_classes
  []
end

+ (Object) disjoint_subject_classes



140
141
142
# File 'app/models/taxon_name_relationship.rb', line 140

def self.disjoint_subject_classes
  []
end

+ (Array of TaxonNameRelationships) disjoint_taxon_name_relationships

Returns if this relationships is set for the subject, then others in this array should not be used for that subject

Returns:

  • (Array of TaxonNameRelationships)

    if this relationships is set for the subject, then others in this array should not be used for that subject



130
131
132
# File 'app/models/taxon_name_relationship.rb', line 130

def self.disjoint_taxon_name_relationships
  []
end

+ (Object) find_for_autocomplete(params)

TODO: match on either name



223
224
225
# File 'app/models/taxon_name_relationship.rb', line 223

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

+ (Object) gbif_status_of_object



152
153
154
# File 'app/models/taxon_name_relationship.rb', line 152

def self.gbif_status_of_object
  nil
end

+ (Object) gbif_status_of_subject



148
149
150
# File 'app/models/taxon_name_relationship.rb', line 148

def self.gbif_status_of_subject
  nil
end

+ (Object) generate_download(scope) (protected)

endregion



773
774
775
776
777
778
779
780
781
782
# File 'app/models/taxon_name_relationship.rb', line 773

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

+ (String?) nomen_uri

Returns the NOMEN uri for this type

Returns:

  • (String, nil)

    the NOMEN uri for this type



229
230
231
# File 'app/models/taxon_name_relationship.rb', line 229

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

+ (Symbol) nomenclatural_priority

Returns determine the relative age of subject and object :direct - subject is younger than object :reverse - object is younger than subject

Returns:

  • (Symbol)

    determine the relative age of subject and object :direct - subject is younger than object :reverse - object is younger than subject



164
165
166
# File 'app/models/taxon_name_relationship.rb', line 164

def self.nomenclatural_priority
  nil 
end

+ (Object) required_taxon_name_relationships

TODO: why isn't this disjoint? disjoint relationships for the taxon as a object



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

def self.required_taxon_name_relationships
  []
end

+ (Array of NomenclatureRank) valid_object_ranks

Returns the valid ranks to which the object name can belong, set in subclasses. (right side)

Returns:

  • (Array of NomenclatureRank)

    the valid ranks to which the object name can belong, set in subclasses. (right side)



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

def self.valid_object_ranks
  []
end

+ (Array of NomenclatureRank) valid_subject_ranks

Returns the valid ranks to which the subject name can belong, set in subclasses. (left side)

Returns:

  • (Array of NomenclatureRank)

    the valid ranks to which the subject name can belong, set in subclasses. (left side)



117
118
119
# File 'app/models/taxon_name_relationship.rb', line 117

def self.valid_subject_ranks
  []
end

Instance Method Details

- (Boolean) is_combination?

TODO:

SourceClassifiedAs is not really Combination in the other sense

Returns:

  • (Boolean)


240
241
242
# File 'app/models/taxon_name_relationship.rb', line 240

def is_combination?
  !!/TaxonNameRelationship::(OriginalCombination|Combination)/.match(self.type.to_s)
end

- (Time) nomenclature_date

Returns effective date of publication, used to determine nomenclatural priorities

Returns:

  • (Time)

    effective date of publication, used to determine nomenclatural priorities



235
236
237
# File 'app/models/taxon_name_relationship.rb', line 235

def nomenclature_date
  self.source ? (self.source.cached_nomenclature_date ? self.source.cached_nomenclature_date.to_time : Time.now) : Time.now
end

- (Array of TaxonNameClassification) object_properties

Returns the inferable TaxonNameClassification(s) added to the subject when this relationship is used !! Not implmented

Returns:

  • (Array of TaxonNameClassification)

    the inferable TaxonNameClassification(s) added to the subject when this relationship is used !! Not implmented



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

def object_properties
  []
end

- (String) object_status

Returns the status inferred by the relationship to the object name

Returns:

  • (String)

    the status inferred by the relationship to the object name



170
171
172
# File 'app/models/taxon_name_relationship.rb', line 170

def object_status
  self.type_name.demodulize.underscore.humanize.downcase
end

- (String) object_status_connector_to_subject

Returns the connecting word in the relationship from the object name to subject name

Returns:

  • (String)

    the connecting word in the relationship from the object name to subject name



188
189
190
# File 'app/models/taxon_name_relationship.rb', line 188

def object_status_connector_to_subject
  ' of'
end

- (String) object_status_tag

Returns a readable fragement combining status and connector

Returns:

  • (String)

    a readable fragement combining status and connector



200
201
202
# File 'app/models/taxon_name_relationship.rb', line 200

def object_status_tag
  object_status + object_status_connector_to_subject
end

- (Object) set_cached_names_for_taxon_names (protected)



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'app/models/taxon_name_relationship.rb', line 333

def set_cached_names_for_taxon_names
  dependants = []
  begin
    TaxonName.transaction do

     #if self.type_name =~/OriginalCombination/
     #  t = self.object_taxon_name
     #  t.update_columns(:cached_original_combination => t.get_original_combination,
     #                   :cached_primary_homonym => t.get_genus_species(:original, :self),
     #                   :cached_primary_homonym_alternative_spelling => t.get_genus_species(:original, :alternative))
      if self.is_combination?
        t = self.object_taxon_name
        t.update_columns(:cached_original_combination => t.get_original_combination,
                         :cached => t.get_full_name,
                         :cached_html => t.get_full_name_html,
                         :cached_author_year => t.get_author_and_year,
                         :cached_valid_taxon_name_id => t.get_valid_taxon_name.id)
#        elsif self.type_name =~/Misspelling/
#          t = self.subject_taxon_name
#          t.update_column(:cached_misspelling, t.get_cached_misspelling)
      elsif self.type_name =~/TaxonNameRelationship::Hybrid/
        t = self.object_taxon_name
        t.update_columns(:cached => t.get_full_name,
                         :cached_html => t.get_full_name_html)
      elsif self.type_name =~/SourceClassifiedAs/
        t = self.subject_taxon_name
        t.update_column(:cached_classified_as, t.get_cached_classified_as)
      elsif TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
        t = self.subject_taxon_name
        if self.type_name =~/Misspelling/
          t.update_column(:cached_misspelling, t.get_cached_misspelling)
        end
        t.update_columns(:cached => t.get_full_name,
                         :cached_html => t.get_full_name_html)
        vn = t.get_valid_taxon_name
        vn.list_of_invalid_taxon_names.each do |s|
          s.update_column(:cached_valid_taxon_name_id, vn.id)
          s.combination_list_self.each do |c|
            c.update_column(:cached_valid_taxon_name_id, vn.id)
          end
        end
      end
    end

  # no point in rescuing and not returning somthing
  rescue
    raise
  end
  false
end

- (Array of TaxonNameClassification) subject_properties

Returns the inferable TaxonNameClassification(s) added to the subject when this relationship is used !! Not implemented

Returns:

  • (Array of TaxonNameClassification)

    the inferable TaxonNameClassification(s) added to the subject when this relationship is used !! Not implemented



104
105
106
# File 'app/models/taxon_name_relationship.rb', line 104

def subject_properties
  []
end

- (String) subject_status

Returns the status inferred by the relationship to the subject name

Returns:

  • (String)

    the status inferred by the relationship to the subject name



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

def subject_status
  self.type_name.demodulize.underscore.humanize.downcase
end

- (String) subject_status_connector_to_object

Returns the connecting word in the relationship from the subject name to object name

Returns:

  • (String)

    the connecting word in the relationship from the subject name to object name



182
183
184
# File 'app/models/taxon_name_relationship.rb', line 182

def subject_status_connector_to_object
  ' of'
end

- (String) subject_status_tag

Returns a readable fragement combining status and connector

Returns:

  • (String)

    a readable fragement combining status and connector



194
195
196
# File 'app/models/taxon_name_relationship.rb', line 194

def subject_status_tag
  subject_status + subject_status_connector_to_object
end

- (Object) sv_coordinated_taxa (protected)



689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
# File 'app/models/taxon_name_relationship.rb', line 689

def sv_coordinated_taxa
  s = self.subject_taxon_name
  o = self.object_taxon_name
  if self.type_name =~ /TaxonNameRelationship::(Iczn|Icnb|Icn)/
    s_new = s.lowest_rank_coordinated_taxon
    o_new = o.lowest_rank_coordinated_taxon


    if o != o_new && self.subject_taxon_name != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
      soft_validations.add(:object_taxon_name_id, "Relationship should move from #{o.rank_class.rank_name} to #{o_new.rank_class.rank_name}",
                           fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{o_new.rank_class.rank_name}")
    end
    if s != s_new
      soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                           fix: :sv_fix_coordinated_taxa, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
    end
  
  elsif self.type_name =~ /TaxonNameRelationship::(OriginalCombination|Combination|SourceClassifiedAs)/

    list = s.list_of_coordinated_names + [s]
    if s.rank_string =~ /Species/ # species group
      s_new =  list.detect{|t| t.rank_class.rank_name == 'species'}
    elsif s.rank_string =~ /Genus/
      s_new =  list.detect{|t| t.rank_class.rank_name == 'genus'}
    else
      s_new = s
    end

    # TODO: Dima fix
    return if s_new.nil?


    if s != s_new
      soft_validations.add(:subject_taxon_name_id, "Relationship should move from #{s.rank_class.rank_name} to #{s_new.rank_class.rank_name}",
                           fix: :sv_fix_combination_relationship, success_message: "Relationship moved to  #{s_new.rank_class.rank_name}")
    end
  end
end

- (Object) sv_fix_combination_relationship (protected)



747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
# File 'app/models/taxon_name_relationship.rb', line 747

def sv_fix_combination_relationship
  s = self.subject_taxon_name
  list = s.list_of_coordinated_names + [s]
  if s.rank_string =~ /Species/
    s_new = list.detect{|t| t.rank_class.rank_name == 'species'}
  elsif s.rank_string =~ /Genus/
    s_new = list.detect{|t| t.rank_class.rank_name == 'genus'}
  else
    s_new = s
  end
  if s != s_new && !s.nil?
    self.subject_taxon_name = s_new
    begin
      TaxonNameRelationship.transaction do
        self.save
        return true
      end
    rescue
    end
  end
  false
end

- (Object) sv_fix_coordinated_taxa (protected)



728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
# File 'app/models/taxon_name_relationship.rb', line 728

def sv_fix_coordinated_taxa
  s = self.subject_taxon_name
  o = self.object_taxon_name
  s_new = s.lowest_rank_coordinated_taxon
  o_new = o.lowest_rank_coordinated_taxon
  if o != o_new || s != s_new
    self.object_taxon_name = o_new
    self.subject_taxon_name = s_new
    begin
      TaxonNameRelationship.transaction do
        self.save
        return true
      end
    rescue
    end
  end
  false
end

- (Object) sv_fix_specify_homonymy_type (protected)



575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'app/models/taxon_name_relationship.rb', line 575

def sv_fix_specify_homonymy_type
  subject_original_genus = self.subject_taxon_name.original_genus
  object_original_genus = self.object_taxon_name.original_genus
  subject_genus = self.subject_taxon_name.ancestor_at_rank('genus')
  object_genus = self.subject_taxon_name.ancestor_at_rank('genus')
  new_relationship_name = 'nil'
  if subject_original_genus == object_original_genus && !subject_original_genus.nil?
    new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary'
  elsif subject_genus != object_genus && !subject_genus.nil?
    new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'
  end
  if self.type_name != new_relationship_name
    self.type = new_relationship_name
    begin
      TaxonNameRelationship.transaction do
        self.save
        return true
      end
    rescue
    end
  end
  false
end

- (Object) sv_fix_specify_synonymy_type (protected)



542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'app/models/taxon_name_relationship.rb', line 542

def sv_fix_specify_synonymy_type
  s = self.subject_taxon_name
  o = self.object_taxon_name
  subject_type = s.type_taxon_name
  object_type = o.type_taxon_name
  new_relationship_name = self.type_name
  if (subject_type == object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
    if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Objective'
    else
      new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Homotypic'
    end
  elsif (subject_type != object_type && !subject_type.nil? ) || (!s.get_primary_type.empty? && !o.get_primary_type.empty? && !s.has_same_primary_type(o))
    if new_relationship_name == 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
      new_relationship_name = 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective'
    else
      new_relationship_name = 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
    end
  end
  if self.type_name != new_relationship_name
    self.type = new_relationship_name
    begin
      TaxonNameRelationship.transaction do
        self.save
        return true
      end
    rescue
    end
  end

  false
end

- (Object) sv_fix_subject_parent_update (protected)



631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
# File 'app/models/taxon_name_relationship.rb', line 631

def sv_fix_subject_parent_update
  if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
    obj = self.object_taxon_name
    subj = self.subject_taxon_name
    unless obj.parent_id == subj.parent_id
      subj.parent_id = obj.parent_id
      begin
        TaxonName.transaction do
          subj.save
          return true
        end
      rescue
      end
    end
  end
  false
end

- (Object) sv_fix_synonym_linked_to_valid_name (protected)



614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
# File 'app/models/taxon_name_relationship.rb', line 614

def sv_fix_synonym_linked_to_valid_name
  if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
    obj = self.object_taxon_name
    unless obj.get_valid_taxon_name == obj
      self.object_taxon_name = obj.get_valid_taxon_name
      begin
        TaxonName.transaction do
          self.save
          return true
        end
      rescue
      end
    end
  end
  false
end

- (Object) sv_matching_type_genus (protected)



649
650
651
652
653
654
655
# File 'app/models/taxon_name_relationship.rb', line 649

def sv_matching_type_genus
  if self.type_name == 'TaxonNameRelationship::Typification::Family'
    if self.object_taxon_name.name.slice(0, 1) != self.subject_taxon_name.name.slice(0, 1)
      soft_validations.add(:object_taxon_name_id, 'Type genus should have the same initial letters as the family-group name')
    end
  end
end

- (Object) sv_not_specific_relationship (protected)



511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'app/models/taxon_name_relationship.rb', line 511

def sv_not_specific_relationship
  case self.type_name
    when 'TaxonNameRelationship::Typification::Genus'
      soft_validations.add(:type, 'Please specify if the type designation is original or subsequent')
    when 'TaxonNameRelationship::Typification::Genus::Monotypy'
      soft_validations.add(:type, 'Please specify if the monotypy is original or subsequent')
    when 'TaxonNameRelationship::Typification::Genus::Tautonomy'
      soft_validations.add(:type, 'Please specify if the tautonomy is absolute or Linnaean')
    when 'TaxonNameRelationship::Icn::Unaccepting'
      soft_validations.add(:type, 'Please specify the reasons for the name being Unaccepted')
    when 'TaxonNameRelationship::Icn::Unaccepting::Synonym'
      soft_validations.add(:type, 'Please specify if this is a homotypic or heterotypic synonym',
          fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being homotypic or heterotypic')
    when 'TaxonNameRelationship::Icnb::Unaccepting::Synonym'
      soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
                           fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
    when 'TaxonNameRelationship::Iczn::Invalidating'
      soft_validations.add(:type, 'Please specify the reason for the name being Invalid') unless self.subject_taxon_name.classification_invalid_or_unavailable?
    when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
      if NomenclaturalRank::Iczn::SpeciesGroup.descendants.collect{|t| t.to_s}.include?(self.subject_taxon_name.rank_string)
        soft_validations.add(:type, 'Please specify if this is a primary or secondary homonym',
            fix: :sv_fix_specify_homonymy_type, success_message: 'Homonym updated to being primary or secondary')
      end
    when 'TaxonNameRelationship::Iczn::Invalidating::Synonym'
      soft_validations.add(:type, 'Please specify if this is a objective or subjective synonym',
          fix: :sv_fix_specify_synonymy_type, success_message: 'Synonym updated to being objective or subjective')
    when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Suppression'
      soft_validations.add(:type, 'Please specify if this is a total, partial, or conditional suppression')
  end
end

- (Object) sv_objective_synonym_relationship (protected)



484
485
486
487
488
489
490
491
492
# File 'app/models/taxon_name_relationship.rb', line 484

def sv_objective_synonym_relationship
  if self.type_name =~ /TaxonNameRelationship::(Iczn::Invalidating::Synonym::Objective|Icn::Unaccepting::Synonym::Homotypic|Icnb::Unaccepting::Synonym::Objective)/
    s = self.subject_taxon_name
    o = self.object_taxon_name
    if (s.type_taxon_name != o.type_taxon_name ) || !s.has_same_primary_type(o)
      soft_validations.add(:type, 'Objective synonyms should have the same type')
    end
  end
end

- (Object) sv_specific_relationship (protected)

waaaaay to long - individual validations should be called in subclasses?



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'app/models/taxon_name_relationship.rb', line 428

def sv_specific_relationship
  s = subject_taxon_name
  o = object_taxon_name
  case type # self.type_name
    when 'TaxonNameRelationship::Iczn::Invalidating::Synonym::Subjective' || 'TaxonNameRelationship::Icn::Unaccepting::Synonym::Heterotypic'
      if (s.type_taxon_name == o.type_taxon_name && !s.type_taxon_name.nil? ) || (!s.get_primary_type.empty? && s.has_same_primary_type(o) )
        soft_validations.add(:type, 'Subjective synonyms should not have the same type')
      end
    when 'TaxonNameRelationship::Iczn::Invalidating::Homonym'
      soft_validations.add(:type, 'Names are not similar enough to be homonyms') unless s.cached_primary_homonym_alternative_spelling == o.cached_primary_homonym_alternative_spelling
    when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' || 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Suppressed'
      if s.original_genus != o.original_genus
        soft_validations.add(:type, 'Primary homonyms should have the same original genus')
      elsif s.cached_primary_homonym_alternative_spelling != o.cached_primary_homonym_alternative_spelling
        soft_validations.add(:type, 'Names are not similar enough to be homonyms')
      end
      if type == 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Primary::Forgotten' && s.year_of_publication > 1899
        soft_validations.add(:type, 'Taxon was not described after 1899')
      end
    when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary'

      if s.original_genus == o.original_genus && !s.original_genus.nil?
        soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'")
      elsif s.get_valid_taxon_name.ancestor_at_rank('genus') != o.get_valid_taxon_name.ancestor_at_rank('genus')
        soft_validations.add(:type, "Secondary homonyms should be placed in the same genus, the homonymy should be deleted or changed to 'secondary homonym replaced before 1961'")
      elsif s.cached_secondary_homonym_alternative_spelling != o.cached_secondary_homonym_alternative_spelling
        soft_validations.add(:type, 'Names are not similar enough to be homonyms')
      end

      if (s.all_generic_placements & o.all_generic_placements).empty?
        soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
      end

    when 'TaxonNameRelationship::Iczn::Invalidating::Homonym::Secondary::Secondary1961'
     
      soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
      soft_validations.add(:type, "Both species described in the same genus, they are 'primary homonyms'") if s.original_genus == o.original_genus && !s.original_genus.nil?
      soft_validations.add(:base, 'Source is not selected') unless source 
     
      soft_validations.add(:base, 'Taxon should be treated a homonym before 1961') if self.source && self.source.year > 1960

      if (s.all_generic_placements & o.all_generic_placements).empty?
        soft_validations.add(:base, 'No combination available showing both species placed in the same genus') 
      end

    when 'TaxonNameRelationship::Iczn::PotentiallyValidating::FamilyBefore1961'
      soft_validations.add(:type, 'Taxon was not described before 1961') if s.year_of_publication > 1960
      soft_validations.add(:base, 'Taxon should be accepted as a replacement name before 1961') if self.source && self.source.year > 1960

    when 'TaxonNameRelationship::Typification::Genus::SubsequentDesignation'
      soft_validations.add(:type, 'Genus described after 1930 is nomen nudum, if type was not designated in the original publication') if o.year_of_publication > 1930
    when 'TaxonNameRelationship::Typification::Genus::Monotypy::Original'
      # @todo Check if more than one species associated with the genus in the original paper
  end
end

- (Object) sv_synonym_linked_to_valid_name (protected)



599
600
601
602
603
604
605
606
607
608
609
610
611
612
# File 'app/models/taxon_name_relationship.rb', line 599

def sv_synonym_linked_to_valid_name
  #synonyms and misspellings should be linked to valid names
  if TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(self.type_name)
    obj = self.object_taxon_name
    subj = self.subject_taxon_name
    if obj.get_valid_taxon_name != obj
      soft_validations.add(:object_taxon_name_id, "The #{self.subject_status} should be associated with a valid name",
                           fix: :sv_fix_synonym_linked_to_valid_name, success_message: 'The associated taxon was updated')
    elsif obj.parent_id != subj.parent_id
      soft_validations.add(:subject_taxon_name_id, "The #{self.subject_status} should have the same parent with the associated taxon",
                           fix: :sv_fix_subject_parent_update, success_message: 'The parent was updated')
    end
  end
end

- (Object) sv_synonym_relationship (protected)



494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'app/models/taxon_name_relationship.rb', line 494

def sv_synonym_relationship
  relationships = TAXON_NAME_RELATIONSHIP_NAMES_INVALID +
      TaxonNameRelationship.collect_to_s(TaxonNameRelationship::Typification::Genus::SubsequentDesignation,
          TaxonNameRelationship::Typification::Genus::RulingByCommission)
  if relationships.include?(self.type_name)
    if self.source
      date1 = self.source.cached_nomenclature_date.to_time
      date2 = self.subject_taxon_name.nomenclature_date
      if !!date1 && !!date2
        soft_validations.add(:base, 'Taxon was not described at the time of citation') if date2 > date1
      end
    else
      soft_validations.add(:base, 'Source is not selected')
    end
  end
end

- (Object) sv_validate_disjoint_object (protected)



405
406
407
408
409
410
411
412
413
414
# File 'app/models/taxon_name_relationship.rb', line 405

def sv_validate_disjoint_object
  classifications = self.object_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
  disjoint_object_classes = self.type_class.disjoint_object_classes
  compare = disjoint_object_classes & classifications
  compare.each do |i|
    c = i.demodulize.underscore.humanize.downcase
    soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
    soft_validations.add(:object_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
  end
end

- (Object) sv_validate_disjoint_relationships (protected)



396
397
398
399
400
401
402
403
# File 'app/models/taxon_name_relationship.rb', line 396

def sv_validate_disjoint_relationships
  subject_relationships = TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).not_self(self)
  subject_relationships.find_each  do |i|
    if self.type_class.disjoint_taxon_name_relationships.include?(i.type_name)
      soft_validations.add(:type, "Conflicting with another relationship: '#{i.subject_status}'")
    end
  end
end

- (Object) sv_validate_disjoint_subject (protected)



416
417
418
419
420
421
422
423
424
425
# File 'app/models/taxon_name_relationship.rb', line 416

def sv_validate_disjoint_subject
  classifications = self.subject_taxon_name.taxon_name_classifications(true).map{|i| i.type_name}
  disjoint_subject_classes = self.type_class.disjoint_subject_classes
  compare = disjoint_subject_classes & classifications
  compare.each do |i|
    c = i.demodulize.underscore.humanize.downcase
    soft_validations.add(:type, "Relationship conflicting with the status: '#{c}'")
    soft_validations.add(:subject_taxon_name_id, "Taxon has a conflicting status: '#{c}'")
  end
end

- (Object) sv_validate_priority (protected)



657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
# File 'app/models/taxon_name_relationship.rb', line 657

def sv_validate_priority
  unless self.type_class.nomenclatural_priority.nil?
    date1 = self.subject_taxon_name.nomenclature_date
    date2 = self.object_taxon_name.nomenclature_date
   if !!date1 and !!date2
      invalid_statuses = TAXON_NAME_CLASS_NAMES_UNAVAILABLE_AND_INVALID & self.subject_taxon_name.taxon_name_classifications.collect{|c| c.type_class.to_s}
      case self.type_class.nomenclatural_priority
        when :direct
          if date2 > date1 && invalid_statuses.empty?
            if self.type_name =~ /TaxonNameRelationship::Iczn::Invalidating::Homonym/
              soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon")
            elsif self.type_name =~ /::Iczn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Iczn::Invalidating::Homonym', 'TaxonNameRelationship::Iczn::Validating').not_self(self).empty?
              soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also a homonym or conserved")
            elsif self.type_name =~ /::Icn::/ && TaxonNameRelationship.where_subject_is_taxon_name(self.subject_taxon_name).with_two_type_bases('TaxonNameRelationship::Icn::Accepting::Conserved', 'TaxonNameRelationship::Icn::Accepting::Sanctioned').not_self(self).empty?
              soft_validations.add(:type, "#{self.object_status.capitalize} should not be older than related taxon, unless it is also conserved or sanctioned")
            end
          end
        when :reverse
          if date1 > date2 && invalid_statuses.empty?
            if self.type_name =~ /TaxonNameRelationship::(Typification|Combination|OriginalCombination)/ && self.type_name != 'TaxonNameRelationship::Typification::Genus::RulingByCommission'
              soft_validations.add(:subject_taxon_name_id, "#{self.subject_status.capitalize} should not be younger than the taxon")
            else
              soft_validations.add(:type, "#{self.subject_status.capitalize} should not be younger than related taxon")
            end
          end
      end
    end
  end
end

- (Object) sv_validate_required_relationships (protected)

region Soft Validation



388
389
390
391
392
393
394
# File 'app/models/taxon_name_relationship.rb', line 388

def sv_validate_required_relationships
  object_relationships = TaxonNameRelationship.where_object_is_taxon_name(self.object_taxon_name).not_self(self).collect{|r| r.type}
  required = self.type_class.required_taxon_name_relationships - object_relationships
  required.each do |r|
    soft_validations.add(:type, " Presence of #{self.subject_status} requires selection of #{r.demodulize.underscore.humanize.downcase}")
  end
end

- (TaxonNameRelationship, String) type_class

Returns the type as a class, if legal, else a string ! Strangeish

Returns:



217
218
219
220
# File 'app/models/taxon_name_relationship.rb', line 217

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

- (Object) type_class=(value)



211
212
213
# File 'app/models/taxon_name_relationship.rb', line 211

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

- (String?) type_name

Returns the type of this relationship, IF the type is a valid name, else nil

Returns:

  • (String, nil)

    the type of this relationship, IF the type is a valid name, else nil



206
207
208
209
# File 'app/models/taxon_name_relationship.rb', line 206

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

- (Object) validate_object_must_equal_subject_for_uncertain_placement (protected)



276
277
278
279
280
# File 'app/models/taxon_name_relationship.rb', line 276

def validate_object_must_equal_subject_for_uncertain_placement
  if self.object_taxon_name_id != self.subject_taxon_name.parent_id && self.type_name == 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
    errors.add(:object_taxon_name_id, 'The parent and related taxon should match')
  end
end

- (Object) validate_rank_group (protected)



325
326
327
328
329
330
331
# File 'app/models/taxon_name_relationship.rb', line 325

def validate_rank_group
  if self.type =~ /Hybrid/ && self.subject_taxon_name && self.object_taxon_name
    if self.subject_taxon_name.rank_class.parent != self.object_taxon_name.rank_class.parent
      errors.add(:subject_taxon_name_id, "Rank of taxon (#{self.subject_taxon_name.rank_class.rank_name}) is not compatible with the rank of hybrid (#{self.object_taxon_name.rank_class.rank_name})")
    end
  end
end

- (Object) validate_subject_and_object_are_not_identical (protected)



270
271
272
273
274
# File 'app/models/taxon_name_relationship.rb', line 270

def validate_subject_and_object_are_not_identical
  if self.object_taxon_name == self.subject_taxon_name
    errors.add(:object_taxon_name, 'Taxon should not refer to itself') unless self.type =~ /OriginalCombination/
  end
end

- (Object) validate_subject_and_object_ranks (protected)



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

def validate_subject_and_object_ranks
  tname = self.type_name

  if tname =~ /TaxonNameRelationship::(Icnb|Icn|Iczn)/ && tname != 'TaxonNameRelationship::Iczn::Validating::UncertainPlacement'
    rank_group = self.subject_taxon_name.rank_class.parent
    unless rank_group == self.object_taxon_name.rank_class.parent
      errors.add(:object_taxon_name_id, "Rank of related taxon should be in the #{rank_group.rank_name}")
    end
  end

  unless self.type_class.blank? # only validate if it is set
    if object_taxon_name
      if object_taxon_name.type == 'Protonym' || object_taxon_name.type == 'Hybrid'
        unless self.type_class.valid_object_ranks.include?(self.object_taxon_name.rank_string)
          errors.add(:object_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
          errors.add(:type, 'Not compatible with the rank of object taxon')
        end
      end
    end

    if subject_taxon_name
      if subject_taxon_name.type == 'Protonym' || subject_taxon_name.type == 'Hybrid'
        unless self.type_class.valid_subject_ranks.include?(self.subject_taxon_name.parent.rank_string)
          soft_validations.add(:subject_taxon_name_id, 'Rank of this taxon is not compatible with the relationship')
          soft_validations.add(:type, 'Not compatible with the rank of subject taxon')
        end
      end
    end
  end
end

- (Object) validate_subject_and_object_share_code (protected)

TODO:

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



264
265
266
267
268
# File 'app/models/taxon_name_relationship.rb', line 264

def validate_subject_and_object_share_code
  if object_taxon_name.type  == 'Protonym' && subject_taxon_name.type == 'Protonym'
    errors.add(:object_taxon_name_id, 'The related taxon is from different potentially_validating code') if subject_taxon_name.rank_class.nomenclatural_code != object_taxon_name.rank_class.nomenclatural_code
  end
end

- (Object) validate_type (protected)

region Validation



248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'app/models/taxon_name_relationship.rb', line 248

def validate_type
  unless TAXON_NAME_RELATIONSHIP_NAMES.include?(type)
    errors.add(:type, "'#{type}' is not a valid taxon name relationship")
  end

  if object_taxon_name.class.to_s == 'Protonym' || object_taxon_name.class.to_s == 'Hybrid'
    errors.add(:type, "'#{type}' is not a valid taxon name relationship") if /TaxonNameRelationship::Combination::/.match(self.type)
  end

  if object_taxon_name.class.to_s == 'Combination'
    errors.add(:type, "'#{type}' is not a valid taxon name relationship") unless /TaxonNameRelationship::Combination::/.match(self.type)
  end
end

- (Object) validate_uniqueness_of_typification_object (protected)

def validate_uniqueness_of_synonym_subject ##### Protonym historically could be listed as a synonym to different taxa

if !self.type.nil? && /Synonym/.match(self.type_name) && !TaxonNameRelationship.where(subject_taxon_name_id: self.subject_taxon_name_id).with_type_contains('Synonym').not_self(self).empty?
  errors.add(:subject_taxon_name_id, 'Only one synonym relationship is allowed')
end

end



319
320
321
322
323
# File 'app/models/taxon_name_relationship.rb', line 319

def validate_uniqueness_of_typification_object
  if /Typification/.match(self.type_name) && !TaxonNameRelationship.where(object_taxon_name_id: self.object_taxon_name_id).with_type_contains('Typification').not_self(self).empty?
    errors.add(:object_taxon_name_id, 'Only one type relationship is allowed')
  end
end