-
Notifications
You must be signed in to change notification settings - Fork 63
Lesson: Define a Complex Network of Related RDF Types
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.
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.
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.
Answer
nodes just have an rdf:value
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.
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
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.
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.