Redleaf Ruby Interface
This is a page to capture ideas for how to make a idiomatic Ruby interface to RDF. We're probably going to incorporate some of the great ideas in Redleaf's predecessors and contemporaries as well as ideas from other smart people posted to various blogs and mailing lists.
Inspiration
Here are a few of the places we'll be looking:
Example Usage
Graph
This example is a translation of a Python Rdflib example:
require 'redleaf' FOAF = Redleaf::Namespace( "http://xmlns.com/foaf/0.1/" ) graph = Redleaf::Graph.new graph.load( "http://bigasterisk.com/foaf.rdf" ) graph.load( "http://www.w3.org/People/Berners-Lee/card.rdf" ) graph.load( "http://danbri.livejournal.com/data/foaf" ) # Create foaf:name triples for each foaf:member_name (the attribute LiveJournal uses for the # member's full name) graph[ nil, FOAF[:member_name], nil ].each do |stmt| graph << [ stmt.subject, FOAF[:name], stmt.object ] end sparql = %( SELECT ?aname ?bname WHERE { ?a foaf:knows ?b . ?a foaf:name ?aname . ?b foaf:name ?bname . } ) graph.query( sparql, :foaf => FOAF ) do |row| puts "%s knows %s" % [ row[:aname], row[:bname] ] end # Output: # Timothy Berners-Lee knows Edd Dumbill # Timothy Berners-Lee knows Jennifer Golbeck # Timothy Berners-Lee knows Nicholas Gibbins # Timothy Berners-Lee knows Nigel Shadbolt # Dan Brickley knows binzac # Timothy Berners-Lee knows Eric Miller # Drew Perttula knows David McClosky # Timothy Berners-Lee knows Dan Connolly # ...
Interacting with a Store
require 'redleaf' require 'redleaf/store' # By default a Graph just uses a Memory-backed Store graph = Redleaf::Graph.new graph.store # ==> #<Redleaf::MemoryStore:0x5f97e0> # Migrate the graph to a BDB-backed Hashes store graph.store = Redleaf::BDBStore.new( 'db01', :dir => '/path/to/dbenv', :new => true ) # Load a graph from a store (?) store = Redleaf::BDBStore.new( 'db01', :dir => '/path/to/dbenv' ) graph = store.graph
Interacting with Parsers
require 'redleaf' require 'redleaf/parser' uri = URI.parse( 'http://www.w3.org/2000/10/rdf-tests/rdfcore/ntriples/test.nt' ) # By default, just try to guess what we're parsing and parse it: statements = Redleaf::Parser.parse( uri ) # Or get the guessed parser class, instantiate it, and parse the URI explicitly parser_class = Redleaf::Parser.guess_type( uri ) # => Redleaf::NTriplesParser parser = parser_class.new parser.parse( uri ) do |stmt| puts stmt end # Or don't guess if you know what's being parsed: parser = Redleaf::NTriplesParser.new statements = parser.parse( uri ) # Handle parse errors: parser = Redleaf::RDFXMLParser.new statements = begin parser.parse( uri ) rescue Redleaf::ParseError => err $stderr.puts "Parse error while parsing '%s': %s" % [ uri, err.message ] [] end
Query Interface
SELECT-type queries (bindings results):
# Select the titles of albums by Metallica created after 1980: sparql = <<-END_OF_QUERY PREFIX mo: <http://purl.org/ontology/mo/> PREFIX dcterms: <http://purl.org/dc/terms/> SELECT ?title WHERE { ?album a mo:Record; dc:creator <http://zitgist.com/music/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab>; dcterms:created ?creation_date; dc:title ?title. FILTER ( xsd:dateTime(?creation_date) > "1981-01-01 00:00:00"^^xsd:dateTime ) . } END_OF_QUERY # Run a query against the graph result = graph.query( sparql ) # => #<Redleaf::BindingsQueryResult:0x52a008> # See what variable bindings the result has names = result.bindings # => [ :title ] result.first.title # => "...And Justice For All"
CONSTRUCT and DESCRIBE-type queries (graph results):
graph << [ :_a, FOAF[:name], "Alice" ] << [ :_a, FOAF[:mbox], URI.parse('mailto:alice@example.org')] sparql = <<-END_OF_QUERY PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX vcard: <http://www.w3.org/2001/vcard-rdf/3.0#> CONSTRUCT { <http://example.org/person#Alice> vcard:FN ?name } WHERE { ?x foaf:name ?name } END_OF_QUERY result = graph.query( sparql ) # => #<Redleaf::GraphQueryResult:0x5272a4> # Not sure how to use the results: make it a delegator for a new Graph object? Iterator for # Statement objects?
ASK-type queries (boolean results):
Future Redleaf::Graph Features
These are a list of features that Redland's model class has that Redleaf::Graph is missing at the moment, mostly because I don't feel like I understand them well enough to do the translation of the functionality into Ruby idiom.
Here's what I'm thinking currently:
Transactions
graph = Redleaf::Graph.new URLS = [...] # Either load all of the URLs or none of them graph.transaction do URLS.each do |url| graph.load( url ) end end
Contexts
Using the ideas in David's notes on the subject, something like:
graph = Redleaf::Graph.new dajobe_foaf = URI.parse( 'http://www.dajobe.org/foaf.rdf' ) # Get a delegating proxy (Redleaf::Graph::Context) for the receiving Redleaf::Graph graph_context = graph.context( dajobe_foaf ) # Append some new nodes in the specified context graph.append_with_context( dajobe_foaf, *statements ) # Or via a proxy, whose #append calls its graph's #append_with_context: graph.context( dajobe_foaf ).append( *statements ) # Also via a proxy, whose #append calls its graph's #append_with_context: graph[ dajobe_foaf ].append( *statements )
Further Reading
- W3C site (of course)
- Burningbird's Practical RDF Book
- Paul Stadig's State of RDF Support in Ruby has some good ideas about design
- Dan Brickley's RDF in Ruby Revisited
- Summary of Recent Discussions about an Application Programming Interface for RDF
- Dave Beckett's Resource Description Framework (RDF) Resource Guide is a treasure-trove of useful annotated links
- Bootstrapping RDF applications with Redland — David Beckett's excellent article (presentation?) for "XTech 2005"
