An ActiveModel-like interface for RDF data
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Build Status Coverage Status Gem Version

An ActiveModel-like interface for RDF data. Models graphs as RDFSources with property/attribute configuration, accessors, and other methods to support Linked Data in a Ruby/Rails enviornment. See RDF Concepts and Abstract Syntax for an informal definition of an RDF Source.

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.


Add gem "active-triples" to your Gemfile and run bundle.

Or install manually with gem install active-triples.

Defining RDFSource Models

The core module of ActiveTriples is ActiveTriples::RDFSource. You can use this module as a mixin to create ActiveModel-like classes that represent an RDF resource as a stateful entity represented by an RDF::Graph. RDFSource implements the RDF::Resource interface, as well as RDF::Queryable, RDF::Enumerable, and RDF::Mutable. This means you can manipulate them by adding or deleting statements, query, serialize, and load arbitrary RDF.

require 'rdf/vocab'

class Thing
  include  ActiveTriples::RDFSource
  configure type: RDF::OWL.Thing, base_uri: ''
  property :title,       predicate: RDF::Vocab::DC.title
  property :description, predicate: RDF::Vocab::DC.description

obj             ='123')
obj.title       = 'Resource'
obj.description = 'A resource.'

obj.dump :ntriples # => "<> <> <> .\n<> <> \"Resource\" .\n<> <> \"A resource.\" .\n"

URI and bnode values are built out as generic Resources when accessed. A more specific model class can be configured on individual properties. :creator, predicate: RDF::Vocab::DC.creator, class_name: 'Person'

class Person
  include  ActiveTriples::RDFSource
  configure type:     RDF::Vocab::FOAF.Person,
            base_uri: ''
  property :name, predicate:

obj_2         ='2')
obj_2.creator =

# => [#<Person:0x3fbe84ac9234(default)>] = 'Herman Melville'

obj_2.dump :ntriples # => "_:g47361345336040 <> \"Herman Melville\" .\n_:g47361345336040 <> <> .\n<> <> <> .\n<> <> _:g47361345336040 .\n"

Open Model

An RDFSource 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.

related =

related << RDF::Statement(related, RDF::Vocab::DC.relation, obj)
related << RDF::Statement(related, RDF::Vocab::DC.subject, 'ActiveTriples')

related.query(subject:   related,
              predicate: RDF::Vocab::DC.relation).each_statement do |s,p,o|
  puts o
# =>

related.query(subject:   related,
              predicate: RDF::Vocab::DC.subject).each_statement do |s,p,o|
  puts o
# => 'ActiveTriples'

Any operation you can run against an RDF::Graph works with RDFSources, too. Or you can use generic setters and getters with URI predicates:

related.set_value(RDF::Vocab::DC.relation, obj)
related.set_value(RDF::Vocab::DC.subject,  'ActiveTriples')

# => [#<Thing:0x3f949c6a2294(default)>]

# => ["ActiveTriples"]

Some convienience methods provide support for handling data from web sources:

  • fetch loads data from the RDFSource's #rdf_subject URI
  • rdf_label queries across common (& configured) label fields and returning the best match
require 'linkeddata' # to support various serializations

uri = ''

osu = uri

# => ["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. :date, predicate:

my_thing      = =

puts my_thing.dump :ntriples
# _:g70072864570340 <> <> .
# _:g70072864570340 <> "2014-06-19Z"^^<> .

Data is cast back to the appropriate class when it is accessed.
# => [Thu, 19 Jun 2014]

Note that you can mix types on a single property. << << "circa 2014"
# => [Thu, 19 Jun 2014, Thu, 19 Jun 2014 11:39:21 -0700, "circa 2014"]

puts my_thing.dump :ntriples
# _:g70072864570340 <> <> .
# _:g70072864570340 <> "2014-06-19Z"^^<> .
# _:g70072864570340 <> "2014-06-19T11:39:21-07:00"^^<> .
# _:g70072864570340 <> "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,
ActiveTriples::Repositories.add_repository :people,

class Person
  include  ActiveTriples::RDFSource
  configure type:       RDF::Vocab::FOAF.Person,
            base_uri:   '',
            repository: :people
  property :name, predicate:

class Thing
  include  ActiveTriples::RDFSource

  configure type:       RDF::OWL.Thing,
            base_uri:   '',
            repository: :default
  property :title,       predicate: RDF::Vocab::DC.title
  property :description, predicate: RDF::Vocab::DC.description
  property :creator,     predicate: RDF::Vocab::DC.creator, class_name: 'Person'

t         ='1')
t.title   = 'A Thing'
t.creator ='1')

t.persisted? # => false

ActiveTriples::Repositories.repositories[:default].dump :ntriples
# => "" = 'Tove'

puts ActiveTriples::Repositories.repositories[:default].dump :ntriples
# <> <> "A Thing" .
# <> <> <> .
# <> <> <> .
# <> <> <> .
# <> <> "Tove" .


Please observe the following guidelines:

  • Do your work in a feature branch based on develop 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.