Skip to content

Instantly share code, notes, and snippets.

@no-reply
Last active July 12, 2017 00:14
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save no-reply/7803282 to your computer and use it in GitHub Desktop.
Save no-reply/7803282 to your computer and use it in GitHub Desktop.
RDF Resources in OregonDigital
# 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"]
@no-reply
Copy link
Author

no-reply commented Dec 5, 2013

I should note that the predicate mapping is back-compatible with existing RDF datastreams. map_predicates works the same as ever:

map_predicates do |map|
  map.title(:in => RDF::DC)
end

@mjgiarlo
Copy link

👏

@jpstroop
Copy link

Wow, slick!

@no-reply
Copy link
Author

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