Skip to content

Lesson: Define a Complex Network of Related RDF Types (AF7)

Michael J. Giarlo edited this page Jun 9, 2014 · 25 revisions

Explanation

This lesson will show ways to work with complex networks of related RDF Types. You may need these features in order to work with RDF content like MADS metadata, which specifies deep trees of linked data structures, or you may need it to work with your own custom networks of linked content.

These features are designed to follow the pattern established by the nested attributes behaviors in Rails (aka. ActiveRecord) In order to align with existing tutorials and documentation on the Rails nested attributes behaviors, we will create RDF Types representing Member, Question, and Answer nodes.

Members

Member nodes will represent people/users. We will map predicates for

  • foaf:nickname
  • foaf:givenName
  • foaf:familyName
  • any number of Question nodes

That information will be expressed using FOAF. Members also have any number of Question nodes.

We will use a custom Vocabulary and custom predicate to represent the relationship between Members and Questions.

Questions

Question nodes have predicates mapped for

  • dc:title
  • dc:description
  • any number of Answer nodes.

We will use a custom Vocabulary and custom predicate to represent the relationship between Questions and Answers.

Answers

Answer nodes just have an rdf:value

Steps

Step: Define the Member RDF Type and a Custom Vocabulary

Create a file called member.rb in the lib/rdf_types folder with this content:

require "active-fedora"
require  "./lib/rdf_vocabularies/questions_vocab.rb"
class Member < ActiveFedora::Rdf::Resource
  configure type: RDF::FOAF.Person
  property :nick, predicate: RDF::FOAF.nick
  property :givenName, predicate: RDF::FOAF.givenName
  property :familyName, predicate: RDF::FOAF.familyName
  property :questions, predicate: QuestionsVocab.askedQuestion, class_name: "Question"
end

Also create a file called questions_vocab.rb in the lib/rdf_vocabularies folder with this content:

class QuestionsVocab < RDF::Vocabulary("http://example.com/ontologies/QuestionsAndAnswers/0.1/") 
  property :Question
  property :Answer  
  property :askedQuestion
  property :hasAnswer
end

In the console, create a Member object and add a couple questions to it:

require "./lib/rdf_types/member"
m = Member.new
m.questions << "Who am I?"
m.questions << "What am I doing here?"
m.questions
=> ["Who am I?", "What am I doing here?"] 

That worked! ...Huh? Those aren't of type Question! Defining something of a certain class type DOES NOT actually enforce restrictions on that property. It only says that is how you intend to interact with that triple and will actually take any RDF value.

The resulting graph in this case contained Questions as literals, such as the string "Who am I?". We want them to be Question nodes that conform to a specific RDF Type that we've defined. In order to enable that, we need to create the class for that Question RDF Type and specify in our Member class that its :questions predicate should point to Question nodes.

Step: Define the Question RDF Type and Set up Association from Members to Questions

First, define the Question class as an RDF Type by creating a file called question.rb in the lib/rdf_types folder with this content:

require "active-fedora"
require "./lib/rdf_vocabularies/questions_vocab"

class Question < ActiveFedora::Rdf::Resource
  configure type: QuestionsVocab.Question
  property :title, predicate: RDF::DC.title
  property :description, predicate: RDF::DC.description
  property :answers, predicate: QuestionsVocab.hasAnswer
end

Now that we've created our Question class, we can use it. However, we can't simply pass a string to it as we did above. We first must build the question node before adding the actual content of the question itself. Open up a new console session and try this out:

require "./lib/rdf_types/question"
require "./lib/rdf_types/member"
member = Member.new
 => #<Member:0x007f9d2d868630 @graph=#<RDF::Graph:0x3fce96c2375c(default)>, @rdf_subject=#<RDF::Node:0x3fce95ba9a70(_:g70156507847280)>> 
member.nick = "thenick"
 => "thenick" 
member.givenName = "Julius"
 => "Julius" 
member.familyName = "Caesar"
 => "Caesar" 
member.questions 
 => []
first_question = Question.new
 => #<Question:0x160f9a4(default)> 
first_question.title = "What's the difference between rdf:value and rdf:property?"
 => "What's the difference between rdf:value and rdf:property?" 
member.questions << first_question
 => nil
# Alternative way to make a second question...
member.questions.build
 => #<Question:0x1bf99f0(default)> 
member.questions[1].title = "Why am I doing this?"
 => "Why am I doing this?" 
member.questions
 => [#<Question:0xaca724(default)>, #<Question:0xbcd720(default)>]
puts member.dump(:ntriples)
_:g70156507847280 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
_:g70156507847280 <http://xmlns.com/foaf/0.1/nick> "thenick" .
_:g70156507847280 <http://xmlns.com/foaf/0.1/givenName> "Julius" .
_:g70156507847280 <http://xmlns.com/foaf/0.1/familyName> "Caesar" .
_:g70156507847280 <http://example.com/ontologies/QuestionsAndAnswers/0.1/askedQuestion> _:g70276150989740 .
_:g70156507847280 <http://example.com/ontologies/QuestionsAndAnswers/0.1/askedQuestion> _:g70276151434180 .
_:g70276150989740 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/ontologies/QuestionsAndAnswers/0.1/Question> .
_:g70276150989740 <http://purl.org/dc/terms/title> "What's the difference between rdf:value and rdf:property?" .
_:g70276151434180 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/ontologies/QuestionsAndAnswers/0.1/Question> .
_:g70276151434180 <http://purl.org/dc/terms/title> "Why am I doing this?" .
 => nil 

For the first question, we have instantiated it, updated its metadata, and added it to our member.questions using the standard syntax of "<<". We can then access these added questions using the standard methods such as first and [0] to access individual questions and modify them.

Note that there is an alternative way to add a question using the .build method on member.questions to create the question node and then set the title of the question node using standard methods like last and [1].

Now we need to create the Answer RDF Type and make Questions point at it, just like Members point at the Question RDF Type

Step: Define the Answer RDF Type

Create a file called answer.rb in the lib/rdf_types folder with this content:

require "active-fedora"
require "./lib/rdf_vocabularies/questions_vocab"

class Answer < ActiveFedora::Rdf::Resource
  configure type: QuestionsVocab.Answer
  property :description, predicate: RDF::DC.description
end

And update question.rb to specify a class_name for the .answers predicate.

property :answers, predicate: QuestionsVocab.hasAnswer, class_name: "Answer"

Now play around in the console

require "./lib/rdf_types/answer"
require "./lib/rdf_types/question"
require "./lib/rdf_types/member"
member = Member.new
 => #<Member:0x144e9a8(default)> 
the_question = Question.new
 => #<Question:0x146e9b0(default)> 
the_answer = Answer.new
 => #<Answer:0x148b830(default)>
the_question.answers << the_answer
 => nil 
member.questions << the_question
 => nil
member.questions.first.answers.first.description = "Because I said so."
puts member.dump(:ntriples)
_:g70335216387980 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
_:g70335216387980 <http://example.com/ontologies/QuestionsAndAnswers/0.1/askedQuestion> _:g70335218352900 .
_:g70335218352900 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/ontologies/QuestionsAndAnswers/0.1/Question> .
_:g70335218352900 <http://example.com/ontologies/QuestionsAndAnswers/0.1/hasAnswer> _:g70335232184940 .
_:g70335232184940 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.com/ontologies/QuestionsAndAnswers/0.1/Answer> .
_:g70335232184940 <http://purl.org/dc/terms/description> "Because I said so." .
 => nil 

Huzzah.

Next Step

Go on to Lesson: Using Rails Nested Attributes behavior to modify Nested Nodes (AF7) or return to the Tame your RDF Metadata with ActiveFedora landing page.

Clone this wiki locally