Tripod
ActiveModel-style Ruby ORM for RDF Linked Data. Works with SPARQL 1.1 HTTP endpoints.
- ActiveModel-compliant interface.
- Inspired by Durran Jordan's Mongoid ORM for MongoDB, and Ben Lavender's RDF ORM, Spira.
- Uses Ruby-RDF to manage the data internally.
Quick start, for using in a rails app.
-
Add it to your Gemfile and bundle
gem tripod $ bundle
-
Configure it (in application.rb, or development.rb/production.rb/test.rb)
# (values shown are the defaults) Tripod.configure do |config| config.update_endpoint = 'http://127.0.0.1:3030/tripod/update' config.query_endpoint = 'http://127.0.0.1:3030/tripod/sparql' config.timeout_seconds = 30 end
-
Include it in your model classes.
class Person include Tripod::Resource # these are the default rdf-type and graph for resources of this class rdf_type 'http://example.com/person' graph_uri 'http://example.com/people' field :name, 'http://example.com/name' field :knows, 'http://example.com/knows', :multivalued => true, :is_uri => true field :aliases, 'http://example.com/alias', :multivalued => true field :age, 'http://example.com/age', :datatype => RDF::XSD.integer field :important_dates, 'http://example.com/importantdates', :datatype => RDF::XSD.date, :multivalued => true end # Note: Active Model validations are supported
-
Use it
uri = 'http://example.com/ric' p = Person.new(uri) p.name = 'Ric' p.age = 31 p.aliases = ['Rich', 'Richard'] p.important_dates = [Date.new(2011,1,1)] p.save! people = Person.all.resources #=> returns all people as an array ric = Person.find('http://example.com/ric') #=> returns a single Person object.
Note:
Tripod doesn't supply a database. You need to install one. I recommend Fuseki, which runs on port 3030 by default.
Some Other interesting features
## Eager Loading
asa = Person.find('http://example.com/asa')
ric = Person.find('http://example.com/ric')
ric.knows = asa.uri
ric.eager_load_predicate_triples! #does a big DESCRIBE statement behind the scenes
knows = ric.get_related_resource('http://example.com/knows', Resource)
knows.label # this won't cause another database lookup
ric.eager_load_object_triples! #does a big DESCRIBE statement behind the scenes
asa = ric.get_related_resource('http://example.com/asa', Person) # returns a fully hydrated Person object for asa, without an extra lookup
## Defining a graph at instantiation-time
class Resource
include Tripod::Resource
field :label, RDF::RDFS.label
# notice also that you don't need to supply an rdf type or graph here!
end
r = Resource.new('http://example.com/foo', 'http://example.com/mygraph')
r.label = "example"
r.save
# Note: Tripod assumes you want to store all resources in named graphs.
# So if you don't supply a graph at any point (i.e. class or instance level),
# you will get an error when you try to persist the resource.
Reading and writing arbitrary predicates
r.write_predicate(RDF.type, 'http://example.com/myresource/type')
r.read_predicate(RDF.type) #=> [RDF::URI.new("http://example.com/myresource/type")]
Finders and criteria
# A Tripod::Criteria object defines a set of constraints for a SPARQL query.
# It doesn't actually do anything against the DB until you run resources, first, or count on it.
# (from Tripod::CriteriaExecution)
Person.all #=> returns a Tripod::Criteria object which selects all resources of rdf_type http://example.com/person, in the http://example.com/people graph
Resource.all #=> returns a criteria object to return resources in the database (as no rdf_type or graph_uri specified at class level)
Person.all.resources #=> returns all the actual resources for the criteria object, as an array-like object
Person.all.resources(:return_graph => false) #=> returns the actual resources, but without returning the graph_uri in the select (helps avoid pagination issues). Note: doesn't set the graph uri on the instantiated resources.
Person.first #=> returns the first person (by crafting a sparql query under the covers that only returns 1 result)
Person.first(:return_graph => false) # as with resources, doesn't return / set the graph_uri.
Person.count #=> returns the count of all people (by crafting a count query under the covers that only returns a count)
# note that you need to use ?uri as the variable for the subject.
Person.where("?uri <http://example.com/name> 'Joe'") #=> returns a Tripod::Criteria object
Resource.graph("http://example.com/mygraph") #=> Retruns a criteria object with a graph restriction (note: if graph_uri set on the class, it will default to using this)
Resource.find_by_sparql('SELECT ?uri ?graph WHERE { GRAPH ?graph { ?uri ?p ?o } }') #=> allows arbitrary sparql. Again, use ?uri for the variable of the subjects (and ?graph for the graph).
Chainable criteria
Person.all.where("?uri <http://example.com/name> 'Ric'").where("?uri <http://example.com/knows> <http://example.com/asa>).first
Person.where("?uri <http://example.com/name> ?name").limit(1).offset(0).order("DESC(?name)")
Running tests
With a Fuseki instance ready and up, edit the config in spec/spec_helper.rb
to reflect your settings. Make sure you bundle
to pull in all dependencies before trying to run the tests.
Some tests require memcached to be set up and running. The tests that require memcached are tagged with :caching_tests => true
; do with this information what you will.
Copyright (c) 2012 Swirrl IT Limited. Released under MIT License