diff --git a/Gemfile.lock b/Gemfile.lock index 4a01ba6..94cafd0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,7 +100,7 @@ GEM unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - uri (1.0.2) + uri (1.0.3) PLATFORMS arm64-darwin-24 diff --git a/lib/ontologies_api_client/base.rb b/lib/ontologies_api_client/base.rb index ff4aba6..bd2a5d0 100644 --- a/lib/ontologies_api_client/base.rb +++ b/lib/ontologies_api_client/base.rb @@ -66,6 +66,13 @@ def type @type end + def self.explore(id) + path = self.respond_to?(:collection_path) ? collection_path : '' + id = "#{path}/#{id}" unless id.include?(path) + inst = self.new(values: {id: id}) + LinkedData::Client::LinkExplorer.new({}, inst) + end + ## # Retrieve a set of data using a link provided on an object # This instantiates an instance of this class and uses @@ -132,11 +139,16 @@ def create_attributes(attributes) attr_exists = self.public_methods(false).include?(attr) unless attr_exists self.class.class_eval do - define_method attr.to_sym do - instance_variable_get("@#{attr}") + unless method_defined?(attr.to_sym) + define_method attr.to_sym do + instance_variable_get("@#{attr}") + end end - define_method "#{attr}=" do |val| - instance_variable_set("@#{attr}", val) + + unless method_defined?("#{attr}=".to_sym) + define_method "#{attr}=" do |val| + instance_variable_set("@#{attr}", val) + end end end end diff --git a/lib/ontologies_api_client/collection.rb b/lib/ontologies_api_client/collection.rb index f0bb0f2..add8ce4 100644 --- a/lib/ontologies_api_client/collection.rb +++ b/lib/ontologies_api_client/collection.rb @@ -24,15 +24,16 @@ def method_missing(meth, *args, &block) ## # Get all top-level links for the API - def top_level_links - @top_level_links ||= HTTP.get(LinkedData::Client.settings.rest_url) + def top_level_links(link = LinkedData::Client.settings.rest_url) + @top_level_links ||= {} + @top_level_links[link] ||= HTTP.get(link) end - ## + # # Return a link given an object (with links) and a media type def uri_from_context(object, media_type) object.links.each do |type, link| - return link if link.media_type && link.media_type.downcase.eql?(media_type.downcase) + return link.dup if link.media_type && link.media_type.downcase.eql?(media_type.downcase) end end diff --git a/lib/ontologies_api_client/link_explorer.rb b/lib/ontologies_api_client/link_explorer.rb index cc4c328..db576cb 100644 --- a/lib/ontologies_api_client/link_explorer.rb +++ b/lib/ontologies_api_client/link_explorer.rb @@ -11,11 +11,17 @@ def initialize(links, instance) @instance = instance end + def get(params = {}) + get_link(@instance.id, params) + end + def method_missing(meth, *args, &block) if combined_links.key?(meth.to_s) explore_link(meth, *args) elsif meth == :batch explore_link(args) + elsif !@instance.id.blank? + forward_explore(meth, *args) else super end @@ -43,10 +49,7 @@ def explore_link(*args) ids = link.map {|l| l.to_s} value_cls.where {|o| ids.include?(o.id)} else - url = replace_template_elements(link.to_s, replacements) - value_cls = LinkedData::Client::Base.class_for_type(link.media_type) - params[:include] ||= value_cls.attributes(full_attributes) - HTTP.get(url, params) + get_link(link, params, replacements, full_attributes) end end @@ -56,6 +59,23 @@ def combined_links private + def forward_explore(meth, *args) + sub_id = Array(args).find { |x| x.is_a?(String) } || '' + escaped_sub_id = Addressable::URI.encode_component(sub_id, Addressable::URI::CharacterClasses::UNRESERVED) + link = "#{@instance.id}/#{meth}/#{escaped_sub_id}".chomp('/') + @instance.id = link + LinkExplorer.new(@links, @instance) + end + + def get_link(link, params, replacements = [], full_attributes = {}) + url = replace_template_elements(link.to_s, replacements) + if link.respond_to? :media_type + value_cls = LinkedData::Client::Base.class_for_type(link.media_type) + params[:include] ||= value_cls.attributes(full_attributes) + end + HTTP.get(url, params) + end + def replace_template_elements(url, values = []) return url if values.nil? || values.empty? @@ -95,4 +115,4 @@ def linkable_attributes end end end -end \ No newline at end of file +end diff --git a/lib/ontologies_api_client/models/class.rb b/lib/ontologies_api_client/models/class.rb index 7c2e250..d98e7c8 100644 --- a/lib/ontologies_api_client/models/class.rb +++ b/lib/ontologies_api_client/models/class.rb @@ -8,9 +8,9 @@ module Models class Class < LinkedData::Client::Base HTTP = LinkedData::Client::HTTP @media_type = %w[http://www.w3.org/2002/07/owl#Class http://www.w3.org/2004/02/skos/core#Concept] - @include_attrs = "prefLabel,definition,synonym,obsolete,hasChildren" - @include_attrs_full = "prefLabel,definition,synonym,obsolete,properties,hasChildren,children" - @attrs_always_present = :prefLabel, :definition, :synonym, :obsolete, :properties, :hasChildren, :children + @include_attrs = "prefLabel,definition,synonym,obsolete,hasChildren,inScheme,memberOf" + @include_attrs_full = "prefLabel,definition,synonym,obsolete,properties,hasChildren,childre,inScheme,memberOf" + @attrs_always_present = :prefLabel, :definition, :synonym, :obsolete, :properties, :hasChildren, :children, :inScheme, :memberOf alias :fullId :id diff --git a/lib/ontologies_api_client/models/collection.rb b/lib/ontologies_api_client/models/collection.rb new file mode 100644 index 0000000..b4c2e01 --- /dev/null +++ b/lib/ontologies_api_client/models/collection.rb @@ -0,0 +1,12 @@ +require_relative "../base" + +module LinkedData + module Client + module Models + class Collection < LinkedData::Client::Base + include LinkedData::Client::Collection + @media_type = "http://www.w3.org/2004/02/skos/core#Collection" + end + end + end +end \ No newline at end of file diff --git a/lib/ontologies_api_client/models/scheme.rb b/lib/ontologies_api_client/models/scheme.rb new file mode 100644 index 0000000..a070a96 --- /dev/null +++ b/lib/ontologies_api_client/models/scheme.rb @@ -0,0 +1,12 @@ +require_relative "../base" + +module LinkedData + module Client + module Models + class Scheme < LinkedData::Client::Base + include LinkedData::Client::Collection + @media_type = "http://www.w3.org/2004/02/skos/core#ConceptScheme" + end + end + end +end \ No newline at end of file diff --git a/lib/ontologies_api_client/models/skos_xl_label.rb b/lib/ontologies_api_client/models/skos_xl_label.rb new file mode 100644 index 0000000..b209af4 --- /dev/null +++ b/lib/ontologies_api_client/models/skos_xl_label.rb @@ -0,0 +1,12 @@ +require_relative "../base" + +module LinkedData + module Client + module Models + class Label < LinkedData::Client::Base + include LinkedData::Client::Collection + @media_type = "http://www.w3.org/2008/05/skos-xl#Label" + end + end + end +end diff --git a/test/models/test_class.rb b/test/models/test_class.rb index 0229ffe..c0d6942 100644 --- a/test/models/test_class.rb +++ b/test/models/test_class.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true -require 'faraday/follow_redirects' require_relative '../test_case' class ClassTest < LinkedData::Client::TestCase + @@purl_prefix = LinkedData::Client.settings.purl_prefix + def test_find id = 'http://bioontology.org/ontologies/Activity.owl#Activity' ontology = 'https://data.bioontology.org/ontologies/BRO' @@ -24,12 +25,13 @@ def test_purl_owl 'https://data.bioontology.org/ontologies/BRO' ) refute_nil cls + expected_purl = "#{@@purl_prefix}/BRO?conceptid=http%3A%2F%2Fbioontology.org%2Fontologies%2FActivity.owl%23Activity" + assert_equal expected_purl, cls.purl res = fetch_response(cls.purl) - assert_equal 200, res.status - assert_equal 'https://bioportal.bioontology.org/ontologies/BRO'\ - '?p=classes&conceptid=http%3A%2F%2Fbioontology.org%2Fontologies%2FActivity.owl%23Activity', - res.env[:url].to_s + assert_equal 302, res.status + assert_equal 'https://bioportal.bioontology.org/ontologies/BRO/classes?conceptid=http%3A%2F%2Fbioontology.org%2Fontologies%2FActivity.owl%23Activity', + res.headers['location'] end # Test PURL generation for a class in a UMLS format ontology @@ -40,10 +42,13 @@ def test_purl_umls ) refute_nil cls + # The ID already contains the PURL host, so .purl should return it as-is + assert_equal cls.id, cls.purl + res = fetch_response(cls.purl) - assert_equal 200, res.status - assert_equal 'https://bioportal.bioontology.org/ontologies/SNOMEDCT?p=classes&conceptid=64572001', - res.env[:url].to_s + assert_equal 302, res.status + assert_equal 'https://bioportal.bioontology.org/ontologies/SNOMEDCT/classes/64572001', + res.headers['location'] end # Test PURL generation for a class in an OBO format ontology @@ -54,20 +59,20 @@ def test_purl_obo ) refute_nil cls + expected_purl = "#{@@purl_prefix}/DOID?conceptid=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FDOID_4" + assert_equal expected_purl, cls.purl + res = fetch_response(cls.purl) - assert_equal 200, res.status - assert_equal 'https://bioportal.bioontology.org/ontologies/DOID'\ - '?p=classes&conceptid=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FDOID_4', - res.env[:url].to_s + assert_equal 302, res.status + assert_equal 'https://bioportal.bioontology.org/ontologies/DOID/classes?conceptid=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FDOID_4', + res.headers['location'] end private def fetch_response(url) - conn = Faraday.new do |f| - f.response :follow_redirects + Faraday.new do |f| f.adapter Faraday.default_adapter - end - conn.get(url) + end.get(url) end end diff --git a/test/models/test_explore.rb b/test/models/test_explore.rb new file mode 100644 index 0000000..931cf18 --- /dev/null +++ b/test/models/test_explore.rb @@ -0,0 +1,50 @@ +require_relative '../test_case' +require 'pry' + +module Models + def self.method_missing + binding.pry + end +end + +class LinkExploreTest < LinkedData::Client::TestCase + def test_explore + sub_direct_explore = LinkedData::Client::Models::Ontology.explore('MEDDRA') + .latest_submission + .get(include: 'all') + + sub_indirect_explore = LinkedData::Client::Models::Ontology.find('MEDDRA').explore.latest_submission + + refute_nil sub_direct_explore + refute_nil sub_indirect_explore + + sub_direct_explore.to_hash.each do |key, value| + value_to_compare = sub_indirect_explore.to_hash[key] + if value.class.ancestors.include?(LinkedData::Client::Base) + value = value.to_hash + value_to_compare = value_to_compare.to_hash + end + # assert_equal value_to_compare, value, "Mismatch for key #{key}" + assert value == value_to_compare, "Mismatch for key #{key}: #{value.inspect} != #{value_to_compare.inspect}" + end + end + + def test_explore_class + + id = 'http://purl.org/sig/ont/fma/fma62955' + cls = LinkedData::Client::Models::Ontology.explore('FMA') + .classes(id) + .children + .get + + refute_empty cls.collection + + cls = LinkedData::Client::Models::Ontology.explore('FMA') + .classes(id) + .children + .get(include: 'prefLabel') + + refute_empty cls.collection + end + +end