Description
An ActiveModel-like interface for RDF data. Models graphs as Resources with property/attribute configuration, accessors, and other methods to support Linked Data in a Ruby/Rails enviornment.
This library was extracted from work on ActiveFedora. It is closely related to (and borrows some syntax from) Spira, but does some important things differently.
Installation
Add gem "active-triples"
to your Gemfile and run bundle
.
Or install manually with gem install active-triples
.
Defining Resource Models
The core class of ActiveTriples is ActiveTriples::Resource. You can subclass this to create ActiveModel-like classes that represent a node in an RDF graph, and its surrounding statements. Resources implement all the functionality of an RDF::Graph. You can manipulate them by adding or deleting statements, query, serialize, and load arbitrary RDF.
class Thing < ActiveTriples::Resource
configure :type => RDF::OWL.Thing, :base_uri => 'http://example.org/things#'
property :title, :predicate => RDF::DC.title
property :description, :predicate => RDF::DC.description
end
obj = Thing.new('123')
obj.title = 'Resource'
obj.description = 'A resource.'
obj.dump :ntriples # => "<http://example.org/things#123> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Thing> .\n<http://example.org/things#123> <http://purl.org/dc/terms/title> \"Resource\" .\n<http://example.org/things#123> <http://purl.org/dc/terms/description> \"A resource.\" .\n"
URI and bnode values are built out as Resources when accessed, and a model class can be configured on individual properties.
Thing.property :creator, :predicate => RDF::DC.creator, :class_name => 'Person'
class Person < ActiveTriples::Resource
configure :type => RDF::FOAF.Person, :base_uri => 'http://example.org/people#'
property :name, :predicate => RDF::FOAF.name
end
obj_2 = Thing.new('2')
obj_2.creator = Person.new
obj_2.creator
# => [#<Person:0x3fbe84ac9234(default)>]
obj_2.creator.first.name = 'Herman Melville'
obj_2.dump :ntriples # => "<http://example.org/things#2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Thing> .\n<http://example.org/things#2> <http://purl.org/dc/terms/creator> _:g70263220218800 .\n_:g70263220218800 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .\n_:g70263220218800 <http://xmlns.com/foaf/0.1/name> \"Herman Melville\" .\n"
Open Model
A Resource lets you handle data as a graph, independent of whether it is defined in the model. This is important for working in a Linked Data context, where you will want access to data you may not have known about when your models were written.
= Thing.new
<< RDF::Statement(, RDF::DC.relation, obj)
<< RDF::Statement(, RDF::DC.subject, 'ActiveTriples')
.query(:subject => , :predicate => RDF::DC.relation).each_statement {|s,p,o| puts o}
# => http://example.org/things#123
.query(:subject => subject, :predicate => RDF::DC.relation).each_statement {|s,p,o| puts o}
# => http://example.org/things#123
Any operation you can run against an RDF::Graph works with Resources, too. Or you can use generic setters and getters with URI predicates:
.set_value(RDF::DC.relation, obj)
.set_value(RDF::DC.subject, 'ActiveTriples')
.get_values(RDF::DC.relation) # => [#<Thing:0x3f949c6a2294(default)>]
.get_values(RDF::DC.subject) # => ["ActiveTriples"]
Some convienience methods provide support for handling data from web sources:
fetch
loads data from the Resource's #rdf_subject URIrdf_label
queries across common (& configured) label fields and returning the best match
require 'linkeddata' # to support various serializations
osu = ActiveTriples::Resource.new 'http://dbpedia.org/resource/Oregon_State_University'
osu.fetch
osu.rdf_label => => ["Oregon State University", "Oregon State University", "Université d'État de l'Oregon", "Oregon State University", "Oregon State University", "オレゴン州立大学", "Universidad Estatal de Oregón", "Oregon State University", "俄勒岡州立大學", "Universidade do Estado do Oregon"]
Typed Data
Typed literals are handled natively through Ruby types and RDF::Literal. There is no need to register a specific type for a property, simply pass the setter the appropriate typed data. See the examples in the RDF::Literal documentation for futher information about supported datatypes.
Thing.property :date, :predicate => RDF::DC.date
my_thing = Thing.new
my_thing.date = Date.today
puts my_thing.dump :ntriples
# _:g70072864570340 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Thing> .
# _:g70072864570340 <http://purl.org/dc/terms/date> "2014-06-19Z"^^<http://www.w3.org/2001/XMLSchema#date> .
Data is cast back to the appropriate class when it is accessed.
my_thing.date
# => [Thu, 19 Jun 2014]
Note that you can mix types on a single property.
my_thing.date << DateTime.now
my_thing.date << "circa 2014"
my_thing.date
# => [Thu, 19 Jun 2014, Thu, 19 Jun 2014 11:39:21 -0700, "circa 2014"]
puts my_thing.dump :ntriples
# _:g70072864570340 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Thing> .
# _:g70072864570340 <http://purl.org/dc/terms/date> "2014-06-19Z"^^<http://www.w3.org/2001/XMLSchema#date> .
# _:g70072864570340 <http://purl.org/dc/terms/date> "2014-06-19T11:39:21-07:00"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
# _:g70072864570340 <http://purl.org/dc/terms/date> "circa 2014" .
Repositories and Persistence
Resources can persist to various databases and triplestores though integration with RDF::Repository.
# Registers in-memory repositories. Other implementations of
# RDF::Repository support persistence to (e.g.) triplestores & NoSQL
# databases.
ActiveTriples::Repositories.add_repository :default, RDF::Repository.new
ActiveTriples::Repositories.add_repository :people, RDF::Repository.new
class Person < ActiveTriples::Resource
configure :type => RDF::FOAF.Person, :base_uri => 'http://example.org/people#', :repository => :people
property :name, :predicate => RDF::FOAF.name
end
class Thing < ActiveTriples::Resource
configure :type => RDF::OWL.Thing, :base_uri => 'http://example.org/things#', :repository => :default
property :title, :predicate => RDF::DC.title
property :description, :predicate => RDF::DC.description
property :creator, :predicate => RDF::DC.creator, :class_name => 'Person'
end
t = Thing.new('1')
t.title = 'A Thing'
t.creator = Person.new('1')
t.persisted? # => false
t.creator.first.name = 'Tove'
t.persist!
ActiveTriples::Repositories.repositories[:default].dump :ntriples
# => "<http://example.org/things#1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2002/07/owl#Thing> .\n<http://example.org/things#1> <http://purl.org/dc/terms/title> \"A Thing\" .\n<http://example.org/things#1> <http://purl.org/dc/terms/creator> <http://example.org/people#1> .\n"
t.creator.first.persisted? # => false
t.creator.first.persist!
ActiveTriples::Repositories.repositories[:people].dump :ntriples
# => "<http://example.org/people#1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .\n<http://example.org/people#1> <http://xmlns.com/foaf/0.1/name> \"Tove\" .\n"
Contributing
Please observe the following guidelines:
- Do your work in a feature branch based on
master
and rebase before submitting a pull request. - Write tests for your contributions.
- Document every method you add using YARD annotations. (Note: Annotations are sparse in the existing codebase, help us fix that!)
- Organize your commits into logical units.
- Don't leave trailing whitespace (i.e. run
git diff --check
before committing). - Use well formed commit messages.