Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validation for circular dependencies in concept relations added #374

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
22 changes: 22 additions & 0 deletions app/models/concept/validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module Validations
validate :unique_alt_labels
validate :exclusive_broader_and_narrower_concepts
validate :no_self_reference_concept_relation
validate :no_circular_concept_relations
end

# top term and broader relations are mutually exclusive
Expand Down Expand Up @@ -142,5 +143,26 @@ def no_self_reference_concept_relation
end
end
end

def no_circular_concept_relations
return unless validatable_for_publishing?
broaders = collect_related_concepts(broader_relations, 'broader_relations')
narrowers = collect_related_concepts(narrower_relations, 'narrower_relations')
relateds = concept_relation_skos_relateds.map { |r| r.target.origin }
circulars = broaders & narrowers
circulars.push(*broaders & relateds)
circulars.push(*narrowers & relateds)

return unless circulars.any?
errors.add :base, I18n.t('txt.models.concept.no_circular_relations', concepts: Iqvoc::Concept.base_class.where(origin: circulars).map { |c| c.pref_label }.flatten.join(', '))
end

def collect_related_concepts relations, relation_type, result_array = []
return [] if relations.nil? || relations.empty?
relation_concepts = relations.select {|r| r.present? && result_array.exclude?(r.target.origin) }.collect { |r| r.target }
result_array.push(*relation_concepts.map { |r| r.origin })
result_array.push(*relation_concepts.map { |r| r.concept_relation_skos_relateds.map { |rc| rc.target.origin } }.flatten)
result_array.push(*collect_related_concepts(relation_concepts.map { |rc| rc.send(relation_type) }.flatten, relation_type, result_array))
end
end
end
1 change: 1 addition & 0 deletions config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ de:
top_term_rooted_error: "Begriffe oberster Ebene dürfen nicht als spezifischere Begriffe verwendet werden."
invalid_rank_for_ranked_relations: 'Ungültiger Rang für %{relation} "%{relation_target_label}".'
no_narrower_and_broader_relations: 'Sowohl allgemeinere als auch spezifischere Beziehungen: %{concepts}'
no_circular_relations: 'Es gibt zirkuläre Abhängigkeiten durch folgende Konzepte und ihre Beziehungen: %{concepts}'
no_self_reference: 'Konzepte dürfen nicht auf sich selbst verweisen. Bitte "Beziehungen" prüfen.'
main_pref_label_language_missing_error: "Es muss mindestens ein bevorzugtes Label in der Hauptsprache des Thesaurus existieren."
pref_labels_with_same_languages_error: "Es darf nur jeweils ein bevorzugtes Label zu einer Sprache geben."
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ en:
top_term_rooted_error: "Top Terms must not be used as narrower terms."
invalid_rank_for_ranked_relations: 'Invalid rank for %{relation} "%{relation_target_label}".'
no_narrower_and_broader_relations: 'Both narrower and broader relations: %{concepts}'
no_circular_relations: 'There are circular dependencies through the following concepts and their relations: %{concepts}'
no_self_reference: 'Concepts must not reference itself. Please check "relations".'
main_pref_label_language_missing_error: "There has to be one preferred Label in the main language of the thesaurus."
pref_labels_with_same_languages_error: "There must be only one preferred Label per language."
Expand Down
46 changes: 45 additions & 1 deletion test/models/concept_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class ConceptTest < ActiveSupport::TestCase
assert_equal 1, concept.broader_relations.count
refute concept.publishable?
error_messages = concept.errors.full_messages_for(:base)
assert_equal 1, error_messages.count
assert_equal 2, error_messages.count
index = error_messages.first.index(':')
assert_equal ": Bear", error_messages.first[index..index + 5]
assert_nil error_messages.first.index(',')
Expand Down Expand Up @@ -249,4 +249,48 @@ class ConceptTest < ActiveSupport::TestCase
refute wolf_concept.publishable?
assert wolf_concept.errors.full_messages_for(:base).include? I18n.t('txt.models.concept.no_self_reference')
end

test 'no circular concept relation dependencies' do
animal_concept = RDFAPI.devour 'animal', 'a', 'skos:Concept'
RDFAPI.devour animal_concept, 'skos:prefLabel', '"Animal"@en'
animal_concept.save

mammal_concept = RDFAPI.devour 'mammal', 'a', 'skos:Concept'
RDFAPI.devour mammal_concept, 'skos:prefLabel', '"Mammal"@en'
RDFAPI.devour mammal_concept, 'skos:broader', animal_concept
mammal_concept.save

bear_concept = RDFAPI.devour 'bear', 'a', 'skos:Concept'
RDFAPI.devour bear_concept, 'skos:prefLabel', '"Bear"@en'
RDFAPI.devour bear_concept, 'skos:broader', mammal_concept
RDFAPI.devour bear_concept, 'skos:narrower', animal_concept
bear_concept.save

assert_equal 1, bear_concept.narrower_relations.count
assert_equal 1, bear_concept.broader_relations.count
refute bear_concept.publishable?
refute mammal_concept.publishable?
refute animal_concept.publishable?
end

test 'no circular related concept relation dependencies' do
animal_concept = RDFAPI.devour 'animal', 'a', 'skos:Concept'
RDFAPI.devour animal_concept, 'skos:prefLabel', '"Animal"@en'
animal_concept.save

mammal_concept = RDFAPI.devour 'mammal', 'a', 'skos:Concept'
RDFAPI.devour mammal_concept, 'skos:prefLabel', '"Mammal"@en'
RDFAPI.devour mammal_concept, 'skos:broader', animal_concept
RDFAPI.devour mammal_concept, 'skos:related', animal_concept
mammal_concept.save

bear_concept = RDFAPI.devour 'bear', 'a', 'skos:Concept'
RDFAPI.devour bear_concept, 'skos:prefLabel', '"Bear"@en'
RDFAPI.devour bear_concept, 'skos:broader', mammal_concept
bear_concept.save

assert bear_concept.publishable?
refute mammal_concept.publishable?
refute animal_concept.publishable?
end
end