Skip to content

Instantly share code, notes, and snippets.

@elrayle
Last active July 29, 2016 15:35
Show Gist options
  • Select an option

  • Save elrayle/5904be67ba505e23d68b to your computer and use it in GitHub Desktop.

Select an option

Save elrayle/5904be67ba505e23d68b to your computer and use it in GitHub Desktop.
Defining and Working with a Graph in ActiveTriples

Defining Links between RDFSources and Navigating the Graph in ActiveTriples


Last Update: 2016-03-09


Table of Contents


Terminology

The following defines terminology used in the document to ensure common understanding.

rdf-statement
defines a triple in the form <subject><predicate><object> where <subject> and <predicate> are both URIs and <object> can be a URI or literal
subject-uri
the uri in the <subject> position of a triple
predicate-uri
the uri in the <predicate> position of a triple
object-value
the value in the <object> position of a triple, which can be a URI or literal
rdf-source
an instance of a class that includes ActiveTriples::RDFSource and defines properties for predicates which become rdf-statements in the datastore/repository
subject-source
when speaking of two rdf-sources in relationship to each other, this is the rdf-source serving the role of <subject> in the rdf-statement. It is the rdf-source that is defining the predicate through a property definition that will hold the link to the other rdf-source that is the <object> of the statement.
object-source
a rdf-source that is the value of a predicate in a subject-source
property-definition
defines rdf-statements by providing a name to use as an accessor for the object-value(s) and the predicate to use for the rdf-statement(s)
property-name
Through reflection, this becomes the methods for accessing the object-value of the rdf-statement generated for this property. For example a property defined with name :has_body will have methods has_body and has_body= defined to get and set the value for has_body, respectively.
property-predicate
defines the URI to use as the predicate-uri for the rdf-statement generated for this property
class_name
defines the class of the object-source that will be the object-value of the rdf-statement generated for this property. See Explicitly defining a link to another rdf-source section for more information.

Defining links between rdf-sources in ActiveTriples

Explicitly defining a link to another rdf-source

Defining:

A property-definition includes a name for accessor methods and predicate to use as the predicate-uri in the generated rdf-statement(s). It can also have a :classname parameter.

:classname
Specifies the class name of another rdf-source. The object-value of this property should be a rdf-source of this class. By setting the :classname, you are explicitly defining the link between the two rdf-sources in the graph. This link is unidirectional from the subject-source to the object-source. To have a bidirectional link, the object-source would need a similar property definition leading back to the original subject-source.

Impact on:

subject-source
  • the set of rdf-statements may include an rdf-statement defining the object-source's type (e.g. <object-source-uri><type-predicate-uri><type-uri>')
persist
  • rdf-statements for the subject-source are persisted
  • an additional triple with the object-source's rdf-statement for its type will also be persisted
  • all rdf-statements are persisted according to the persistence strategy defined on the subject-source
resume
  • resuming an subject-source is performed by creating a new instance of the rdf-source class and passing in the URI that is the subject-uri (e.g. anno1 = AnnotationResource.new('http://www.example.com/anno1') )
  • initial read of the subject-source from the datastore/repository will include all rdf-statements that match predicates in property-definitions of the subject-source
  • initial read of the subject-source from the datastore/repository will NOT include the extra object-source's type rdf-statement
  • once the accessor for the object-source property is referenced, the subject-source will have the extra object-source's type rdf-statement
  • all other aspects of resuming happen in accordance with the persistence strategy of the subject-source
destroy
  • removes all rdf-statements of the subject-source in accordance with the persistence strategy defined on the subject-source
  • has no impact on object-sources of the subject-source

When the :classname is defined, and the property is referenced

Implicitly defined link to another rdf-source

An implicit definition of a link to another rdf-source occurs when the property is defined without a :classname AND the object-value of the rdf-statement is a URI. That object-value will be


Examples

Classes for use by all examples

require 'active_triples'
require 'linkeddata'

ActiveTriples::Repositories.add_repository :default, RDF::Repository.new
r = ActiveTriples::Repositories.repositories[:default]

class DummyAuthor
  include ActiveTriples::RDFSource
  configure repository: :default, type: RDF::URI('http://www.example.com/type/Author')  
  property :name, predicate: RDF::URI('http://www.example.com/ontology/name')
end

class DummyPage
  include ActiveTriples::RDFSource
  configure repository: :default, type: RDF::URI('http://www.example.com/type/Page')  
  property :page_number, predicate: RDF::URI('http://www.example.com/ontology/pageNumber')
end

class DummyChapter
  include ActiveTriples::RDFSource
  configure repository: :default, type: RDF::URI('http://www.example.com/type/Chapter')  
  property :title, predicate: RDF::URI('http://www.example.com/ontology/title')
  property :has_page, predicate: RDF::URI('http://www.example.com/ontology/hasPage'), class_name: DummyPage   # Explicit Link
end

class DummyBook
  include ActiveTriples::RDFSource
  configure repository: :default, type: RDF::URI('http://www.example.com/type/Book')  
  property :title, predicate: RDF::URI('http://www.example.com/ontology/title')
  property :has_chapter, predicate: RDF::URI('http://www.example.com/ontology/hasChapter'), class_name: DummyChapter  # is Explicit Link
  property :author, predicate: RDF::URI('http://www.example.com/ontology/author')                                     # can be Implicit Link
end

Setting up objects with Explicit and Implicit Links

bk1 = DummyBook.new('http://www.example.com/book1')
bk1.title = 'Learning about Explicit Links in ActiveTriples'

ch1 = DummyChapter.new('http://www.example.com/chapter1',bk1)
ch1.title = 'Defining a source with an Explicit Link'
ch2 = DummyChapter.new('http://www.example.com/chapter2',bk1)
ch2.title = 'Persisting a source with Explicit Links'


pgs = []
1.upto(4) do |pgnum|
  pg = DummyPage.new("http://www.example.com/page#{pgnum}",bk1)
  pg.page_number = pgnum
  ch1.has_page << pg  if pgnum.between?(1,2)     # set explicit link via class_name
  ch2.has_page << pg  if pgnum.between?(3,4)     # set explicit link via class_name
end

au1 = DummyAuthor.new('http://www.example.com/book1/author1',bk1)
au1.name = 'Chad T. Nuga'

bk1.has_chapter = [ch1,ch2]          # set explicit link via class_name
bk1.author = au1                     # set implicit link

# Persists all objects using ParentStrategy because all objects except bk1 were created with bk1 as the parent.
# This is a convenience.  All objects could have been persisted individually with RepositoryStrategy.  This is
# not a requirement of using class_name.
bk1.persist!       

puts r.dump :ttl
# <http://www.example.com/book1> a <http://www.example.com/type/Book>;
#    <http://www.example.com/ontology/author> <http://www.example.com/book1/author1>;
#    <http://www.example.com/ontology/hasChapter> <http://www.example.com/chapter1>,
#      <http://www.example.com/chapter2>;
#    <http://www.example.com/ontology/title> "Learning about Explicit Links in ActiveTriples" .
# 
# <http://www.example.com/book1/author1> a <http://www.example.com/type/Author>;
#    <http://www.example.com/ontology/name> "Chad T. Nuga" .
# 
# <http://www.example.com/chapter1> a <http://www.example.com/type/Chapter>;
#    <http://www.example.com/ontology/hasPage> <http://www.example.com/page1>,
#      <http://www.example.com/page2>;
#    <http://www.example.com/ontology/title> "Defining a source with an Explicit Link" .
# 
# <http://www.example.com/chapter2> a <http://www.example.com/type/Chapter>;
#    <http://www.example.com/ontology/hasPage> <http://www.example.com/page3>,
#      <http://www.example.com/page4>;
#    <http://www.example.com/ontology/title> "Persisting a source with Explicit Links" .
# 
# <http://www.example.com/page1> a <http://www.example.com/type/Page>;
#    <http://www.example.com/ontology/pageNumber> 1 .
# 
# <http://www.example.com/page2> a <http://www.example.com/type/Page>;
#    <http://www.example.com/ontology/pageNumber> 2 .
# 
# <http://www.example.com/page3> a <http://www.example.com/type/Page>;
#    <http://www.example.com/ontology/pageNumber> 3 .
# 
# <http://www.example.com/page4> a <http://www.example.com/type/Page>;
#    <http://www.example.com/ontology/pageNumber> 4 .

Read primary object (instance of DummyBook) from the repository

bk1r = DummyBook.new('http://www.example.com/book1')
bk1r.class                 # => DummyBook
bk1r.title                 # => "Learning about Explicit Links in ActiveTriples"
bk1r.persistence_strategy  # => ActiveTriples::RepositoryStrategy
puts bk1r.dump :ttl
# <http://www.example.com/book1> a <http://www.example.com/type/Book>;
#    <http://www.example.com/ontology/author> <http://www.example.com/book1/author1>;
#    <http://www.example.com/ontology/hasChapter> <http://www.example.com/chapter1>,
#      <http://www.example.com/chapter2>;
#    <http://www.example.com/ontology/title> "Learning about Explicit Links in ActiveTriples" .
info_16 NOTE: There aren't any triples with the chapters, pages, or author as subject.

get explicit link from primary object

ch1r = bk1r.has_chapter.first
ch1r.class                 # => DummyChapter
ch1r.title                 # => "Defining a source with an Explicit Link"
ch1r.persistence_strategy  # => ActiveTriples::ParentingStrategy
puts ch1r.dump :ttl
# <http://www.example.com/chapter1> a <http://www.example.com/type/Chapter>;
#    <http://www.example.com/ontology/hasPage> <http://www.example.com/page1>,
#      <http://www.example.com/page2>;
#    <http://www.example.com/ontology/title> "Defining a source with an Explicit Link" .
info_16 NOTE: The chapter is using ParentingStrategy for persistence and has bk1r as the final_parent.
info_16 NOTE: Chapter1 was read via lazy load. Chapter1 was not populated from the repository until it was referenced using bk1r.has_chapter. At this point, page1 and page2 are not loaded yet because they have not been referenced.
puts bk1r.dump :ttl
# <http://www.example.com/book1> a <http://www.example.com/type/Book>;
#    <http://www.example.com/ontology/author> <http://www.example.com/book1/author1>;
#    <http://www.example.com/ontology/hasChapter> <http://www.example.com/chapter1>,
#      <http://www.example.com/chapter2>;
#    <http://www.example.com/ontology/title> "Learning about Explicit Links in ActiveTriples" .
# 
# <http://www.example.com/chapter1> a <http://www.example.com/type/Chapter>;
#    <http://www.example.com/ontology/hasPage> <http://www.example.com/page1>,
#      <http://www.example.com/page2>;
#    <http://www.example.com/ontology/title> "Defining a source with an Explicit Link" .
# 
# <http://www.example.com/chapter2> a <http://www.example.com/type/Chapter>;
#    <http://www.example.com/ontology/hasPage> <http://www.example.com/page3>,
#      <http://www.example.com/page4>;
#    <http://www.example.com/ontology/title> "Persisting a source with Explicit Links" .
info_16 NOTE: The triples for both chapters have now been loaded into bk1r via lazy load. Why both chapters instead of just Chapter1? This happens because Chapter1 was loaded by referencing bk1r.has_chapter which holds both Chapter1 and Chapter2 in the multi-value has_chapter property.

get implicit link from primary object

au1r = bk1r.author.first
au1r.class                 # => ActiveTriples::Resource
au1r.name                  # NoMethodError: undefined method `name' for #<ActiveTriples::Resource:0x00...>
au1r.persistence_strategy  # => ActiveTriples::ParentingStrategy
puts au1r.dump :ttl        # => nil
info_16 NOTE: Even though au1r has ParentingStrategy, it will not lazy load the author triples because it does not know what class to use for the author property.

using information in an implicit link to directly read from the repository

au1r = DummyAuthor.new(bk1r.author.first.rdf_subject)
au1r.class                 # => DummyAuthor
au1r.name                  # => 'Chad T. Nuga'
au1r.persistence_strategy  # => ActiveTriples::RepositoryStrategy
puts au1r.dump :ttl
# <http://www.example.com/book1/author1> a <http://www.example.com/type/Author>;
#    <http://www.example.com/ontology/name> "Chad T. Nuga" .
info_16 NOTE: Reading directly from the repository reads in all triples for author1 and sets the persistence_strategy to RepositoryStrategy.

Navigating the Graph

Create a reasonably complex graph for testing

require 'active_triples'
require 'linkeddata'

ActiveTriples::Repositories.add_repository :default, RDF::Repository.new
r = ActiveTriples::Repositories.repositories[:default]

class DummyAuthor
  include ActiveTriples::RDFSource
  configure repository: :default, type: RDF::URI('http://www.example.com/type/Author')  
  property :name, predicate: RDF::URI('http://www.example.com/ontology/name')
end

class DummyPage
  include ActiveTriples::RDFSource
  configure repository: :default, type: RDF::URI('http://www.example.com/type/Page')  
  property :page_number, predicate: RDF::URI('http://www.example.com/ontology/pageNumber')
end

class DummyChapter
  include ActiveTriples::RDFSource
  configure repository: :default, type: RDF::URI('http://www.example.com/type/Chapter')  
  property :title, predicate: RDF::URI('http://www.example.com/ontology/title')
  property :has_page, predicate: RDF::URI('http://www.example.com/ontology/hasPage'), class_name: DummyPage, cast: true   # Explicit Link
end

class DummyBook
  include ActiveTriples::RDFSource
  configure repository: :default, type: RDF::URI('http://www.example.com/type/Book')  
  property :title, predicate: RDF::URI('http://www.example.com/ontology/title')
  property :has_chapter, predicate: RDF::URI('http://www.example.com/ontology/hasChapter'), class_name: DummyChapter  # Explicit Link
  property :author, predicate: RDF::URI('http://www.example.com/ontology/author')   # can be Implicit Link
end

bk1 = DummyBook.new('http://www.example.com/book1')
bk1.title = 'Learning about Explicit Links in ActiveTriples'

ch1 = DummyChapter.new('http://www.example.com/book1/chapter1',bk1)
ch1.title = 'Defining a source with an Explicit Link'
ch2 = DummyChapter.new('http://www.example.com/book1/chapter2',bk1)
ch2.title = 'Persisting a source with Explicit Links'
ch3 = DummyChapter.new('http://www.example.com/book1/chapter3',bk1)
ch3.title = 'Resuming a source with Explicit Links'
ch4 = DummyChapter.new('http://www.example.com/book1/chapter4',bk1)
ch4.title = 'Destroying a source with Explicit Links'
ch5 = DummyChapter.new('http://www.example.com/book1/chapter5',bk1)
ch5.title = 'Navigating an Explicit Link'

1.upto(15) do |pgnum|
  pg = DummyPage.new("http://www.example.com/book1/page#{pgnum}",bk1)
  pg.page_number = pgnum
  ch1.has_page << pg  if pgnum.between?(1,3)
  ch2.has_page << pg  if pgnum.between?(4,6)
  ch3.has_page << pg  if pgnum.between?(7,9)
  ch4.has_page << pg  if pgnum.between?(10,12)
  ch5.has_page << pg  if pgnum.between?(13,15)
end

au1 = DummyAuthor.new('http://www.example.com/book1/author1',bk1)
au1.name = 'Chad T. Nuga'

bk1.has_chapter = [ch1,ch2,ch3,ch4,ch5]
bk1.author = au1

bk1.persist!


bk2 = DummyBook.new('http://www.example.com/book2')
bk2.title = 'Learning about Implicit Links in ActiveTriples'

ch1 = DummyChapter.new('http://www.example.com/book2/chapter1',bk2)
ch1.title = 'Defining a source with an Implicit Link'
ch2 = DummyChapter.new('http://www.example.com/book2/chapter2',bk2)
ch2.title = 'Persisting a source with Implicit Links'
ch3 = DummyChapter.new('http://www.example.com/book2/chapter3',bk2)
ch3.title = 'Resuming a source with Implicit Links'
ch4 = DummyChapter.new('http://www.example.com/book2/chapter4',bk2)
ch4.title = 'Destroying a source with Implicit Links'
ch5 = DummyChapter.new('http://www.example.com/book2/chapter5',bk2)
ch5.title = 'Navigating an Implicit Link'

1.upto(15).each do |pgnum|
  pg = DummyPage.new("http://www.example.com/book2/page#{pgnum}",bk2)
  pg.page_number = pgnum
  ch1.has_page << pg  if pgnum.between?(1,3)
  ch2.has_page << pg  if pgnum.between?(4,6)
  ch3.has_page << pg  if pgnum.between?(7,9)
  ch4.has_page << pg  if pgnum.between?(10,12)
  ch5.has_page << pg  if pgnum.between?(13,15)
end

bk2.has_chapter = [ch1,ch2,ch3,ch4,ch5]
bk2.author = 'Della Wear'

bk2.persist!

puts r.dump :ttl

Following an Explicit Link

# while all objects are still in memory
ch1 = bk1.has_chapter.first
puts ch1.class
puts ch1.rdf_subject
puts ch1.persistence_strategy
puts ch1.dump :ttl

pg1 = ch1.has_page.first
puts pg1.class
puts pg1.rdf_subject
puts pg1.persistence_strategy
puts pg1.dump :ttl

# after resuming book1
bk1r = DummyBook.new('http://www.example.com/book1')

ch1r = bk1r.has_chapter.first
puts ch1r.class
puts ch1r.rdf_subject
puts ch1r.persistence_strategy
puts ch1r.dump :ttl


ch1r.reload
puts ch1r.dump :ttl

ch1r = DummyChapter.new(ch1r.rdf_subject)
puts pg1.persistence_strategy
puts ch1r.dump :ttl

pg1r = ch1r.has_page.first
puts pg1r.class
puts pg1r.rdf_subject
puts pg1.persistence_strategy
puts pg1r.dump :ttl

pg1r = DummyPage.new(pg1r.rdf_subject)
puts pg1.persistence_strategy
puts pg1r.dump :ttl

Following an Implicit Link

# while all objects are still in memory
au1 = bk1.author.first
puts au1.class
puts au1r.rdf_subject
puts au1.persistence_strategy
puts au1.dump :ttl

# after resuming book1
bk1r = DummyBook.new('http://www.example.com/book1')

au1r = bk1r.author.first
puts au1r.class
puts au1r.rdf_subject
puts au1r.persistence_strategy
puts au1r.dump :ttl

au1r = DummyAuthor.new(au1r.rdf_subject)
puts au1r.class
puts au1r.rdf_subject
puts au1r.persistence_strategy
puts au1r.dump :ttl
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment