From 905f76ea31eff1d6dacb10a97dffde3b89a00e21 Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Tue, 8 Apr 2025 23:27:58 +0200 Subject: [PATCH 01/19] Feature: add mod-api SemanticArtefactCatalogRecord model (#202) * add record model * add attributes to record model * add additional attributes and make default attributes * fix record context in catalog * address request changes --- .../semantic_artefact/attribute_fetcher.rb | 13 +++ .../models/mod/semantic_artefact.rb | 3 +- .../models/mod/semantic_artefact_catalog.rb | 2 +- .../mod/semantic_artefact_catalog_record.rb | 97 +++++++++++++++++++ 4 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 lib/ontologies_linked_data/models/mod/semantic_artefact_catalog_record.rb diff --git a/lib/ontologies_linked_data/concerns/semantic_artefact/attribute_fetcher.rb b/lib/ontologies_linked_data/concerns/semantic_artefact/attribute_fetcher.rb index 0a6d18c4..1198b565 100644 --- a/lib/ontologies_linked_data/concerns/semantic_artefact/attribute_fetcher.rb +++ b/lib/ontologies_linked_data/concerns/semantic_artefact/attribute_fetcher.rb @@ -13,12 +13,25 @@ def bring(*attributes) hash[model][attr] = mapped_attr end + populate_from_self(grouped_attributes[self.class]) if grouped_attributes[self.class].any? fetch_from_ontology(grouped_attributes[:ontology]) if grouped_attributes[:ontology].any? fetch_from_submission(grouped_attributes[:ontology_submission]) if grouped_attributes[:ontology_submission].any? fetch_from_metrics(grouped_attributes[:metric]) if grouped_attributes[:metric].any? end private + + def populate_from_self(attributes) + attributes.each_key do |attr| + if self.class.handler?(attr) + send(attr) + else + value = self.class.default(attr) + value = value.call(self) if value.is_a?(Proc) + send("#{attr}=", value || (respond_to?(attr) ? send(attr) : nil)) + end + end + end def fetch_from_ontology(attributes) return if attributes.empty? diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact.rb index d0df179d..f75b5600 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact.rb @@ -1,4 +1,5 @@ require 'ontologies_linked_data/models/mod/semantic_artefact_distribution' +require 'ontologies_linked_data/models/mod/semantic_artefact_catalog_record' require 'ontologies_linked_data/models/skos/scheme' require 'ontologies_linked_data/models/skos/collection' require 'ontologies_linked_data/models/skos/skosxl' @@ -119,7 +120,7 @@ class SemanticArtefact < LinkedData::Models::Base links_load :acronym link_to LinkedData::Hypermedia::Link.new("distributions", lambda {|s| "artefacts/#{s.acronym}/distributions"}, LinkedData::Models::SemanticArtefactDistribution.type_uri), - LinkedData::Hypermedia::Link.new("record", lambda {|s| "artefacts/#{s.acronym}/record"}), + LinkedData::Hypermedia::Link.new("record", lambda {|s| "artefacts/#{s.acronym}/record"}, LinkedData::Models::SemanticArtefactCatalogRecord.type_uri), LinkedData::Hypermedia::Link.new("resources", lambda {|s| "artefacts/#{s.acronym}/resources"}), LinkedData::Hypermedia::Link.new("single_resource", lambda {|s| "artefacts/#{s.acronym}/resources/{:resourceID}"}), LinkedData::Hypermedia::Link.new("classes", lambda {|s| "artefacts/#{s.acronym}/classes"}, LinkedData::Models::Class.uri_type), diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb index a6331fca..47837e2b 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb @@ -148,7 +148,7 @@ class SemanticArtefactCatalog < LinkedData::Models::Base LinkedData::Hypermedia::Link.new("submissions", lambda {|s| "submissions"}, LinkedData::Models::OntologySubmission.type_uri), LinkedData::Hypermedia::Link.new("submission_metadata", lambda {|s| "submission_metadata"}, nil), LinkedData::Hypermedia::Link.new("artefacts", lambda {|s| "artefacts"}, LinkedData::Models::SemanticArtefact.type_uri), - LinkedData::Hypermedia::Link.new("records", lambda {|s| "records"}, nil), + LinkedData::Hypermedia::Link.new("records", lambda {|s| "records"}, LinkedData::Models::SemanticArtefactCatalogRecord.type_uri), LinkedData::Hypermedia::Link.new("users", lambda {|s| "users"}, LinkedData::Models::User.type_uri), LinkedData::Hypermedia::Link.new("agents", lambda {|s| "agents"}, LinkedData::Models::Agent.type_uri), LinkedData::Hypermedia::Link.new("groups", lambda {|s| "groups"}, LinkedData::Models::Group.type_uri), diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog_record.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog_record.rb new file mode 100644 index 00000000..114059b4 --- /dev/null +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog_record.rb @@ -0,0 +1,97 @@ +module LinkedData + module Models + class SemanticArtefactCatalogRecord < LinkedData::Models::Base + include LinkedData::Concerns::SemanticArtefact::AttributeMapping + include LinkedData::Concerns::SemanticArtefact::AttributeFetcher + + model :SemanticArtefactCatalogRecord, namespace: :mod, name_with: ->(r) { record_id_generator(r) } + + # mod specs attributes + attribute_mapped :acronym, namespace: :omv, mapped_to: { model: :ontology } + attribute_mapped :relatedArtefactId, mapped_to: { model: self }, default: ->(r) { RDF::URI("#{(Goo.id_prefix)}artefacts/#{CGI.escape(r.ontology.acronym.to_s)}") } + attribute_mapped :homepage, namespace: :foaf, mapped_to: { model: self }, default: ->(r) { RDF::URI("http://#{LinkedData.settings.ui_host}/#{r.ontology.acronym}") } #handler: :record_home_page + attribute_mapped :created, namespace: :dcterms, mapped_to: { model: self }, handler: :get_creation_date + attribute_mapped :modified, namespace: :dcterms, mapped_to: { model: self }, handler: :get_modification_date + attribute_mapped :curatedOn, namespace: :pav, mapped_to: { model: :ontology_submission } + attribute_mapped :curatedBy, namespace: :pav, mapped_to: { model: :ontology_submission } + + # additional attributes + attribute_mapped :viewingRestriction, mapped_to: { model: :ontology } + attribute_mapped :administeredBy, mapped_to: { model: :ontology } + attribute_mapped :doNotUpdate, mapped_to: { model: :ontology } + attribute_mapped :flat, mapped_to: { model: :ontology } + attribute_mapped :summaryOnly, mapped_to: { model: :ontology } + attribute_mapped :acl, mapped_to: { model: :ontology } + attribute_mapped :ontologyType, mapped_to: { model: :ontology } + attribute_mapped :classType, mapped_to: { model: :ontology_submission } + attribute_mapped :missingImports, mapped_to: { model: :ontology_submission } + attribute_mapped :submissionStatus, mapped_to: { model: :ontology_submission } + attribute_mapped :pullLocation, mapped_to: { model: :ontology_submission } + attribute_mapped :dataDump, namespace: :void, mapped_to: { model: :ontology_submission } + attribute_mapped :csvDump, mapped_to: { model: :ontology_submission } + attribute_mapped :uploadFilePath, mapped_to: { model: :ontology_submission } + attribute_mapped :diffFilePath, mapped_to: { model: :ontology_submission } + attribute_mapped :masterFileName, mapped_to: { model: :ontology_submission } + + + attribute :ontology, type: :ontology, enforce: [:existence] + attribute :submission, type: :ontology_submission + + # Access control + read_restriction_based_on ->(record) { record.ontology } + + serialize_default :acronym, :relatedArtefactId, :homepage, :created, :modified, :curatedOn, :curatedBy + serialize_never :ontology, :submission + + def self.record_id_generator(record) + record.ontology.bring(:acronym) if record.ontology.bring?(:acronym) + raise ArgumentError, "Acronym is nil for ontology #{record.ontology.id} to generate id" if record.ontology.acronym.nil? + return RDF::URI.new( + "#{(Goo.id_prefix)}records/#{CGI.escape(record.ontology.acronym.to_s)}" + ) + end + + ## + ## find an artefact (ontology) and map it to record + def self.find(artefact_id) + ont = Ontology.find(artefact_id).include(:acronym, :viewingRestriction, :administeredBy, :acl).first + return nil unless ont + new.tap do |sacr| + sacr.ontology = ont + end + end + + def self.all(attributes, page, pagesize) + all_count = Ontology.where.count + onts = Ontology.where.include(:acronym, :viewingRestriction, :administeredBy, :acl).page(page, pagesize).page_count_set(all_count).all + all_records = onts.map do |o| + new.tap do |sacr| + sacr.ontology = o + sacr.bring(*attributes) if attributes + end + end + Goo::Base::Page.new(page, pagesize, all_count, all_records) + end + + private + def get_modification_date + fetch_submission_date(:max_by) + end + + def get_creation_date + fetch_submission_date(:min_by) + end + + def fetch_submission_date(method) + @ontology.bring(submissions: [:submissionId, :creationDate]) if @ontology.bring?(:submissions) + submission = @ontology.submissions.public_send(method, &:submissionId) + return unless submission + + submission.bring(:creationDate) unless submission.bring?(:creationDate) + submission.creationDate + end + + + end + end +end From 068c0b8c64160e5dcf807d470240b4fd9294c9c8 Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Tue, 22 Apr 2025 17:58:57 +0200 Subject: [PATCH 02/19] Feature: add hydra pagination to mod models (#204) * add hydra pagination * add ModBase class and links in serialization * fix collection id * fix generation context for tests * inherit catalogRecord model from ModBase * address request changes * fix tests --- .../models/mod/hydra_page.rb | 59 ++++++++++ .../models/mod/mod_base.rb | 6 + .../models/mod/semantic_artefact.rb | 24 ++-- .../models/mod/semantic_artefact_catalog.rb | 2 +- .../mod/semantic_artefact_catalog_record.rb | 4 +- .../mod/semantic_artefact_distribution.rb | 2 +- .../monkeypatches/object.rb | 2 + .../serializers/json.rb | 105 +++++++++++++----- test/models/mod/test_artefact.rb | 40 ++++--- 9 files changed, 184 insertions(+), 60 deletions(-) create mode 100644 lib/ontologies_linked_data/models/mod/hydra_page.rb create mode 100644 lib/ontologies_linked_data/models/mod/mod_base.rb diff --git a/lib/ontologies_linked_data/models/mod/hydra_page.rb b/lib/ontologies_linked_data/models/mod/hydra_page.rb new file mode 100644 index 00000000..877b4c9d --- /dev/null +++ b/lib/ontologies_linked_data/models/mod/hydra_page.rb @@ -0,0 +1,59 @@ +module LinkedData + module Models + class HydraPage < Goo::Base::Page + + def convert_hydra_page(options, &block) + { + '@id': get_request_path(options), + '@type': 'hydra:Collection', + totalItems: self.aggregate, + itemsPerPage: self.size, + view: generate_hydra_page_view(options, self.page_number, self.total_pages), + member: map { |item| item.to_flex_hash(options, &block) } + } + end + + def self.generate_hydra_context + { + 'hydra': 'http://www.w3.org/ns/hydra/core#', + 'Collection': 'hydra:Collection', + 'member': 'hydra:member', + 'totalItems': 'hydra:totalItems', + 'itemsPerPage': 'hydra:itemsPerPage', + 'view': 'hydra:view', + 'firstPage': 'hydra:first', + 'lastPage': 'hydra:last', + 'previousPage': 'hydra:previous', + 'nextPage': 'hydra:next', + } + end + + private + + def generate_hydra_page_view(options, page, page_count) + request_path = get_request_path(options) + params = options[:request] ? options[:request].params.dup : {} + + build_url = ->(page_number) { + query = Rack::Utils.build_nested_query(params.merge("page" => page_number.to_s)) + request_path ? "#{request_path}?#{query}" : "?#{query}" + } + + { + "@id": build_url.call(page), + "@type": "hydra:PartialCollectionView", + firstPage: build_url.call(1), + previousPage: page > 1 ? build_url.call(page - 1) : nil, + nextPage: page < page_count ? build_url.call(page + 1) : nil, + lastPage: page_count != 0 ? build_url.call(page_count) : build_url.call(1) + } + end + + def get_request_path(options) + request_path = options[:request] ? "#{LinkedData.settings.rest_url_prefix.chomp("/")}#{options[:request].path}" : nil + request_path + end + + end + end +end diff --git a/lib/ontologies_linked_data/models/mod/mod_base.rb b/lib/ontologies_linked_data/models/mod/mod_base.rb new file mode 100644 index 00000000..e1962058 --- /dev/null +++ b/lib/ontologies_linked_data/models/mod/mod_base.rb @@ -0,0 +1,6 @@ +module LinkedData + module Models + class ModBase < LinkedData::Models::Base + end + end +end diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact.rb index f75b5600..62ee88f7 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact.rb @@ -8,7 +8,7 @@ module LinkedData module Models - class SemanticArtefact < LinkedData::Models::Base + class SemanticArtefact < LinkedData::Models::ModBase include LinkedData::Concerns::SemanticArtefact::AttributeMapping include LinkedData::Concerns::SemanticArtefact::AttributeFetcher @@ -122,7 +122,6 @@ class SemanticArtefact < LinkedData::Models::Base link_to LinkedData::Hypermedia::Link.new("distributions", lambda {|s| "artefacts/#{s.acronym}/distributions"}, LinkedData::Models::SemanticArtefactDistribution.type_uri), LinkedData::Hypermedia::Link.new("record", lambda {|s| "artefacts/#{s.acronym}/record"}, LinkedData::Models::SemanticArtefactCatalogRecord.type_uri), LinkedData::Hypermedia::Link.new("resources", lambda {|s| "artefacts/#{s.acronym}/resources"}), - LinkedData::Hypermedia::Link.new("single_resource", lambda {|s| "artefacts/#{s.acronym}/resources/{:resourceID}"}), LinkedData::Hypermedia::Link.new("classes", lambda {|s| "artefacts/#{s.acronym}/classes"}, LinkedData::Models::Class.uri_type), LinkedData::Hypermedia::Link.new("concepts", lambda {|s| "artefacts/#{s.acronym}/concepts"}, LinkedData::Models::Class.uri_type), LinkedData::Hypermedia::Link.new("properties", lambda {|s| "artefacts/#{s.acronym}/properties"}, "#{Goo.namespaces[:metadata].to_s}Property"), @@ -168,7 +167,7 @@ def self.all_artefacts(attributes, page, pagesize) sa.bring(*attributes) if attributes end end - Goo::Base::Page.new(page, pagesize, all_count, all_artefacts) + LinkedData::Models::HydraPage.new(page, pagesize, all_count, all_artefacts) end def latest_distribution(status) @@ -181,15 +180,22 @@ def distribution(dist_id) SemanticArtefactDistribution.new(sub) unless sub.nil? end - def all_distributions(options = {}) - to_bring = options[:includes] - @ontology.bring(:submissions) - - @ontology.submissions.map do |submission| + def all_distributions(attributes, page, pagesize) + filter_by_acronym = Goo::Filter.new(ontology: [:acronym]) == @ontology.acronym + submissions_count = OntologySubmission.where.filter(filter_by_acronym).count + submissions_page = OntologySubmission.where.include(:distributionId) + .filter(filter_by_acronym) + .order_by(distributionId: :desc) + .page(page, pagesize) + .page_count_set(submissions_count) + .all + + all_distributions = submissions_page.map do |submission| SemanticArtefactDistribution.new(submission).tap do |dist| - dist.bring(*to_bring) if to_bring + dist.bring(*attributes) if attributes end end + LinkedData::Models::HydraPage.new(page, pagesize, submissions_count, all_distributions) end def analytics diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb index 47837e2b..6d91ae2b 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb @@ -16,7 +16,7 @@ module LinkedData module Models - class SemanticArtefactCatalog < LinkedData::Models::Base + class SemanticArtefactCatalog < LinkedData::Models::ModBase model :SemanticArtefactCatalog, namespace: :mod, scheme: File.join(__dir__, '../../../../config/schemes/semantic_artefact_catalog.yml'), diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog_record.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog_record.rb index 114059b4..e4128f64 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog_record.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog_record.rb @@ -1,6 +1,6 @@ module LinkedData module Models - class SemanticArtefactCatalogRecord < LinkedData::Models::Base + class SemanticArtefactCatalogRecord < LinkedData::Models::ModBase include LinkedData::Concerns::SemanticArtefact::AttributeMapping include LinkedData::Concerns::SemanticArtefact::AttributeFetcher @@ -70,7 +70,7 @@ def self.all(attributes, page, pagesize) sacr.bring(*attributes) if attributes end end - Goo::Base::Page.new(page, pagesize, all_count, all_records) + LinkedData::Models::HydraPage.new(page, pagesize, all_count, all_records) end private diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb index 21ad3299..cd7e39b3 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb @@ -1,7 +1,7 @@ module LinkedData module Models - class SemanticArtefactDistribution < LinkedData::Models::Base + class SemanticArtefactDistribution < LinkedData::Models::ModBase include LinkedData::Concerns::SemanticArtefact::AttributeMapping include LinkedData::Concerns::SemanticArtefact::AttributeFetcher diff --git a/lib/ontologies_linked_data/monkeypatches/object.rb b/lib/ontologies_linked_data/monkeypatches/object.rb index 45599d1b..38fa5cff 100644 --- a/lib/ontologies_linked_data/monkeypatches/object.rb +++ b/lib/ontologies_linked_data/monkeypatches/object.rb @@ -190,6 +190,8 @@ def enumerable_handling(options, &block) new_hash[key] = value.to_flex_hash(options, &block) end return new_hash + elsif kind_of?(LinkedData::Models::HydraPage) + return self.convert_hydra_page(options, &block) elsif kind_of?(Goo::Base::Page) return convert_goo_page(options, &block) end diff --git a/lib/ontologies_linked_data/serializers/json.rb b/lib/ontologies_linked_data/serializers/json.rb index e4161b49..1aa67f72 100644 --- a/lib/ontologies_linked_data/serializers/json.rb +++ b/lib/ontologies_linked_data/serializers/json.rb @@ -7,48 +7,95 @@ class JSON def self.serialize(obj, options = {}) + return serialize_mod_objects(obj, options) if mod_object?(obj) + # Handle the serialization for all other objects in the standard way hash = obj.to_flex_hash(options) do |hash, hashed_obj| - current_cls = hashed_obj.respond_to?(:klass) ? hashed_obj.klass : hashed_obj.class - result_lang = self.get_languages(get_object_submission(hashed_obj), options[:lang]) if result_lang.nil? + process_common_serialization(hash, hashed_obj, options) + end + MultiJson.dump(hash) + end + + def self.serialize_mod_objects(obj, options = {}) + # using one context and links in mod objects + global_links, global_context = {}, {} - # Add the id to json-ld attribute - if current_cls.ancestors.include?(LinkedData::Hypermedia::Resource) && !current_cls.embedded? && hashed_obj.respond_to?(:id) - prefixed_id = LinkedData::Models::Base.replace_url_id_to_prefix(hashed_obj.id) - hash["@id"] = prefixed_id.to_s - end + hash = obj.to_flex_hash(options) do |hash, hashed_obj| + process_common_serialization(hash, hashed_obj, options, global_links, global_context) + end - # Add the type - hash["@type"] = type(current_cls, hashed_obj) if hash["@id"] - - # Generate links - # NOTE: If this logic changes, also change in xml.rb - if generate_links?(options) - links = LinkedData::Hypermedia.generate_links(hashed_obj) - unless links.empty? - hash["links"] = links - hash["links"].merge!(generate_links_context(hashed_obj)) if generate_context?(options) - end - end + result = {} + # handle adding the context for HydraPage + if obj.is_a?(LinkedData::Models::HydraPage) + global_context["@context"] ||= {} + global_context["@context"].merge!(LinkedData::Models::HydraPage.generate_hydra_context) + end + result.merge!(global_context) unless global_context.empty? + result.merge!(hash) if hash.is_a?(Hash) + result.merge!(global_links) unless global_links.empty? + MultiJson.dump(result) + end + + + private + + def self.process_common_serialization(hash, hashed_obj, options, global_links= nil, global_context= nil) + current_cls = hashed_obj.respond_to?(:klass) ? hashed_obj.klass : hashed_obj.class + result_lang = get_languages(get_object_submission(hashed_obj), options[:lang]) + + add_id_and_type(hash, hashed_obj, current_cls) + add_links(hash, hashed_obj, options, global_links) if generate_links?(options) + add_context(hash, hashed_obj, options, current_cls, result_lang, global_context) if generate_context?(options) + end - # Generate context + def self.add_id_and_type(hash, hashed_obj, current_cls) + return unless current_cls.ancestors.include?(LinkedData::Hypermedia::Resource) && !current_cls.embedded? && hashed_obj.respond_to?(:id) + + hash["@id"] = LinkedData::Models::Base.replace_url_id_to_prefix(hashed_obj.id).to_s + hash["@type"] = type(current_cls, hashed_obj) if hash["@id"] + end + + def self.add_links(hash, hashed_obj, options, global_links) + return if global_links&.any? + + links = LinkedData::Hypermedia.generate_links(hashed_obj) + return if links.empty? + + if global_links.nil? + hash["links"] = links + hash["links"].merge!(generate_links_context(hashed_obj)) if generate_context?(options) + elsif global_links.empty? + global_links["links"] = links + global_links["links"].merge!(generate_links_context(hashed_obj)) if generate_context?(options) + end + end + + def self.add_context(hash, hashed_obj, options, current_cls, result_lang, global_context) + return if global_context&.any? + + if global_context.nil? if current_cls.ancestors.include?(Goo::Base::Resource) && !current_cls.embedded? - if generate_context?(options) - context = generate_context(hashed_obj, hash.keys, options) - hash.merge!(context) - end + context = generate_context(hashed_obj, hash.keys, options) + hash.merge!(context) elsif (hashed_obj.instance_of?(LinkedData::Models::ExternalClass) || hashed_obj.instance_of?(LinkedData::Models::InterportalClass)) && !current_cls.embedded? # Add context for ExternalClass - context_hash = { "@vocab" => Goo.vocabulary.to_s, "prefLabel" => "http://data.bioontology.org/metadata/skosprefLabel" } - context = { "@context" => context_hash } - hash.merge!(context) + external_class_context = { "@context" => { "@vocab" => Goo.vocabulary.to_s, "prefLabel" => "http://data.bioontology.org/metadata/skosprefLabel" } } + hash.merge!(external_class_context) end hash['@context']['@language'] = result_lang if hash['@context'] + elsif global_context.empty? + context = generate_context(hashed_obj, hash.keys, options) + global_context.replace(context) + global_context["@context"]["@language"] = result_lang unless global_context.empty? end - MultiJson.dump(hash) end - private + def self.mod_object?(obj) + return false if obj.nil? + single_object = (obj.class == Array) && obj.any? ? obj.first : obj + single_object.class.ancestors.include?(LinkedData::Models::HydraPage) || single_object.class.ancestors.include?(LinkedData::Models::ModBase) + end + def self.get_object_submission(obj) obj.class.respond_to?(:attributes) && obj.class.attributes.include?(:submission) ? obj.submission : nil diff --git a/test/models/mod/test_artefact.rb b/test/models/mod/test_artefact.rb index ecabe518..20885abd 100644 --- a/test/models/mod/test_artefact.rb +++ b/test/models/mod/test_artefact.rb @@ -3,13 +3,18 @@ class TestArtefact < LinkedData::TestOntologyCommon + def self.before_suite + backend_4s_delete + helper = LinkedData::TestOntologyCommon.new(self) + helper.init_test_ontology_msotest "STY" + end + def test_create_artefact sa = LinkedData::Models::SemanticArtefact.new assert_equal LinkedData::Models::SemanticArtefact , sa.class end def test_find_artefact - create_test_ontology sa = LinkedData::Models::SemanticArtefact.find('STY') assert_equal LinkedData::Models::SemanticArtefact , sa.class assert_equal "STY", sa.acronym @@ -25,7 +30,6 @@ def test_goo_attrs_to_load end def test_bring_attrs - create_test_ontology r = LinkedData::Models::SemanticArtefact.find('STY') r.bring(*LinkedData::Models::SemanticArtefact.goo_attrs_to_load([:all])) ont = r.ontology @@ -77,7 +81,6 @@ def test_bring_attrs def test_latest_distribution - create_test_ontology sa = LinkedData::Models::SemanticArtefact.find('STY') assert_equal "STY", sa.acronym latest_distribution = sa.latest_distribution(status: :any) @@ -87,25 +90,26 @@ def test_latest_distribution assert_equal 1, latest_distribution.submission.submissionId end + def test_all_artefacts + attributes = LinkedData::Models::SemanticArtefact.goo_attrs_to_load([]) + page = 1 + pagesize = 1 + all_artefacts = LinkedData::Models::SemanticArtefact.all_artefacts(attributes, page, pagesize) + assert_equal LinkedData::Models::HydraPage, all_artefacts.class + assert_equal 1, all_artefacts.length + assert_equal LinkedData::Models::SemanticArtefact, all_artefacts[0].class + end + def test_distributions - create_test_ontology r = LinkedData::Models::SemanticArtefact.find('STY') - options = { - status: "ANY", - includes: LinkedData::Models::SemanticArtefactDistribution.goo_attrs_to_load([]) - } - all_distros = r.all_distributions(options) - - assert_equal Array, all_distros.class - assert_equal 1, all_distros.length + attributes = LinkedData::Models::SemanticArtefactDistribution.goo_attrs_to_load([]) + page = 1 + pagesize = 1 + all_distros = r.all_distributions(attributes, page, pagesize) + assert_equal LinkedData::Models::HydraPage, all_distros.class assert_equal 1, all_distros.length assert_equal LinkedData::Models::SemanticArtefactDistribution, all_distros[0].class assert_equal Set[:distributionId, :title, :hasRepresentationLanguage, :hasSyntax, :description, :created, :modified, :conformsToKnowledgeRepresentationParadigm, :usedEngineeringMethodology, :prefLabelProperty, :synonymProperty, :definitionProperty, :accessURL, :downloadURL], all_distros[0].loaded_attributes end - - private - def create_test_ontology - acr = "STY" - init_test_ontology_msotest acr - end + end \ No newline at end of file From f67735fba1744b3aba25144e64634c30c2545f39 Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Thu, 24 Apr 2025 20:00:50 +0200 Subject: [PATCH 03/19] Fix: add catalog attributes and fix distribution attributes (#207) * add catalog attribute and fix title distribution * update tests * add bytesize to distribution * fix tests * remove configuration from config file and remove PortalConfig model * merge PortalConfiguration test to Catalog tests * dumb api key for federated portals example --- config/config.rb.sample | 32 ---------- config/schemes/semantic_artefact_catalog.yml | 17 +++++- .../models/mod/semantic_artefact_catalog.rb | 5 +- .../mod/semantic_artefact_distribution.rb | 15 ++++- .../models/portal_config.rb | 57 ----------------- test/models/mod/test_artefact.rb | 1 - test/models/mod/test_artefact_catalog.rb | 61 ++++++++++++++++--- test/models/mod/test_artefact_distribution.rb | 2 +- test/models/test_portal_configuration.rb | 27 -------- 9 files changed, 82 insertions(+), 135 deletions(-) delete mode 100644 lib/ontologies_linked_data/models/portal_config.rb delete mode 100644 test/models/test_portal_configuration.rb diff --git a/config/config.rb.sample b/config/config.rb.sample index d3e9f9e0..7539c8d8 100644 --- a/config/config.rb.sample +++ b/config/config.rb.sample @@ -92,38 +92,6 @@ begin link: 'https://www.googleapis.com/oauth2/v3/userinfo' } } - - config.ui_name = 'Bioportal' - config.title = 'NCBO BioPortal' - config.description = "The world's most comprehensive repository of biomedical ontologies" - config.color = '#234979' - config.logo = '' - config.fundedBy = [ - { - img_src: 'https://identity.stanford.edu/wp-content/uploads/sites/3/2020/07/block-s-right.png', - url: 'https://www.stanford.edu', - - }, - { - img_src: 'https://ontoportal.org/images/logo.png', - url: 'https://ontoportal.org/', - } - ] - config.federated_portals = { - 'agroportal' => { - api: 'http://data.agroportal.lirmm.fr', - ui: 'http://agroportal.lirmm.fr', - apikey: '1cfae05f-9e67-486f-820b-b393dec5764b', - color: '#1e2251' - }, - 'bioportal' => { - api: 'http://data.bioontology.org', - ui: 'http://bioportal.bioontology.org', - apikey: '4a5011ea-75fa-4be6-8e89-f45c8c84844e', - color: '#234979' - }, - - } end rescue NameError => e binding.pry diff --git a/config/schemes/semantic_artefact_catalog.yml b/config/schemes/semantic_artefact_catalog.yml index 06ced74e..6451b110 100644 --- a/config/schemes/semantic_artefact_catalog.yml +++ b/config/schemes/semantic_artefact_catalog.yml @@ -63,7 +63,12 @@ fundedBy: helpText: "Founder of the catalog" example: '' enforcedValues: [img_src: "", url: "" ] - default: [] + default: [ + { + img_src: 'https://ontoportal.org/images/logo.png', + url: 'https://ontoportal.org/' + } + ] federated_portals: display: "general" @@ -71,7 +76,15 @@ federated_portals: helpText: "The Federated portal" example: '' enforcedValues: [name: "", api: "", ui: "", apikey: "", color: "" ] - default: [] + default: [ + { + name: 'agroportal', + api: 'http://data.agroportal.lirmm.fr', + ui: 'http://agroportal.lirmm.fr', + apikey: "DUMMY_API_KEY_123456", + color: '#3cb371' + } + ] homepage: display: "general" diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb index 6d91ae2b..73fcca74 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb @@ -168,9 +168,10 @@ class SemanticArtefactCatalog < LinkedData::Models::ModBase LinkedData::Hypermedia::Link.new("replies", lambda {|s| "replies"}, LinkedData::Models::Notes::Reply.type_uri), LinkedData::Hypermedia::Link.new("reviews", lambda {|s| "reviews"}, LinkedData::Models::Review.type_uri) - serialize_default :acronym, :title, :identifier, :status, :language, :type, :accessRights, :license, :rightsHolder, :description, + serialize_default :acronym, :title, :color, :description, :logo,:identifier, :status, :language, :type, :accessRights, :license, :rightsHolder, :landingPage, :keyword, :bibliographicCitation, :created, :modified , :contactPoint, :creator, :contributor, - :publisher, :subject, :coverage, :createdWith, :accrualMethod, :accrualPeriodicity, :wasGeneratedBy, :accessURL + :publisher, :subject, :coverage, :createdWith, :accrualMethod, :accrualPeriodicity, :wasGeneratedBy, :accessURL, + :numberOfArtefacts, :federated_portals, :fundedBy def self.type_uri namespace[model_name].to_s diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb index cd7e39b3..e1eb5c28 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb @@ -9,7 +9,7 @@ class SemanticArtefactDistribution < LinkedData::Models::ModBase # SAD attrs that map with submission attribute_mapped :distributionId, mapped_to: { model: :ontology_submission, attribute: :submissionId } - attribute_mapped :title, namespace: :dcterms, mapped_to: { model: :ontology_submission, attribute: :URI } + attribute_mapped :title, namespace: :dcterms, mapped_to: { model: :ontology, attribute: :name } attribute_mapped :deprecated, namespace: :owl, mapped_to: { model: :ontology_submission } attribute_mapped :hasRepresentationLanguage, namespace: :mod, mapped_to: { model: :ontology_submission, attribute: :hasOntologyLanguage } attribute_mapped :hasFormalityLevel, namespace: :mod, mapped_to: { model: :ontology_submission } @@ -60,8 +60,10 @@ class SemanticArtefactDistribution < LinkedData::Models::ModBase attribute_mapped :classesWithNoAuthorMetadata, namespace: :mod, enforce: [:integer], mapped_to: { model: :metric } attribute_mapped :classesWithNoDateMetadata, namespace: :mod, enforce: [:integer], mapped_to: { model: :metric } attribute_mapped :numberOfMappings, namespace: :mod, enforce: [:integer], mapped_to: { model: :metric } + attribute_mapped :byteSize, namespace: :dcat, mapped_to: { model: self }, handler: :calculate_byte_size # Attr special to SemanticArtefactDistribution + attribute :ontology, type: :ontology attribute :submission, type: :ontology_submission # Access control @@ -69,7 +71,7 @@ class SemanticArtefactDistribution < LinkedData::Models::ModBase serialize_default :distributionId, :title, :hasRepresentationLanguage, :hasSyntax, :description, :created, :modified, :conformsToKnowledgeRepresentationParadigm, :usedEngineeringMethodology, :prefLabelProperty, - :synonymProperty, :definitionProperty, :accessURL, :downloadURL + :synonymProperty, :definitionProperty, :accessURL, :downloadURL, :byteSize serialize_never :submission @@ -78,7 +80,7 @@ def self.distribution_id_generator(ss) ss.submission.ontology.bring(:acronym) if !ss.submission.ontology.loaded_attributes.include?(:acronym) raise ArgumentError, "Acronym is nil to generate id" if ss.submission.ontology.acronym.nil? return RDF::URI.new( - "#{(Goo.id_prefix)}artefacts/#{CGI.escape(ss.submission.ontology.acronym.to_s)}/distributions/#{ss.submission.submissionId.to_s}" + "#{(Goo.id_prefix)}artefacts/#{CGI.escape(ss.ontology.acronym.to_s)}/distributions/#{ss.submission.submissionId.to_s}" ) end @@ -88,6 +90,13 @@ def initialize(sub) @submission = sub @submission.bring(*[:submissionId, :ontology=>[:acronym, :administeredBy, :acl, :viewingRestriction]]) @distributionId = sub.submissionId + @ontology = @submission.ontology + end + + def calculate_byte_size + @submission.bring(:uploadFilePath) if @submission.bring?(:uploadFilePath) + updload_file_path = @submission.uploadFilePath + File.exist?(updload_file_path) ? File.size(updload_file_path) : 0 end diff --git a/lib/ontologies_linked_data/models/portal_config.rb b/lib/ontologies_linked_data/models/portal_config.rb deleted file mode 100644 index 18169fca..00000000 --- a/lib/ontologies_linked_data/models/portal_config.rb +++ /dev/null @@ -1,57 +0,0 @@ -module LinkedData - module Models - class PortalConfig < LinkedData::Models::Base - model :SemanticArtefactCatalogue, namespace: :mod, name_with: :acronym - attribute :acronym, enforce: [:unique, :existence] - attribute :title, namespace: :dcterms, enforce: [:existence] - attribute :color, enforce: [:existence, :valid_hash_code] - attribute :description, namespace: :dcterms - attribute :logo, namespace: :foaf, enforce: [:url] - attribute :numberOfArtefacts, namespace: :mod, handler: :ontologies_count - attribute :federated_portals, handler: :federated_portals_settings - attribute :fundedBy, namespace: :foaf, enforce: [:list] - - serialize_default :acronym, :title, :color, :description, :logo, :numberOfArtefacts, :federated_portals, :fundedBy - - def initialize(*args) - super - init_federated_portals_settings - end - - def self.current_portal_config - p = LinkedData::Models::PortalConfig.new - - p.acronym = LinkedData.settings.ui_name.downcase - p.title = LinkedData.settings.title - p.description = LinkedData.settings.description - p.color = LinkedData.settings.color - p.logo = LinkedData.settings.logo - p.fundedBy = LinkedData.settings.fundedBy - p - end - - def init_federated_portals_settings(federated_portals = nil) - @federated_portals = federated_portals || LinkedData.settings.federated_portals.symbolize_keys - end - - def federated_portals_settings - @federated_portals - end - - def ontologies_count - LinkedData::Models::Ontology.where(viewingRestriction: 'public').count - end - - def self.valid_hash_code(inst, attr) - inst.bring(attr) if inst.bring?(attr) - str = inst.send(attr) - - return if (/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/ === str) - [:valid_hash_code, - "Invalid hex color code: '#{str}'. Please provide a valid hex code in the format '#FFF' or '#FFFFFF'."] - end - end - end -end - - diff --git a/test/models/mod/test_artefact.rb b/test/models/mod/test_artefact.rb index 20885abd..620261b5 100644 --- a/test/models/mod/test_artefact.rb +++ b/test/models/mod/test_artefact.rb @@ -109,7 +109,6 @@ def test_distributions assert_equal LinkedData::Models::HydraPage, all_distros.class assert_equal 1, all_distros.length assert_equal LinkedData::Models::SemanticArtefactDistribution, all_distros[0].class - assert_equal Set[:distributionId, :title, :hasRepresentationLanguage, :hasSyntax, :description, :created, :modified, :conformsToKnowledgeRepresentationParadigm, :usedEngineeringMethodology, :prefLabelProperty, :synonymProperty, :definitionProperty, :accessURL, :downloadURL], all_distros[0].loaded_attributes end end \ No newline at end of file diff --git a/test/models/mod/test_artefact_catalog.rb b/test/models/mod/test_artefact_catalog.rb index a6c46c05..27a2beed 100644 --- a/test/models/mod/test_artefact_catalog.rb +++ b/test/models/mod/test_artefact_catalog.rb @@ -3,17 +3,60 @@ class TestArtefactCatalog < LinkedData::TestOntologyCommon + def self.before_suite + backend_4s_delete + self.new("before_suite").teardown + catalog = LinkedData::Models::SemanticArtefactCatalog.new + catalog.save + end + + def self.after_suite + self.new("before_suite").teardown + end + def test_create_artefact_catalog - sac = LinkedData::Models::SemanticArtefactCatalog.new - assert_equal LinkedData::Models::SemanticArtefactCatalog , sac.class - assert_equal "http://data.bioontology.org/", sac.id.to_s + catalog = LinkedData::Models::SemanticArtefactCatalog.all.first + assert_equal LinkedData::Models::SemanticArtefactCatalog , catalog.class + catalog.bring(*LinkedData::Models::SemanticArtefactCatalog.goo_attrs_to_load([])) + expected = { + acronym: "OntoPortal", + title: "OntoPortal", + color: "#5499A3", + description: "Welcome to OntoPortal Appliance, your ontology repository for your ontologies", + status: "alpha", + accessRights: "public", + logo: "https://ontoportal.org/images/logo.png", + license: "https://opensource.org/licenses/BSD-2-Clause", + federated_portals: [ + "{:name=>\"agroportal\", :api=>\"http://data.agroportal.lirmm.fr\", :ui=>\"http://agroportal.lirmm.fr\", :apikey=>\"DUMMY_API_KEY_123456\", :color=>\"#3cb371\"}" + ], + fundedBy: [ + "{:img_src=>\"https://ontoportal.org/images/logo.png\", :url=>\"https://ontoportal.org/\"}" + ], + language: ["English"], + keyword: [], + bibliographicCitation: [], + subject: [], + coverage: [], + createdWith: [], + accrualMethod: [], + accrualPeriodicity: [], + wasGeneratedBy: [], + contactPoint: [], + creator: [], + contributor: [], + publisher: [], + id: RDF::URI("http://data.bioontology.org/") + } + assert_equal expected, catalog.to_hash + refute_nil catalog.numberOfArtefacts end def test_goo_attrs_to_load default_attrs = LinkedData::Models::SemanticArtefactCatalog.goo_attrs_to_load([]) - assert_equal [:acronym, :title, :identifier, :status, :language, :type, :accessRights, :license, :rightsHolder, - :description, :landingPage, :keyword, :bibliographicCitation, :created, :modified, :contactPoint, :creator, - :contributor, :publisher, :subject, :coverage, :createdWith, :accrualMethod, :accrualPeriodicity, :wasGeneratedBy, :accessURL], default_attrs + assert_equal [:acronym, :title, :color, :description, :logo, :identifier, :status, :language, :type, :accessRights, :license, :rightsHolder, + :landingPage, :keyword, :bibliographicCitation, :created, :modified, :contactPoint, :creator, :contributor, :publisher, :subject, + :coverage, :createdWith, :accrualMethod, :accrualPeriodicity, :wasGeneratedBy, :accessURL, :numberOfArtefacts, :federated_portals, :fundedBy], default_attrs specified_attrs = LinkedData::Models::SemanticArtefactCatalog.goo_attrs_to_load([:acronym, :title, :keyword, :featureList]) assert_equal [:acronym, :title, :keyword, :featureList], specified_attrs @@ -25,10 +68,8 @@ def test_goo_attrs_to_load assert_equal computed_attrs, computed_attrs_bring end - def test_bring_attrs - sac = LinkedData::Models::SemanticArtefactCatalog.new - assert_equal true, sac.valid? - sac.save + def test_bring_all_attrs + sac = LinkedData::Models::SemanticArtefactCatalog.all.first all_attrs_to_bring = LinkedData::Models::SemanticArtefactCatalog.goo_attrs_to_load([:all]) sac.bring(*all_attrs_to_bring) assert_equal all_attrs_to_bring.sort, (sac.loaded_attributes.to_a + [:type]).sort diff --git a/test/models/mod/test_artefact_distribution.rb b/test/models/mod/test_artefact_distribution.rb index e99b26d3..48ad9f9d 100644 --- a/test/models/mod/test_artefact_distribution.rb +++ b/test/models/mod/test_artefact_distribution.rb @@ -16,7 +16,7 @@ def test_goo_attrs_to_load attrs = LinkedData::Models::SemanticArtefactDistribution.goo_attrs_to_load([]) assert_equal [:distributionId, :title, :hasRepresentationLanguage, :hasSyntax, :description, :created, :modified, :conformsToKnowledgeRepresentationParadigm, :usedEngineeringMethodology, :prefLabelProperty, - :synonymProperty, :definitionProperty, :accessURL, :downloadURL], attrs + :synonymProperty, :definitionProperty, :accessURL, :downloadURL, :byteSize], attrs end def test_bring_attrs diff --git a/test/models/test_portal_configuration.rb b/test/models/test_portal_configuration.rb deleted file mode 100644 index dcebbc93..00000000 --- a/test/models/test_portal_configuration.rb +++ /dev/null @@ -1,27 +0,0 @@ -require_relative '../test_case' - -class TestPortalConfiguration < LinkedData::TestCase - - def test_read_portal_config - config = LinkedData::Models::PortalConfig.current_portal_config - - expected = { acronym: 'bioportal', - title: 'NCBO BioPortal', - color: '#234979', - description: "The world's most comprehensive repository of biomedical ontologies", - logo: '', - fundedBy: [{ img_src: 'https://identity.stanford.edu/wp-content/uploads/sites/3/2020/07/block-s-right.png', url: 'https://www.stanford.edu' }, - { img_src: 'https://ontoportal.org/images/logo.png', url: 'https://ontoportal.org/' }], - id: RDF::URI.new('http://data.bioontology.org/SemanticArtefactCatalogues/bioportal') } - - assert config.valid? - - assert_equal expected, config.to_hash - - expected_federated_portals = { 'agroportal' => { api: 'http://data.agroportal.lirmm.fr', ui: 'http://agroportal.lirmm.fr', apikey: '1cfae05f-9e67-486f-820b-b393dec5764b', color: '#1e2251' }, - 'bioportal' => { api: 'http://data.bioontology.org', ui: 'http://bioportal.bioontology.org', apikey: '4a5011ea-75fa-4be6-8e89-f45c8c84844e', color: '#234979' } }.symbolize_keys - assert_equal expected_federated_portals, config.federated_portals - refute_nil config.numberOfArtefacts - end -end - From 913ff00af28225c00133ba41cc2d51eb08cd90ff Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Fri, 25 Apr 2025 18:12:46 +0200 Subject: [PATCH 04/19] handle non existing uploadFilePath in distribution bytesize (#208) --- .../models/mod/semantic_artefact_distribution.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb index e1eb5c28..019aad3b 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_distribution.rb @@ -95,8 +95,8 @@ def initialize(sub) def calculate_byte_size @submission.bring(:uploadFilePath) if @submission.bring?(:uploadFilePath) - updload_file_path = @submission.uploadFilePath - File.exist?(updload_file_path) ? File.size(updload_file_path) : 0 + upload_file_path = @submission.uploadFilePath + File.exist?(upload_file_path.to_s) ? File.size(upload_file_path.to_s) : 0 end From a4633fbfa07190d754f727ba85ac410385558b9a Mon Sep 17 00:00:00 2001 From: MUH <58882014+muhammedBkf@users.noreply.github.com> Date: Wed, 7 May 2025 11:01:47 +0200 Subject: [PATCH 05/19] index embedded agents as json (#209) --- lib/ontologies_linked_data/models/agents/agent.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/ontologies_linked_data/models/agents/agent.rb b/lib/ontologies_linked_data/models/agents/agent.rb index 24601748..323c4aa5 100644 --- a/lib/ontologies_linked_data/models/agents/agent.rb +++ b/lib/ontologies_linked_data/models/agents/agent.rb @@ -26,7 +26,13 @@ class Agent < LinkedData::Models::Base enable_indexing(:agents_metadata) def embedded_doc - "#{self.name} #{self.acronym} #{self.email} #{self.agentType}" + { + "id": "#{self.id}", + "name": "#{self.name}", + "acronym": "#{self.acronym}", + "email": "#{self.email}", + "agentType": "#{self.agentType}" + }.to_json end def self.load_agents_usages(agents = [], agent_attributes = OntologySubmission.agents_attr_uris) From 905eb1cb30a5232969f20b83f258e585f57dd207 Mon Sep 17 00:00:00 2001 From: MUH <58882014+muhammedBkf@users.noreply.github.com> Date: Fri, 16 May 2025 17:14:46 +0200 Subject: [PATCH 06/19] exclude serialized methods from nested (embedded) resources (#211) --- lib/ontologies_linked_data/monkeypatches/object.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ontologies_linked_data/monkeypatches/object.rb b/lib/ontologies_linked_data/monkeypatches/object.rb index 38fa5cff..12acee2c 100644 --- a/lib/ontologies_linked_data/monkeypatches/object.rb +++ b/lib/ontologies_linked_data/monkeypatches/object.rb @@ -33,12 +33,13 @@ def to_flex_hash(options = {}, &block) only = Set.new(options[:include_for_class] || []) end - if all # Get everything + if all && !options[:nested] # Get everything if not an embedded object methods = self.class.hypermedia_settings[:serialize_methods] if self.is_a?(LinkedData::Hypermedia::Resource) end - # Check to see if we're nested, if so remove necessary properties + # Check to see if we're nested, if so remove necessary properties and serialize methods only = only - do_not_serialize_nested(options) + only -= self.class.hypermedia_settings[:serialize_methods] if options[:nested] # Determine whether to use defaults from the DSL or all attributes hash = populate_attributes(hash, all, only, options) From 821b389d8699cedbe9d45a7683825fa9b6e0f2ff Mon Sep 17 00:00:00 2001 From: MUH <58882014+muhammedBkf@users.noreply.github.com> Date: Fri, 16 May 2025 17:15:22 +0200 Subject: [PATCH 07/19] limit agents embedded affiliation attributes (#212) --- lib/ontologies_linked_data/models/agents/agent.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ontologies_linked_data/models/agents/agent.rb b/lib/ontologies_linked_data/models/agents/agent.rb index 323c4aa5..7a607c9b 100644 --- a/lib/ontologies_linked_data/models/agents/agent.rb +++ b/lib/ontologies_linked_data/models/agents/agent.rb @@ -17,7 +17,7 @@ class Agent < LinkedData::Models::Base attribute :affiliations, enforce: %i[Agent list is_organization], namespace: :org, property: :memberOf attribute :creator, type: :user, enforce: [:existence] embed :identifiers, :affiliations - embed_values affiliations: LinkedData::Models::Agent.goo_attrs_to_load + [identifiers: LinkedData::Models::AgentIdentifier.goo_attrs_to_load] + embed_values affiliations: [:name, :agentType, :homepage, :acronym, :email, :identifiers] serialize_methods :usages write_access :creator From a7fba21bad56859712d7cdcdd7b14adad194e6c8 Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Tue, 20 May 2025 16:18:51 +0200 Subject: [PATCH 08/19] Fix: change links from global to local && change catalog links to use /mod-api namespace (#213) --- .../models/mod/semantic_artefact_catalog.rb | 36 ++++++++++--------- .../serializers/json.rb | 23 ++++-------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb index 73fcca74..c963784d 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb @@ -41,18 +41,18 @@ class SemanticArtefactCatalog < LinkedData::Models::ModBase attribute :relation, namespace: :dcterms, enforce: [:string] attribute :hasPolicy, namespace: :mod, enforce: [:string] attribute :themeTaxonomy, namespace: :mod, enforce: [:string] - + attribute :created, namespace: :dcterms, enforce: [:date] attribute :curatedOn, namespace: :pav, enforce: [:date] - + attribute :deprecated, namespace: :owl, enforce: [:boolean] - + attribute :homepage, namespace: :foaf, enforce: [:url], default: ->(s) { RDF::URI("http://#{LinkedData.settings.ui_host}") } attribute :logo, namespace: :foaf, enforce: [:url] attribute :license, namespace: :dcterms, enforce: [:url] attribute :mailingList, namespace: :mod, enforce: [:url] attribute :fairScore, namespace: :mod, enforce: [:url] - + attribute :federated_portals, enforce: [:list] attribute :fundedBy, namespace: :foaf, enforce: [:list] attribute :language, namespace: :dcterms, enforce: [:list] @@ -132,7 +132,7 @@ class SemanticArtefactCatalog < LinkedData::Models::ModBase numberOfMappings: :mappings_counts, numberOfAgents: :agents_counts } - + METRICS_ATTRIBUTES.each do |attr_name, config| handler = config.is_a?(Hash) ? config[:handler] : config mapped_to = config.is_a?(Hash) ? config[:mapped_to] : attr_name @@ -140,15 +140,12 @@ class SemanticArtefactCatalog < LinkedData::Models::ModBase define_method(handler) { calculate_attr_from_metrics(mapped_to) } end - link_to LinkedData::Hypermedia::Link.new("doc/legacy-api", lambda {|s| "documentation"}, nil), - LinkedData::Hypermedia::Link.new("doc/mod-api", lambda {|s| "doc/api"}, nil), + link_to LinkedData::Hypermedia::Link.new("documentation", lambda {|s| "documentation"}, nil), LinkedData::Hypermedia::Link.new("ontologies", lambda {|s| "ontologies"}, LinkedData::Models::Ontology.type_uri), LinkedData::Hypermedia::Link.new("ontologies_full", lambda {|s| "ontologies_full"}, LinkedData::Models::Ontology.type_uri), LinkedData::Hypermedia::Link.new("ontology_metadata", lambda {|s| "ontology_metadata"}, nil), LinkedData::Hypermedia::Link.new("submissions", lambda {|s| "submissions"}, LinkedData::Models::OntologySubmission.type_uri), LinkedData::Hypermedia::Link.new("submission_metadata", lambda {|s| "submission_metadata"}, nil), - LinkedData::Hypermedia::Link.new("artefacts", lambda {|s| "artefacts"}, LinkedData::Models::SemanticArtefact.type_uri), - LinkedData::Hypermedia::Link.new("records", lambda {|s| "records"}, LinkedData::Models::SemanticArtefactCatalogRecord.type_uri), LinkedData::Hypermedia::Link.new("users", lambda {|s| "users"}, LinkedData::Models::User.type_uri), LinkedData::Hypermedia::Link.new("agents", lambda {|s| "agents"}, LinkedData::Models::Agent.type_uri), LinkedData::Hypermedia::Link.new("groups", lambda {|s| "groups"}, LinkedData::Models::Group.type_uri), @@ -166,17 +163,22 @@ class SemanticArtefactCatalog < LinkedData::Models::ModBase LinkedData::Hypermedia::Link.new("annotator", lambda {|s| "annotator"}, nil), LinkedData::Hypermedia::Link.new("notes", lambda {|s| "notes"}, LinkedData::Models::Note.type_uri), LinkedData::Hypermedia::Link.new("replies", lambda {|s| "replies"}, LinkedData::Models::Notes::Reply.type_uri), - LinkedData::Hypermedia::Link.new("reviews", lambda {|s| "reviews"}, LinkedData::Models::Review.type_uri) - + LinkedData::Hypermedia::Link.new("reviews", lambda {|s| "reviews"}, LinkedData::Models::Review.type_uri), + LinkedData::Hypermedia::Link.new("mod-api_documentation", lambda {|s| "mod-api/doc"}, nil), + LinkedData::Hypermedia::Link.new("artefacts", lambda {|s| "mod-api/artefacts"}, LinkedData::Models::SemanticArtefact.type_uri), + LinkedData::Hypermedia::Link.new("records", lambda {|s| "mod-api/records"}, LinkedData::Models::SemanticArtefactCatalogRecord.type_uri), + LinkedData::Hypermedia::Link.new("search_content", lambda {|s| "mod-api/search/content"}, nil), + LinkedData::Hypermedia::Link.new("search_metadata", lambda {|s| "mod-api/search/metadata"}, nil) + serialize_default :acronym, :title, :color, :description, :logo,:identifier, :status, :language, :type, :accessRights, :license, :rightsHolder, - :landingPage, :keyword, :bibliographicCitation, :created, :modified , :contactPoint, :creator, :contributor, + :landingPage, :keyword, :bibliographicCitation, :created, :modified , :contactPoint, :creator, :contributor, :publisher, :subject, :coverage, :createdWith, :accrualMethod, :accrualPeriodicity, :wasGeneratedBy, :accessURL, :numberOfArtefacts, :federated_portals, :fundedBy def self.type_uri namespace[model_name].to_s end - + def ontologies_count LinkedData::Models::Ontology.where(viewingRestriction: 'public').count end @@ -204,11 +206,11 @@ def set_uri_regex_pattern def set_preferred_namespace_uri "" end - + def set_preferred_namespace_prefix "" end - + def set_metadata_voc "" end @@ -216,11 +218,11 @@ def set_metadata_voc def mod_uri RDF::URI("https://w3id.org/mod") end - + def set_feature_list [] end - + def set_supported_schema [] end diff --git a/lib/ontologies_linked_data/serializers/json.rb b/lib/ontologies_linked_data/serializers/json.rb index 1aa67f72..187b460d 100644 --- a/lib/ontologies_linked_data/serializers/json.rb +++ b/lib/ontologies_linked_data/serializers/json.rb @@ -18,10 +18,10 @@ def self.serialize(obj, options = {}) def self.serialize_mod_objects(obj, options = {}) # using one context and links in mod objects - global_links, global_context = {}, {} + global_context = {} hash = obj.to_flex_hash(options) do |hash, hashed_obj| - process_common_serialization(hash, hashed_obj, options, global_links, global_context) + process_common_serialization(hash, hashed_obj, options, global_context) end result = {} @@ -32,19 +32,18 @@ def self.serialize_mod_objects(obj, options = {}) end result.merge!(global_context) unless global_context.empty? result.merge!(hash) if hash.is_a?(Hash) - result.merge!(global_links) unless global_links.empty? MultiJson.dump(result) end private - def self.process_common_serialization(hash, hashed_obj, options, global_links= nil, global_context= nil) + def self.process_common_serialization(hash, hashed_obj, options, global_context= nil) current_cls = hashed_obj.respond_to?(:klass) ? hashed_obj.klass : hashed_obj.class result_lang = get_languages(get_object_submission(hashed_obj), options[:lang]) add_id_and_type(hash, hashed_obj, current_cls) - add_links(hash, hashed_obj, options, global_links) if generate_links?(options) + add_links(hash, hashed_obj, options) if generate_links?(options) add_context(hash, hashed_obj, options, current_cls, result_lang, global_context) if generate_context?(options) end @@ -55,19 +54,11 @@ def self.add_id_and_type(hash, hashed_obj, current_cls) hash["@type"] = type(current_cls, hashed_obj) if hash["@id"] end - def self.add_links(hash, hashed_obj, options, global_links) - return if global_links&.any? - + def self.add_links(hash, hashed_obj, options) links = LinkedData::Hypermedia.generate_links(hashed_obj) return if links.empty? - - if global_links.nil? - hash["links"] = links - hash["links"].merge!(generate_links_context(hashed_obj)) if generate_context?(options) - elsif global_links.empty? - global_links["links"] = links - global_links["links"].merge!(generate_links_context(hashed_obj)) if generate_context?(options) - end + hash["links"] = links + hash["links"].merge!(generate_links_context(hashed_obj)) if generate_context?(options) end def self.add_context(hash, hashed_obj, options, current_cls, result_lang, global_context) From 28d4083675acfc852c63198931e8ea5b3edfdfbc Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Wed, 21 May 2025 10:16:52 +0200 Subject: [PATCH 09/19] Revert "exclude serialized methods from nested (embedded) resources (#211)" This reverts commit 905eb1cb30a5232969f20b83f258e585f57dd207. --- lib/ontologies_linked_data/monkeypatches/object.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/ontologies_linked_data/monkeypatches/object.rb b/lib/ontologies_linked_data/monkeypatches/object.rb index 12acee2c..38fa5cff 100644 --- a/lib/ontologies_linked_data/monkeypatches/object.rb +++ b/lib/ontologies_linked_data/monkeypatches/object.rb @@ -33,13 +33,12 @@ def to_flex_hash(options = {}, &block) only = Set.new(options[:include_for_class] || []) end - if all && !options[:nested] # Get everything if not an embedded object + if all # Get everything methods = self.class.hypermedia_settings[:serialize_methods] if self.is_a?(LinkedData::Hypermedia::Resource) end - # Check to see if we're nested, if so remove necessary properties and serialize methods + # Check to see if we're nested, if so remove necessary properties only = only - do_not_serialize_nested(options) - only -= self.class.hypermedia_settings[:serialize_methods] if options[:nested] # Determine whether to use defaults from the DSL or all attributes hash = populate_attributes(hash, all, only, options) From 6b976ec2a5fddf3b8d0f353ce01269642c22273a Mon Sep 17 00:00:00 2001 From: MUH <58882014+muhammedBkf@users.noreply.github.com> Date: Wed, 21 May 2025 16:18:43 +0200 Subject: [PATCH 10/19] exclude serialized methods from nested (embedded) agents (#214) --- lib/ontologies_linked_data/models/agents/agent.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ontologies_linked_data/models/agents/agent.rb b/lib/ontologies_linked_data/models/agents/agent.rb index 7a607c9b..853f3ccd 100644 --- a/lib/ontologies_linked_data/models/agents/agent.rb +++ b/lib/ontologies_linked_data/models/agents/agent.rb @@ -20,6 +20,8 @@ class Agent < LinkedData::Models::Base embed_values affiliations: [:name, :agentType, :homepage, :acronym, :email, :identifiers] serialize_methods :usages + prevent_serialize_when_nested :usages + write_access :creator access_control_load :creator From 766c6b0a5f4dd4bad4e11285b818f40195c72524 Mon Sep 17 00:00:00 2001 From: MUH <58882014+muhammedBkf@users.noreply.github.com> Date: Thu, 22 May 2025 10:20:49 +0200 Subject: [PATCH 11/19] Feature: add keywords loading during agent serialization (#205) * add keywords loading during agent serialization * Limit agents values to the requested ones in the usages request --- .../models/agents/agent.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/ontologies_linked_data/models/agents/agent.rb b/lib/ontologies_linked_data/models/agents/agent.rb index 853f3ccd..6f7888b5 100644 --- a/lib/ontologies_linked_data/models/agents/agent.rb +++ b/lib/ontologies_linked_data/models/agents/agent.rb @@ -17,8 +17,8 @@ class Agent < LinkedData::Models::Base attribute :affiliations, enforce: %i[Agent list is_organization], namespace: :org, property: :memberOf attribute :creator, type: :user, enforce: [:existence] embed :identifiers, :affiliations + serialize_methods :usages, :keywords embed_values affiliations: [:name, :agentType, :homepage, :acronym, :email, :identifiers] - serialize_methods :usages prevent_serialize_when_nested :usages @@ -41,6 +41,7 @@ def self.load_agents_usages(agents = [], agent_attributes = OntologySubmission. q = Goo.sparql_query_client.select(:id, :property, :agent, :status).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id,LinkedData::Models::OntologySubmission.attribute_uri(:submissionStatus),:status], [:id, :property, :agent]) q = q.filter("?status = <#{RDF::URI.new(LinkedData::Models::SubmissionStatus.id_prefix + 'RDF')}> || ?status = <#{RDF::URI.new(LinkedData::Models::SubmissionStatus.id_prefix + 'UPLOADED')}>") q = q.filter(agent_attributes.map{|attr| "?property = <#{attr}>"}.join(' || ')) + q = q.values(:agent, *agents.map { |agent| RDF::URI(agent.id.to_s)}) data = q.each_solution.group_by{|x| x[:agent]} @@ -65,6 +66,21 @@ def usages(force_update: false) @usages end + def self.load_agents_keywords(agent) + q = Goo.sparql_query_client.select(:keywords).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id, :property, :agent], [:id, LinkedData::Models::OntologySubmission.attribute_uri(:keywords), :keywords]) + q = q.filter("?agent = <#{agent.id}>") + q = q.values(:id, *agent.usages.keys.map { |uri| RDF::URI(uri.to_s)}) + + + keywords = q.solutions.map { |solution| solution[:keywords].to_s } + agent.instance_variable_set("@keywords", keywords) + agent.loaded_attributes.add(:keywords) + end + def keywords(force_update: false) + self.class.load_agents_keywords(self) if !instance_variable_defined?("@keywords") || force_update + @keywords + end + def unique_identifiers(inst, attr) inst.bring(attr) if inst.bring?(attr) identifiers = inst.send(attr) From 4c4d4e038c3ae5b537a7e784f384503e98905c3d Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Wed, 28 May 2025 17:08:02 +0200 Subject: [PATCH 12/19] add :groups, :categories, :relatedAgents, :affiliatedAgents attributs to the agent model (#215) Co-authored-by: AmineBKF --- .../models/agents/agent.rb | 96 ++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/lib/ontologies_linked_data/models/agents/agent.rb b/lib/ontologies_linked_data/models/agents/agent.rb index 6f7888b5..48fea4c9 100644 --- a/lib/ontologies_linked_data/models/agents/agent.rb +++ b/lib/ontologies_linked_data/models/agents/agent.rb @@ -17,10 +17,10 @@ class Agent < LinkedData::Models::Base attribute :affiliations, enforce: %i[Agent list is_organization], namespace: :org, property: :memberOf attribute :creator, type: :user, enforce: [:existence] embed :identifiers, :affiliations - serialize_methods :usages, :keywords + serialize_methods :usages, :keywords, :groups, :categories, :relatedAgents, :affiliatedAgents embed_values affiliations: [:name, :agentType, :homepage, :acronym, :email, :identifiers] - prevent_serialize_when_nested :usages + prevent_serialize_when_nested :usages, :affiliations, :keywords, :groups, :categories, :relatedAgents, :affiliatedAgents write_access :creator access_control_load :creator @@ -81,6 +81,72 @@ def keywords(force_update: false) @keywords end + def self.load_agents_categories_groups(agent) + q = Goo.sparql_query_client.select(:groups, :categories).distinct.from(LinkedData::Models::Ontology.uri_type) + q = q.optional([:id, LinkedData::Models::Ontology.attribute_uri(:group), :groups]) + q = q.optional([:id, LinkedData::Models::Ontology.attribute_uri(:hasDomain), :categories]) + # Strip trailing '/submissions/' from URIs + uris = agent.usages.keys.map do |uri| + cleaned_uri = uri.to_s.sub(/\/submissions\/\d+$/, "") + RDF::URI(cleaned_uri) + end + q = q.values(:id, *uris) + + [:groups, :categories].each do |attr| + values = q.solutions.map { |solution| solution[attr] }.compact.uniq.reject(&:empty?) + agent.instance_variable_set("@#{attr}", values) + agent.loaded_attributes.add(attr) + end + end + def categories + self.class.load_agents_categories_groups(self) if !instance_variable_defined?("@categories") + @categories + end + def groups + self.class.load_agents_categories_groups(self) if !instance_variable_defined?("@groups") + @groups + end + + + def self.load_related_agents(agent) + q = Goo.sparql_query_client.select(:id, :agent).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id, :property, :agent]) + q = q.filter(OntologySubmission.agents_attr_uris.map{|attr| "?property = <#{attr}>"}.join(' || ')) + q = q.values(:id, *agent.usages.keys.map { |uri| RDF::URI(uri.to_s)}) + relatedAgentsIds = q.each_solution.group_by{|x| x[:agent].to_s} + .reject { |agent_id, _| agent_id == agent.id.to_s } + .transform_values { |solutions| solutions.map { |s| s[:id] } } + # map the previously fetched usages + relatedAgents = self.fetch_agents_data(relatedAgentsIds.keys).each { |agent| agent.usages = relatedAgentsIds[agent.id.to_s].map(&:to_s).uniq } + + agent.instance_variable_set("@relatedAgents", relatedAgents) + agent.loaded_attributes.add(:relatedAgents) + end + + def relatedAgents + self.class.load_related_agents(self) if !instance_variable_defined?("@relatedAgents") + @relatedAgents + end + + def self.load_affiliated_agents(agent) + return nil unless agent.agentType == 'organization' + q = Goo.sparql_query_client.select(:id).distinct.from(LinkedData::Models::Agent.uri_type) + q = q.where([:id, LinkedData::Models::Agent.attribute_uri(:affiliations), :agent]) + q = q.values(:agent, *agent.id) + + affiliatedAgentsIds = q.solutions.map { |solution| solution[:id].to_s }.uniq + affiliatedAgents = self.fetch_agents_data(affiliatedAgentsIds) + + + agent.instance_variable_set("@affiliatedAgents", affiliatedAgents) + agent.loaded_attributes.add(:affiliatedAgents) + + end + + def affiliatedAgents + self.class.load_affiliated_agents(self) if !instance_variable_defined?("@affiliatedAgents") + @affiliatedAgents + end + def unique_identifiers(inst, attr) inst.bring(attr) if inst.bring?(attr) identifiers = inst.send(attr) @@ -107,6 +173,32 @@ def is_organization(inst, attr) [] end + def self.fetch_agents_data(affiliated_agents_ids) + return [] if affiliated_agents_ids.empty? + + agent_ids = affiliated_agents_ids.map(&:to_s).uniq + + q = Goo.sparql_query_client + .select(:id, :name, :acronym, :agentType) + .distinct + .from(LinkedData::Models::Agent.uri_type) + .where( + [:id, LinkedData::Models::Agent.attribute_uri(:name), :name], + [:id, LinkedData::Models::Agent.attribute_uri(:agentType), :agentType] + ) + .optional([:id, LinkedData::Models::Agent.attribute_uri(:acronym), :acronym]) + .values(:id, *agent_ids.map { |uri| RDF::URI(uri.to_s) }) + + q.solutions.map do |agent| + LinkedData::Models::Agent.read_only( + id: agent[:id].to_s, + name: agent[:name].to_s, + acronym: agent[:acronym].to_s, + agentType: agent[:agentType].to_s, + usages: nil + ) + end + end end end end From eb4b482e9e973b1ff0dfad3b40f5ace74134fa94 Mon Sep 17 00:00:00 2001 From: MUH <58882014+muhammedBkf@users.noreply.github.com> Date: Wed, 25 Jun 2025 17:25:27 +0200 Subject: [PATCH 13/19] Remove `groups` from agents serialized attributes (#216) * remove groups from agents serialized attributes * don't calculate ontologies relates attributes if agents have no usages * remove `klass` attribute from related and affiliated agents --- .../models/agents/agent.rb | 85 +++++++++++-------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/lib/ontologies_linked_data/models/agents/agent.rb b/lib/ontologies_linked_data/models/agents/agent.rb index 48fea4c9..10a0ab46 100644 --- a/lib/ontologies_linked_data/models/agents/agent.rb +++ b/lib/ontologies_linked_data/models/agents/agent.rb @@ -67,12 +67,16 @@ def usages(force_update: false) end def self.load_agents_keywords(agent) - q = Goo.sparql_query_client.select(:keywords).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id, :property, :agent], [:id, LinkedData::Models::OntologySubmission.attribute_uri(:keywords), :keywords]) - q = q.filter("?agent = <#{agent.id}>") - q = q.values(:id, *agent.usages.keys.map { |uri| RDF::URI(uri.to_s)}) + if agent.usages.empty? + keywords = [] + else + q = Goo.sparql_query_client.select(:keywords).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id, :property, :agent], [:id, LinkedData::Models::OntologySubmission.attribute_uri(:keywords), :keywords]) + q = q.filter("?agent = <#{agent.id}>") + q = q.values(:id, *agent.usages.keys.map { |uri| RDF::URI(uri.to_s)}) - keywords = q.solutions.map { |solution| solution[:keywords].to_s } + keywords = q.solutions.map { |solution| solution[:keywords].to_s } + end agent.instance_variable_set("@keywords", keywords) agent.loaded_attributes.add(:keywords) end @@ -80,44 +84,44 @@ def keywords(force_update: false) self.class.load_agents_keywords(self) if !instance_variable_defined?("@keywords") || force_update @keywords end - - def self.load_agents_categories_groups(agent) - q = Goo.sparql_query_client.select(:groups, :categories).distinct.from(LinkedData::Models::Ontology.uri_type) - q = q.optional([:id, LinkedData::Models::Ontology.attribute_uri(:group), :groups]) - q = q.optional([:id, LinkedData::Models::Ontology.attribute_uri(:hasDomain), :categories]) - # Strip trailing '/submissions/' from URIs - uris = agent.usages.keys.map do |uri| - cleaned_uri = uri.to_s.sub(/\/submissions\/\d+$/, "") - RDF::URI(cleaned_uri) - end - q = q.values(:id, *uris) - - [:groups, :categories].each do |attr| - values = q.solutions.map { |solution| solution[attr] }.compact.uniq.reject(&:empty?) - agent.instance_variable_set("@#{attr}", values) - agent.loaded_attributes.add(attr) + + def self.load_agents_categories(agent) + if agent.usages.empty? + categories = [] + else + uris = agent.class.strip_submission_id_from_uris(agent.usages.keys) + + q = Goo.sparql_query_client.select(:categories).distinct.from(LinkedData::Models::Ontology.uri_type) + q = q.optional([:id, LinkedData::Models::Ontology.attribute_uri(:hasDomain), :categories]) + q = q.values(:id, *uris) + + categories = q.solutions.map { |solution| solution[:categories] || solution["categories"] }.compact.uniq.reject(&:empty?) end - end + agent.instance_variable_set("@categories", categories) + agent.loaded_attributes.add("categories") + end + def categories - self.class.load_agents_categories_groups(self) if !instance_variable_defined?("@categories") + self.class.load_agents_categories(self) @categories end - def groups - self.class.load_agents_categories_groups(self) if !instance_variable_defined?("@groups") - @groups - end - def self.load_related_agents(agent) - q = Goo.sparql_query_client.select(:id, :agent).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id, :property, :agent]) - q = q.filter(OntologySubmission.agents_attr_uris.map{|attr| "?property = <#{attr}>"}.join(' || ')) - q = q.values(:id, *agent.usages.keys.map { |uri| RDF::URI(uri.to_s)}) - relatedAgentsIds = q.each_solution.group_by{|x| x[:agent].to_s} - .reject { |agent_id, _| agent_id == agent.id.to_s } - .transform_values { |solutions| solutions.map { |s| s[:id] } } - # map the previously fetched usages - relatedAgents = self.fetch_agents_data(relatedAgentsIds.keys).each { |agent| agent.usages = relatedAgentsIds[agent.id.to_s].map(&:to_s).uniq } - + if agent.usages.empty? + relatedAgents = [] + else + q = Goo.sparql_query_client.select(:id, :agent).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id, :property, :agent]) + q = q.filter(OntologySubmission.agents_attr_uris.map{|attr| "?property = <#{attr}>"}.join(' || ')) + q = q.values(:id, *agent.usages.keys.map { |uri| RDF::URI(uri.to_s)}) + relatedAgentsIds = q.each_solution.group_by{|x| x[:agent].to_s} + .reject { |agent_id, _| agent_id == agent.id.to_s } + .transform_values { |solutions| solutions.map { |s| s[:id] } } + # map the previously fetched usages + relatedAgents = self.fetch_agents_data(relatedAgentsIds.keys) + .reject { |ag| ag.id == agent.id } + .each { |agent| agent.usages = relatedAgentsIds[agent.id.to_s].map(&:to_s).uniq } + .map { |agent| agent.to_h.reject { |k, _| [:klass, :aggregates, :unmapped].include?(k) }} + end agent.instance_variable_set("@relatedAgents", relatedAgents) agent.loaded_attributes.add(:relatedAgents) end @@ -134,7 +138,7 @@ def self.load_affiliated_agents(agent) q = q.values(:agent, *agent.id) affiliatedAgentsIds = q.solutions.map { |solution| solution[:id].to_s }.uniq - affiliatedAgents = self.fetch_agents_data(affiliatedAgentsIds) + affiliatedAgents = self.fetch_agents_data(affiliatedAgentsIds).map { |agent| agent.to_h.reject { |k, _| [:klass, :aggregates, :unmapped].include?(k) }} agent.instance_variable_set("@affiliatedAgents", affiliatedAgents) @@ -199,6 +203,13 @@ def self.fetch_agents_data(affiliated_agents_ids) ) end end + + def self.strip_submission_id_from_uris(uris) + uris.map do |uri| + cleaned_uri = uri.to_s.sub(%r{/submissions/\d+$}, '') + RDF::URI(cleaned_uri) + end + end end end end From abcb92637a11749f335e8a58ba735b538abf7dec Mon Sep 17 00:00:00 2001 From: AmineBKF Date: Thu, 26 Jun 2025 11:00:48 +0200 Subject: [PATCH 14/19] add subjects loading during agent serialization --- .../models/agents/agent.rb | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/ontologies_linked_data/models/agents/agent.rb b/lib/ontologies_linked_data/models/agents/agent.rb index 10a0ab46..75ff2195 100644 --- a/lib/ontologies_linked_data/models/agents/agent.rb +++ b/lib/ontologies_linked_data/models/agents/agent.rb @@ -17,10 +17,10 @@ class Agent < LinkedData::Models::Base attribute :affiliations, enforce: %i[Agent list is_organization], namespace: :org, property: :memberOf attribute :creator, type: :user, enforce: [:existence] embed :identifiers, :affiliations - serialize_methods :usages, :keywords, :groups, :categories, :relatedAgents, :affiliatedAgents + serialize_methods :usages, :keywords, :groups, :categories, :subjects, :relatedAgents, :affiliatedAgents embed_values affiliations: [:name, :agentType, :homepage, :acronym, :email, :identifiers] - prevent_serialize_when_nested :usages, :affiliations, :keywords, :groups, :categories, :relatedAgents, :affiliatedAgents + prevent_serialize_when_nested :usages, :affiliations, :keywords, :groups, :categories, :subjects, :relatedAgents, :affiliatedAgents write_access :creator access_control_load :creator @@ -106,6 +106,27 @@ def categories @categories end + def self.load_agents_subjects(agent) + if agent.usages.empty? + subjects = [] + else + uris = agent.class.strip_submission_id_from_uris(agent.usages.keys) + + q = Goo.sparql_query_client.select(:subjects).distinct.from(LinkedData::Models::OntologySubmission.uri_type) + q = q.optional([:id, LinkedData::Models::OntologySubmission.attribute_uri(:hasDomain), :subjects]) + q = q.values(:id, *uris) + + subjects = q.solutions.map { |solution| solution[:subjects] || solution["subjects"] }.compact.uniq.reject(&:empty?) + end + agent.instance_variable_set("@subjects", subjects) + agent.loaded_attributes.add("subjects") + end + + def subjects + self.class.load_agents_subjects(self) + @subjects + end + def self.load_related_agents(agent) if agent.usages.empty? relatedAgents = [] From 8ce21d045c6fe829c6893c472e2293233d65d402 Mon Sep 17 00:00:00 2001 From: AmineBKF Date: Fri, 27 Jun 2025 10:34:16 +0200 Subject: [PATCH 15/19] add agents subjects attribute --- lib/ontologies_linked_data/models/agents/agent.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/ontologies_linked_data/models/agents/agent.rb b/lib/ontologies_linked_data/models/agents/agent.rb index 75ff2195..f9848e0b 100644 --- a/lib/ontologies_linked_data/models/agents/agent.rb +++ b/lib/ontologies_linked_data/models/agents/agent.rb @@ -110,13 +110,17 @@ def self.load_agents_subjects(agent) if agent.usages.empty? subjects = [] else - uris = agent.class.strip_submission_id_from_uris(agent.usages.keys) - + uris = agent.usages.keys q = Goo.sparql_query_client.select(:subjects).distinct.from(LinkedData::Models::OntologySubmission.uri_type) q = q.optional([:id, LinkedData::Models::OntologySubmission.attribute_uri(:hasDomain), :subjects]) q = q.values(:id, *uris) - subjects = q.solutions.map { |solution| solution[:subjects] || solution["subjects"] }.compact.uniq.reject(&:empty?) + subjects = q.solutions + .map { |solution| solution[:subjects] || solution["subjects"] } + .compact + .map(&:to_s) + .reject(&:empty?) + .uniq end agent.instance_variable_set("@subjects", subjects) agent.loaded_attributes.add("subjects") From 2014407bf814174693141a8710526c4e9035f0d6 Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Fri, 27 Jun 2025 11:19:58 +0200 Subject: [PATCH 16/19] Hotfix: embed agents attributes in catalog --- .../models/mod/semantic_artefact_catalog.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb index c963784d..dbc63c71 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact_catalog.rb @@ -175,6 +175,8 @@ class SemanticArtefactCatalog < LinkedData::Models::ModBase :publisher, :subject, :coverage, :createdWith, :accrualMethod, :accrualPeriodicity, :wasGeneratedBy, :accessURL, :numberOfArtefacts, :federated_portals, :fundedBy + embed :rightsHolder, :contactPoint, :creator, :contributor, :curatedBy, :translator, :publisher, :endorsedBy + def self.type_uri namespace[model_name].to_s end From 6a8e779d922e0df97f5f0efe07e8fcd28ce634c3 Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Fri, 27 Jun 2025 12:04:54 +0200 Subject: [PATCH 17/19] fix: test artefact catalog --- test/models/mod/test_artefact_catalog.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/mod/test_artefact_catalog.rb b/test/models/mod/test_artefact_catalog.rb index 27a2beed..aff493d1 100644 --- a/test/models/mod/test_artefact_catalog.rb +++ b/test/models/mod/test_artefact_catalog.rb @@ -72,6 +72,6 @@ def test_bring_all_attrs sac = LinkedData::Models::SemanticArtefactCatalog.all.first all_attrs_to_bring = LinkedData::Models::SemanticArtefactCatalog.goo_attrs_to_load([:all]) sac.bring(*all_attrs_to_bring) - assert_equal all_attrs_to_bring.sort, (sac.loaded_attributes.to_a + [:type]).sort + assert_equal (all_attrs_to_bring.flat_map { |e| e.is_a?(Hash) ? e.keys : e }).sort, (sac.loaded_attributes.to_a + [:type]).sort end end \ No newline at end of file From b1100838db9a2841d35a8487de9a0071243f8f20 Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Fri, 27 Jun 2025 15:00:05 +0200 Subject: [PATCH 18/19] fix test test_goo_attrs_to_load catalog --- test/models/mod/test_artefact_catalog.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/mod/test_artefact_catalog.rb b/test/models/mod/test_artefact_catalog.rb index aff493d1..c4d59928 100644 --- a/test/models/mod/test_artefact_catalog.rb +++ b/test/models/mod/test_artefact_catalog.rb @@ -56,7 +56,7 @@ def test_goo_attrs_to_load default_attrs = LinkedData::Models::SemanticArtefactCatalog.goo_attrs_to_load([]) assert_equal [:acronym, :title, :color, :description, :logo, :identifier, :status, :language, :type, :accessRights, :license, :rightsHolder, :landingPage, :keyword, :bibliographicCitation, :created, :modified, :contactPoint, :creator, :contributor, :publisher, :subject, - :coverage, :createdWith, :accrualMethod, :accrualPeriodicity, :wasGeneratedBy, :accessURL, :numberOfArtefacts, :federated_portals, :fundedBy], default_attrs + :coverage, :createdWith, :accrualMethod, :accrualPeriodicity, :wasGeneratedBy, :accessURL, :numberOfArtefacts, :federated_portals, :fundedBy].sort, (default_attrs.flat_map { |e| e.is_a?(Hash) ? e.keys : e }).sort specified_attrs = LinkedData::Models::SemanticArtefactCatalog.goo_attrs_to_load([:acronym, :title, :keyword, :featureList]) assert_equal [:acronym, :title, :keyword, :featureList], specified_attrs From 505fbb9f2d9ec23834cfcd5ffd2ba1249b2b254e Mon Sep 17 00:00:00 2001 From: Imad Bourouche Date: Wed, 2 Jul 2025 17:46:33 +0200 Subject: [PATCH 19/19] QA fixes MOD-API (#218) * update artefacts links * address QA changes for distributions --- .../semantic_artefact/attribute_fetcher.rb | 16 +++++++------- .../models/mod/semantic_artefact.rb | 22 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/lib/ontologies_linked_data/concerns/semantic_artefact/attribute_fetcher.rb b/lib/ontologies_linked_data/concerns/semantic_artefact/attribute_fetcher.rb index 1198b565..84b0b3c4 100644 --- a/lib/ontologies_linked_data/concerns/semantic_artefact/attribute_fetcher.rb +++ b/lib/ontologies_linked_data/concerns/semantic_artefact/attribute_fetcher.rb @@ -43,21 +43,21 @@ def fetch_from_ontology(attributes) def fetch_from_submission(attributes) return if attributes.empty? - @latest ||= defined?(@ontology) ? @ontology.latest_submission(status: :ready) : @submission - return unless @latest - @latest.bring(*attributes.values) + @submission_to_fetch_from ||= defined?(@submission) ? @submission : defined?(@ontology) ? @ontology.latest_submission(status: :ready) : nil + return unless @submission_to_fetch_from + @submission_to_fetch_from.bring(*attributes.values) attributes.each do |attr, mapped_attr| - self.send("#{attr}=", @latest.send(mapped_attr)) if @latest.respond_to?(mapped_attr) + self.send("#{attr}=", @submission_to_fetch_from.send(mapped_attr)) if @submission_to_fetch_from.respond_to?(mapped_attr) end end def fetch_from_metrics(attributes) return if attributes.empty? - @latest ||= defined?(@ontology) ? @ontology.latest_submission(status: :ready) : @submission - return unless @latest - @latest.bring(metrics: [attributes.values]) + @submission_to_fetch_from ||= defined?(@submission) ? @submission : defined?(@ontology) ? @ontology.latest_submission(status: :ready) : nil + return unless @submission_to_fetch_from + @submission_to_fetch_from.bring(metrics: [attributes.values]) attributes.each do |attr, mapped_attr| - metric_value = @latest.metrics&.respond_to?(mapped_attr) ? @latest.metrics.send(mapped_attr) || 0 : 0 + metric_value = @submission_to_fetch_from.metrics&.respond_to?(mapped_attr) ? @submission_to_fetch_from.metrics.send(mapped_attr) || 0 : 0 self.send("#{attr}=", metric_value) end end diff --git a/lib/ontologies_linked_data/models/mod/semantic_artefact.rb b/lib/ontologies_linked_data/models/mod/semantic_artefact.rb index 62ee88f7..f822828e 100644 --- a/lib/ontologies_linked_data/models/mod/semantic_artefact.rb +++ b/lib/ontologies_linked_data/models/mod/semantic_artefact.rb @@ -119,16 +119,16 @@ class SemanticArtefact < LinkedData::Models::ModBase attribute :ontology, type: :ontology, enforce: [:existence] links_load :acronym - link_to LinkedData::Hypermedia::Link.new("distributions", lambda {|s| "artefacts/#{s.acronym}/distributions"}, LinkedData::Models::SemanticArtefactDistribution.type_uri), - LinkedData::Hypermedia::Link.new("record", lambda {|s| "artefacts/#{s.acronym}/record"}, LinkedData::Models::SemanticArtefactCatalogRecord.type_uri), - LinkedData::Hypermedia::Link.new("resources", lambda {|s| "artefacts/#{s.acronym}/resources"}), - LinkedData::Hypermedia::Link.new("classes", lambda {|s| "artefacts/#{s.acronym}/classes"}, LinkedData::Models::Class.uri_type), - LinkedData::Hypermedia::Link.new("concepts", lambda {|s| "artefacts/#{s.acronym}/concepts"}, LinkedData::Models::Class.uri_type), - LinkedData::Hypermedia::Link.new("properties", lambda {|s| "artefacts/#{s.acronym}/properties"}, "#{Goo.namespaces[:metadata].to_s}Property"), - LinkedData::Hypermedia::Link.new("individuals", lambda {|s| "artefacts/#{s.acronym}/classes/roots"}, LinkedData::Models::Class.uri_type), - LinkedData::Hypermedia::Link.new("schemes", lambda {|s| "artefacts/#{s.acronym}/schemes"}, LinkedData::Models::SKOS::Scheme.uri_type), - LinkedData::Hypermedia::Link.new("collection", lambda {|s| "artefacts/#{s.acronym}/collections"}, LinkedData::Models::SKOS::Collection.uri_type), - LinkedData::Hypermedia::Link.new("labels", lambda {|s| "artefacts/#{s.acronym}/labels"}, LinkedData::Models::SKOS::Label.uri_type) + link_to LinkedData::Hypermedia::Link.new("distributions", lambda {|s| "mod-api/artefacts/#{s.acronym}/distributions"}, LinkedData::Models::SemanticArtefactDistribution.type_uri), + LinkedData::Hypermedia::Link.new("record", lambda {|s| "mod-api/artefacts/#{s.acronym}/record"}, LinkedData::Models::SemanticArtefactCatalogRecord.type_uri), + LinkedData::Hypermedia::Link.new("resources", lambda {|s| "mod-api/artefacts/#{s.acronym}/resources"}), + LinkedData::Hypermedia::Link.new("classes", lambda {|s| "mod-api/artefacts/#{s.acronym}/resources/classes"}, LinkedData::Models::Class.uri_type), + LinkedData::Hypermedia::Link.new("concepts", lambda {|s| "mod-api/artefacts/#{s.acronym}/resources/concepts"}, LinkedData::Models::Class.uri_type), + LinkedData::Hypermedia::Link.new("properties", lambda {|s| "mod-api/artefacts/#{s.acronym}/resources/properties"}, "#{Goo.namespaces[:metadata].to_s}Property"), + LinkedData::Hypermedia::Link.new("individuals", lambda {|s| "mod-api/artefacts/#{s.acronym}/resources/individuals"}, LinkedData::Models::Class.uri_type), + LinkedData::Hypermedia::Link.new("schemes", lambda {|s| "mod-api/artefacts/#{s.acronym}/resources/schemes"}, LinkedData::Models::SKOS::Scheme.uri_type), + LinkedData::Hypermedia::Link.new("collection", lambda {|s| "mod-api/artefacts/#{s.acronym}/resources/collections"}, LinkedData::Models::SKOS::Collection.uri_type), + LinkedData::Hypermedia::Link.new("labels", lambda {|s| "mod-api/artefacts/#{s.acronym}/resources/labels"}, LinkedData::Models::SKOS::Label.uri_type) # Access control read_restriction_based_on ->(artefct) { artefct.ontology } @@ -185,11 +185,9 @@ def all_distributions(attributes, page, pagesize) submissions_count = OntologySubmission.where.filter(filter_by_acronym).count submissions_page = OntologySubmission.where.include(:distributionId) .filter(filter_by_acronym) - .order_by(distributionId: :desc) .page(page, pagesize) .page_count_set(submissions_count) .all - all_distributions = submissions_page.map do |submission| SemanticArtefactDistribution.new(submission).tap do |dist| dist.bring(*attributes) if attributes