-
Notifications
You must be signed in to change notification settings - Fork 63
Lesson: Define a Complex Network of Related RDF Types (AF7)
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
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.
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
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.
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.