Last active
July 12, 2017 00:14
-
-
Save no-reply/7803282 to your computer and use it in GitHub Desktop.
RDF Resources in OregonDigital
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# RdfResource is a subclass of RDF::Graph with property configuration, accessors, and some other methods | |
# for managing "resources" as discrete subgraphs which can be managed by a Hydra datastream model. | |
# | |
# The relevant modules and class are: | |
# | |
# https://github.com/OregonDigital/oregondigital/tree/master/lib/oregon_digital/rdf/rdf_configurable.rb | |
# https://github.com/OregonDigital/oregondigital/tree/master/lib/oregon_digital/rdf/rdf_properties.rb | |
# https://github.com/OregonDigital/oregondigital/tree/master/lib/oregon_digital/rdf/rdf_resource.rb | |
bnode = OregonDigital::RDF::RdfResource.new | |
bnode.rdf_subject | |
# => #<RDF::Node:0x4199b10(_:g68786960)> | |
bnode << RDF::Statement.new(bnode.rdf_subject, RDF::DC.title, RDF::Literal('A blank node')) | |
bnode << RDF::Statement.new(bnode.rdf_subject, RDF::DC.subject, RDF::Literal('RDF')) | |
bnode.dump :ntriples | |
# => "_:g68786960 <http://purl.org/dc/terms/title> \"A blank node\" .\n_:g68786960 <http://purl.org/dc/terms/subject> \"RDF\" .\n" | |
bnode.set_subject!(RDF::URI('http://example.org/1')) | |
bnode.rdf_subject | |
# => #<RDF::URI:0x452a4dc(http://example.org/1)> | |
bnode.dump :ntriples | |
# => "<http://example.org/1> <http://purl.org/dc/terms/title> \"A blank node\" .\n<http://example.org/1> <http://purl.org/dc/terms/subject> \"RDF\" .\n" | |
# Define subclasses of RdfResource to configure and register properties for types of resources | |
class License < OregonDigital::RDF::RdfResource | |
configure :base_uri => 'http://example.org/license', :type => RDF::DC.LicenseDocument | |
property :title, :predicate => RDF::DC.title do |index| | |
index.as :displayable, :facetable | |
end | |
end | |
cc = License.new('cc') | |
cc.rdf_subject | |
# => #<RDF::URI:0x39a7b28(http://example.org/license/cc)> | |
# cc = License.new; cc.set_subject!('cc'); would accomplish the same thing | |
cc.title = "Creative Commons" | |
cc.dump :ntriples | |
# => "<http://example.org/license/cc> <http://purl.org/dc/terms/title> \"Creative Commons\" .\n" | |
class Document < OregonDigital::RDF::RdfResource | |
configure :base_uri => 'http://example.org/document', :type => RDF::URI('http://purl.org/ontology/bibo/Document') | |
property :title, :predicate => RDF::DC.title do |index| | |
index.as :displayable, :facetable, :searchable | |
end | |
property :license, :predicate => RDF::DC.license, :class_name => License do |index| | |
index.as :displayable, :facetable | |
end | |
end | |
doc = Document.new('1') | |
doc.title = 'A Document' | |
doc.license = cc | |
doc.license | |
# => [#<License:0x47b22f4(default)>] | |
doc.license.first.title | |
# => ["Creative Commons"] | |
doc.dump :ntriples | |
# => "<http://example.org/document/1> <http://purl.org/dc/terms/title> \"A Document\" .\n<http://example.org/document/1> <http://purl.org/dc/terms/license> <http://example.org/license/cc> .\n<http://example.org/license/cc> <http://purl.org/dc/terms/title> \"Creative Commons\" .\n" | |
# note that you can also manage the graph manually; but do so with care! | |
# it matters which statements are in the graph for the object being called. | |
doc2 = Document.new('2') | |
doc2 << RDF::Statement.new(doc2.rdf_subject, RDF::DC.license, cc.rdf_subject) | |
cc.title | |
# => ["Creative Commons"] | |
doc2.license.first.title | |
# => [] | |
doc2 << cc | |
doc2.license.first.title | |
# => ["Creative Commons"] | |
# RDF Resource Datastreams implement the normal AF::Datastream interface as a wrapper around an RdfResource object. | |
# They will also pass down properties defined at the datastream level to the resource they wrap. | |
# | |
# https://github.com/OregonDigital/oregondigital/tree/master/lib/oregon_digital/rdf/object_resource.rb | |
# https://github.com/OregonDigital/oregondigital/tree/master/lib/oregon_digital/rdf_resource_datastream.rb | |
# https://github.com/OregonDigital/oregondigital/tree/master/lib/oregon_digital/quad_resource_datastream.rb | |
class GenericResourceDatastream < OregonDigital::QuadResourceDatastream | |
property :title, :predicate => RDF::DC[:title] do |index| | |
index.as :searchable, :displayable | |
end | |
property :creator, :predicate => RDF::DC[:creator] do |index| | |
index.as :searchable, :displayable, :facetable | |
end | |
property :contributor, :predicate => RDF::DC[:contributor] do |index| | |
index.as :searchable, :displayable, :facetable | |
end | |
property :license, :predicate => RDF::DC[:license], :class_name => License do |index| | |
index.as :displayable | |
end | |
end | |
class DummyAsset < ActiveFedora::Base | |
has_metadata :name => 'descMetadata', :type => GenericResourceDatastream | |
delegate :title, :to => :descMetadata, :multiple => true | |
delegate :license, :to => :descMetadata, :multiple => true | |
end | |
asset = DummyAsset.new | |
asset.title = "Comet in Moominland" | |
asset.license = cc | |
asset.save | |
asset.descMetadata.content | |
# => "<http://example.org/license/cc> <http://purl.org/dc/terms/title> \"Creative Commons\" .\n<http://oregondigital.org/resource/changeme:55> <http://purl.org/dc/terms/title> \"Comet in Moominland\" .\n<http://oregondigital.org/resource/changeme:55> <http://purl.org/dc/terms/license> <http://example.org/license/cc> .\n" | |
# By default, resources persist to their 'parent' datastream. Persistence to any RDF::Repository | |
# is configurable. | |
OregonDigital::RDF::RdfRepositories.add_repository :repo, RDF::Repository.new | |
class CCLicense < OregonDigital::RDF::RdfResource | |
configure :base_uri => 'http://creativecommons.org/licenses/', :type => RDF::DC.LicenseDocument,:repository => :repo | |
property :title, :predicate => RDF::DC.title | |
end | |
by = CCLicense.new('by/4.0/') | |
by.title = 'By Attribution' | |
by.dump :ntriples | |
# =>"<http://creativecommons.org/licenses/by/4.0/> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://purl.org/dc/terms/LicenseDocument> .\n<http://creativecommons.org/licenses/by/4.0/> <http://purl.org/dc/terms/title> \"By Attribution\" .\n" | |
by.persist! | |
OregonDigital::RDF::RdfRepositories.repositories[:repo].dump :ntriples | |
# =>"<http://creativecommons.org/licenses/by/4.0/> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://purl.org/dc/terms/LicenseDocument> .\n<http://creativecommons.org/licenses/by/4.0/> <http://purl.org/dc/terms/title> \"By Attribution\" .\n" | |
# The license is now in the repository, so any RdfResource initialized with that URI will load its data. | |
by2 = CCLicense.new('by/4.0/') | |
by2.title | |
# => ["By Attribution"] | |
GenericResourceDatastream.property :license, :predicate => RDF::DC[:license], :class_name => CCLicense | |
asset = DummyAsset.new | |
asset.license = by | |
asset.descMetadata.content | |
# => "<http://oregondigital.org/resource/changeme:66> <http://purl.org/dc/terms/license> <http://creativecommons.org/licenses/by/4.0/> .\n" | |
asset.license.first.title | |
# => ["By Attribution"] | |
# RdfResources with URIs also respond to #fetch, which retrieves the data at their rdf_subject URI | |
# and loads it into the triplestore. | |
by.fetch | |
by.title | |
# => ["By Attribution", "Attribution 4.0 International"] | |
# RdfResource also responds to #rdf_label, which returns values from the first of the following | |
# predicates to respond non-empty: | |
# | |
# [RDF::SKOS.prefLabel, RDF::DC.title, RDF::RDFS.label, RDF::SKOS.altLabel, RDF::SKOS.hiddenLabel] | |
by.rdf_label | |
# => ["By Attribution", "Attribution 4.0 International"] | |
# #rdf_label is also configurable on the class level. Configured labels are prepended to the default | |
# labels, and take priority. | |
CCLicense.configure :rdf_label => RDF::DC.title | |
# RdfResource classes can include Controlled to add controlled vocabulary functionality. | |
# | |
# Vocabularies are registered in the constant RDF_VOCABS (in an initializer). Vocabularies need a 'prefix' (the base URI | |
# for the vocabulary) and, optionally, a 'source' (an RDF document defining the vocabulary). The 'source' is the prefix | |
# url by default. Registered vocabularies are "strict" by default; this means they will refuse to emit a URI that isn't | |
# defined in a source. | |
RDF_VOCABS = { | |
:dcmitype => { :prefix => 'http://purl.org/dc/dcmitype/', :source => 'http://dublincore.org/2012/06/14/dctype.rdf' }, | |
:marcrel => { :prefix => 'http://id.loc.gov/vocabulary/relators/', :source => 'http://id.loc.gov/vocabulary/relators.nt' }, | |
:iso_639_2 => { :prefix => 'http://id.loc.gov/vocabulary/iso639-2/', :source => 'http://id.loc.gov/vocabulary/iso639-2.nt' }, | |
:cclicenses => { :prefix => 'http://creativecommons.org/licenses/', :source => 'https://raw.github.com/OregonDigital/opaque_ns/master/cclicenses/cclicenses.nt'}, | |
:ccpublic => { :prefix => 'http://creativecommons.org/publicdomain/', :source => 'https://raw.github.com/OregonDigital/opaque_ns/master/cclicenses/cclicenses.nt'}, | |
:geonames => { :prefix => 'http://sws.geonames.org/', :strict => false, :fetch => false }, | |
:lcsh => { :prefix => 'http://id.loc.gov/authorities/subjects/', :strict => false, :fetch => false }, | |
:aat => { :prefix => 'http://vocab.getty.edu/aat/', :strict => false, :fetch => false } | |
} | |
# There is a VocabularyLoader, adapted from the one included in ruby-rdf 1.1, that will fetch registered vocabularies | |
# and create an RDF::Vocabulary or an RDF::StrictVocabulary. The gen_vocabs rake task does this for each registered vocabulary. | |
# This makes it easy to maintain strict vocabularies in the event that they are changed upstream. | |
# | |
# Strict vocabularies are good for small, relatively static value sets like DCMIType or CC Licenses. All of the terms will be | |
# fetched into the generated class in advance, which makes it possible to, e.g., create a drop down menu for the vocab. | |
# Non-strict vocabularies can be used for large sets of terms or vocabularies which are not published in a single source document | |
# like geonames, or LCSH. | |
# RdfResources that implement controlled vocabularies can use terms from multiple vocabularies. | |
class RightsStatement < OregonDigital::RDF::RdfResource | |
include OregonDigital::RDF::Controlled | |
configure :type => RDF::DC.RightsStatement | |
use_vocabulary :cclicenses | |
use_vocabulary :ccpublic | |
end | |
by = RightsStatement.new('not/real/') | |
# OregonDigital::RDF::Controlled::ControlledVocabularyError: Term not in controlled vocabularies: not/real/ | |
by = RightsStatement.new('by/4.0/') | |
zero = RightsStatement.new('zero/1.0/') | |
# #load_vocabularies fetches all terms for all StrictVocabularies used by the class. | |
RightsStatement.load_vocabularies | |
by.rdf_label | |
# => ["Attribution 4.0 International"] | |
zero.rdf_label | |
# => ["CC0 1.0 Universal"] |
👏
Wow, slick!
I've moved the parts of this that apply to AF7 here: https://gist.github.com/no-reply/9519740
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I should note that the predicate mapping is back-compatible with existing RDF datastreams. map_predicates works the same as ever: