Skip to content

Lesson: Define a Complex Network of Related RDF Types

Adam Wead edited this page Feb 24, 2014 · 12 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 with this content:

require "active-fedora"
require  "./questions_vocab.rb"
class Member
  include ActiveFedora::RdfObject
  rdf_type "http://xmlns.com/foaf/0.1/Person"
  map_predicates do |map|
    map.nick(in: RDF::FOAF)
    map.givenName(in: RDF::FOAF)
    map.familyName(in: RDF::FOAF)
    map.questions(to: "askedQuestion", in: QuestionsVocab, class_name: "Question")
  end
end

Also create a file called questions_vocab.rb 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 "./member"
graph = RDF::Graph.new
m = Member.new(graph)
m.questions << "Who am I?"
=> ["Who am I?"]
m.questions << "What am I doing here?"
=> ["What am I doing here?"]
m.questions

Oh no! ArgumentError!

The resulting graph contains 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 with this content:

require "active-fedora"
require "./vocabularies/questions_vocab"
class Question
  include ActiveFedora::RdfObject
  rdf_type QuestionsVocab.Question
  map_predicates do |map|
    map.title(in: RDF::DC)
    map.description(in: RDF::DC)
    map.answers(to: "hasAnswer", in: QuestionsVocab)
  end
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 "./question"
require "./member.rb"
member = Member.new(RDF::Graph.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 
 => []
member.questions.build
 => #<Question:0x007fd4e2080fc0 @graph=#<RDF::Graph:0x3fea7159cfcc(default)>, @rdf_subject=#<RDF::Node:0x3fea710413ac(_:g70276150989740)>> 
member.questions.first.title = "What's the difference between rdf:value and rdf:property?"
 => "What's the difference between rdf:value and rdf:property?" 
member.questions.build
 => #<Question:0x007fd4e215af18 @graph=#<RDF::Graph:0x3fea7159cfcc(default)>, @rdf_subject=#<RDF::Node:0x3fea710adbc4(_:g70276151434180)>> 
member.questions[1].title = "Why am I doing this?"
 => "Why am I doing this?" 
puts member.graph.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 

Note that for every new question that's added to our member, we must call .build to create the question node, then set the title of the question node to the actual question. The questions method that Member calls is an array of questions, so we can use the standard methods such as first and [1] to access individual questions and create or update their titles.

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 with this content:

require "active-fedora"
require "./questions_vocab"
class Answer
  include ActiveFedora::RdfObject
  rdf_type QuestionsVocab.Answer
  map_predicates do |map|
    map.description(in: RDF::DC)
  end
end

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

map.answers(to: "hasAnswer", in: QuestionsVocab, class_name: "Answer")

Now play around in the console

require "./answer"
require "./question"
require "./member"
member = Member.new(RDF::Graph.new)
member.questions.build
member.questions.first.answers.build
member.questions.first.answers.first.description = "Because I said so."
puts member.graph.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 or return to the Tame your RDF Metadata with ActiveFedora landing page.

Clone this wiki locally