-
Notifications
You must be signed in to change notification settings - Fork 61
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:nicknamefoaf:givenNamefoaf:familyName- any number of
Questionnodes
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:titledc:description- any number of
Answernodes.
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"
endAlso 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
endIn 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
endNow 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
endAnd 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.