diff --git a/app/models/protonym.rb b/app/models/protonym.rb index 14d95b5920..eda84a1af4 100644 --- a/app/models/protonym.rb +++ b/app/models/protonym.rb @@ -13,7 +13,7 @@ class Protonym < TaxonName where("taxon_name_relationships.type LIKE 'TaxonNameRelationship::OriginalCombination::%'") }, class_name: 'TaxonNameRelationship', foreign_key: :object_taxon_name_id - has_many :type_material + has_many :type_materials, class_name: 'TypeMaterial' # subject object # Aus original_genus of bus @@ -64,10 +64,13 @@ class Protonym < TaxonName scope :without_taxon_name_classification, -> (taxon_name_class_name) { where('id not in (SELECT taxon_name_id FROM taxon_name_classifications WHERE type LIKE ?)', "#{taxon_name_class_name}")} scope :without_taxon_name_classification_array, -> (taxon_name_class_name_array) { where('id not in (SELECT taxon_name_id FROM taxon_name_classifications WHERE type in (?))', taxon_name_class_name_array) } scope :without_taxon_name_classifications, -> { includes(:taxon_name_classifications).where(taxon_name_classifications: {taxon_name_id: nil}) } + scope :with_type_material_array, -> (type_material_array) { joins('LEFT OUTER JOIN "type_materials" ON "type_materials"."protonym_id" = "taxon_names"."id"').where("type_materials.biological_object_id in (?) AND type_materials.type_type in ('holotype', 'neotype', 'lectotype', 'syntype', 'syntypes')", type_material_array) } + scope :with_type_of_taxon_names, -> (type_id) { includes(:related_taxon_name_relationships).where("taxon_name_relationships.type LIKE 'TaxonNameRelationship::Typification%' AND taxon_name_relationships.subject_taxon_name_id = ?", type_id).references(:related_taxon_name_relationships) } + scope :not_self, -> (id) {where('taxon_names.id <> ?', id )} scope :that_is_valid, -> { joins('LEFT OUTER JOIN taxon_name_relationships tnr ON taxon_names.id = tnr.subject_taxon_name_id'). - where('taxon_names.id NOT IN (SELECT subject_taxon_name_id FROM taxon_name_relationships WHERE type LIKE "TaxonNameRelationship::Iczn::Invalidating%" OR type LIKE "TaxonNameRelationship::Icn::Unaccepting%")') + where("taxon_names.id NOT IN (SELECT subject_taxon_name_id FROM taxon_name_relationships WHERE type LIKE 'TaxonNameRelationship::Iczn::Invalidating%' OR type LIKE 'TaxonNameRelationship::Icn::Unaccepting%')") } soft_validate(:sv_validate_parent_rank, set: :validate_parent_rank) @@ -77,6 +80,8 @@ class Protonym < TaxonName soft_validate(:sv_validate_coordinated_names, set: :validate_coordinated_names) soft_validate(:sv_single_sub_taxon, set: :single_sub_taxon) soft_validate(:sv_parent_priority, set: :parent_priority) + soft_validate(:sv_homotypic_synonyms, set: :homotypic_synonyms) + soft_validate(:sv_potential_homonyms, set: :potential_homonyms) before_validation :check_format_of_name, :validate_rank_class_class, @@ -140,8 +145,8 @@ def self.family_group_base(name_string) def get_primary_type return [] unless self.rank_class.parent.to_s =~ /Species/ - s = self.type_material.syntypes - p = self.type_material.primary + s = self.type_materials.syntypes + p = self.type_materials.primary if s.empty? && p.count == 1 p elsif p.empty? && s.empty? @@ -289,7 +294,7 @@ def sv_fix_coordinated_names types2.each do |t| new_type_material.push({type_type: t.type_type, protonym_id: t.protonym_id, biological_object_id: t.biological_object_id, source_id: t.source_id}) end - self.type_material.build(new_type_material) + self.type_materials.build(new_type_material) fixed = true end @@ -330,9 +335,9 @@ def sv_type_placement def sv_primary_types if self.rank_class.parent.to_s =~ /Species/ - if self.type_material.primary.empty? && self.type_material.syntypes.empty? + if self.type_materials.primary.empty? && self.type_materials.syntypes.empty? soft_validations.add(:base, 'Primary type is not selected') - elsif self.type_material.primary.count > 1 || (!self.type_material.primary.empty? && !self.type_material.syntypes.empty?) + elsif self.type_materials.primary.count > 1 || (!self.type_materials.primary.empty? && !self.type_materials.syntypes.empty?) soft_validations.add(:base, 'More than one primary type are selected') end end @@ -406,6 +411,44 @@ def sv_parent_priority end end + def sv_homotypic_synonyms + unless self.unavailable_or_invalid? + if self.id == self.lowest_rank_coordinated_taxon.id + possible_synonyms = [] + if self.rank_class.to_s =~ /Species/ + primary_types = self.get_primary_type + unless primary_types.empty? + p = primary_types.collect!{|t| t.biological_object_id} + possible_synonyms = Protonym.with_type_material_array(p).that_is_valid.not_self(self.id) + end + else + type = self.type_taxon_name + unless type.nil? + possible_synonyms = Protonym.with_type_of_taxon_names(type.id).that_is_valid.not_self(self.id) + end + end + unless possible_synonyms.empty? + possible_synonyms.select!{|s| s.id == s.lowest_rank_coordinated_taxon.id} + end + date1 = self.nomenclature_date unless possible_synonyms.empty? + possible_synonyms.each do |s| + date2 = s.nomenclature_date + if date1.nil? || date2.nil? || (not date1 < date2) + soft_validations.add(:base, "Taxon should be a synonym of #{s.cached_name + ' ' + s.cached_author_year} since they share the same type") + end + end + end + end + end + + def sv_potential_homonyms + if self.id == self.lowest_rank_coordinated_taxon + if self.rank_class.to_s =~ /Species/ + else + end + end + end + #endregion end diff --git a/app/models/taxon_name.rb b/app/models/taxon_name.rb index 61cb09597a..1e2c97e2a2 100644 --- a/app/models/taxon_name.rb +++ b/app/models/taxon_name.rb @@ -616,7 +616,7 @@ def sv_fix_parent_is_valid_name def sv_cached_names # if updated, update also set_cached_names cached = true - if self.cached_name.blank? + if self.primary_homonym.blank? cached = false elsif self.cached_author_year != get_author_and_year cached = false @@ -635,10 +635,21 @@ def sv_cached_names end unless cached soft_validations.add(:base, 'Cached values should be updated', - fix: :set_cached_names, success_message: 'Cached values were updated') + fix: :sv_fix_cached_names, success_message: 'Cached values were updated') end end + def sv_fix_cached_names + begin + TaxonName.transaction do + self.save + return true + end + rescue + end + false + end + def sv_validate_parent_rank true # see validation in Protonym.rb end @@ -667,6 +678,15 @@ def sv_parent_priority true # see validation in Protonym.rb end + def sv_homotypic_synonyms + true # see validation in Protonym.rb + end + + def sv_potential_homonyms + true # see validation in Protonym.rb + end + + #endregion end diff --git a/spec/models/protonym_spec.rb b/spec/models/protonym_spec.rb index 8c27945bf0..c6ef945431 100644 --- a/spec/models/protonym_spec.rb +++ b/spec/models/protonym_spec.rb @@ -265,30 +265,30 @@ context 'missing_fields' do specify "source author, year are missing" do - @species.soft_validate(:missing_fields) - expect(@species.soft_validations.messages_on(:source_id).empty?).to be_true - expect(@species.soft_validations.messages_on(:verbatim_author).empty?).to be_true - expect(@species.soft_validations.messages_on(:year_of_publication).empty?).to be_true - end + @species.soft_validate(:missing_fields) + expect(@species.soft_validations.messages_on(:source_id).empty?).to be_true + expect(@species.soft_validations.messages_on(:verbatim_author).empty?).to be_true + expect(@species.soft_validations.messages_on(:year_of_publication).empty?).to be_true + end specify 'author and year are missing' do @kingdom.soft_validate(:missing_fields) expect(@kingdom.soft_validations.messages_on(:verbatim_author).empty?).to be_false expect(@kingdom.soft_validations.messages_on(:year_of_publication).empty?).to be_false end specify 'fix author and year from the source' do - @source.update(year: 1758, author: 'Linnaeus') - @source.save - @kingdom.source = @source - @kingdom.soft_validate(:missing_fields) - expect(@kingdom.soft_validations.messages_on(:verbatim_author).empty?).to be_false - expect(@kingdom.soft_validations.messages_on(:year_of_publication).empty?).to be_false - @kingdom.fix_soft_validations # get author and year from the source - @kingdom.soft_validate(:missing_fields) - expect(@kingdom.soft_validations.messages_on(:verbatim_author).empty?).to be_true - expect(@kingdom.soft_validations.messages_on(:year_of_publication).empty?).to be_true - expect(@kingdom.verbatim_author).to eq('Linnaeus') - expect(@kingdom.year_of_publication).to eq(1758) - end + @source.update(year: 1758, author: 'Linnaeus') + @source.save + @kingdom.source = @source + @kingdom.soft_validate(:missing_fields) + expect(@kingdom.soft_validations.messages_on(:verbatim_author).empty?).to be_false + expect(@kingdom.soft_validations.messages_on(:year_of_publication).empty?).to be_false + @kingdom.fix_soft_validations # get author and year from the source + @kingdom.soft_validate(:missing_fields) + expect(@kingdom.soft_validations.messages_on(:verbatim_author).empty?).to be_true + expect(@kingdom.soft_validations.messages_on(:year_of_publication).empty?).to be_true + expect(@kingdom.verbatim_author).to eq('Linnaeus') + expect(@kingdom.year_of_publication).to eq(1758) + end end context 'coordinated taxa' do @@ -381,6 +381,7 @@ expect(@subfamily.soft_validations.messages_on(:base).empty?).to be_false @subfamily.fix_soft_validations @subfamily.reload + expect(@subfamily.valid?).to be_true @subfamily.soft_validate expect(@subfamily.soft_validations.messages_on(:base).empty?).to be_true end @@ -495,6 +496,38 @@ expect(@subgenus.soft_validations.messages_on(:base).count).to eq(1) end end + + context 'missing synonym relationship' do + specify 'same type species' do + g1 = FactoryGirl.create(:relationship_genus, name: 'Aus', parent: @family) + g2 = FactoryGirl.create(:relationship_genus, name: 'Bus', parent: @family) + s1 = FactoryGirl.create(:relationship_species, name: 'cus', parent: g1) + g1.type_species = s1 + g2.type_species = s1 + expect(g1.save).to be_true + expect(g2.save).to be_true + g1.soft_validate(:homotypic_synonyms) + expect(g1.soft_validations.messages_on(:base).count).to eq(1) + g1.iczn_set_as_unnecessary_replaced_name = g2 + expect(g1.save).to be_true + g1.soft_validate(:homotypic_synonyms) + expect(g1.soft_validations.messages_on(:base).empty?).to be_true + end + specify 'same type specimen' do + s1 = FactoryGirl.create(:relationship_species, name: 'bus', parent: @genus) + s2 = FactoryGirl.create(:relationship_species, name: 'cus', parent: @genus) + t1 = FactoryGirl.create(:valid_type_material, protonym: s1, type_type: 'holotype') + t2 = FactoryGirl.create(:valid_type_material, protonym: s2, type_type: 'neotype', biological_object_id: t1.biological_object_id) + expect(s1.save).to be_true + expect(s2.save).to be_true + s1.soft_validate(:homotypic_synonyms) + expect(s1.soft_validations.messages_on(:base).count).to eq(1) + s1.iczn_set_as_unjustified_emendation_of = s2 + expect(s1.save).to be_true + s1.soft_validate(:homotypic_synonyms) + expect(s1.soft_validations.messages_on(:base).empty?).to be_true + end + end end context 'scopes' do