A Close Look at the WorldCat Discovery Ruby Gem

This is the third installment in our deep dive series on the WorldCat Discovery API. This week we will be taking a close look at some of the demo code we have written ourselves to exercise the API throughout its development process. We have decided to share our work through our OCLC Developer Network Github.com account.

Code Library Goals

Our engineering staff at OCLC have been hard at work creating the WorldCat Discovery API. However, when developing any application you want robust amounts of usage that tries to exercise the application in expected and unexpected ways. Fortunately, on the Developer Network team we have a lot of experience working with our own APIs and took the opportunity to put together a few code libraries and demonstration applications to show off some of the capabilities of the API. In particular, we wanted to create:

  • Reference implementations in a couple of common programming languages: we chose PHP and Ruby as they have wide usage throughout the library development community through VUFind and Blackligh/Hydra, respectively;
  • Foundation code that could be used by real-world API clients: feel free to clone the code or fork it for use in your application;
  • A demonstration of how to work with RDF parsers: our investigations into working with Linked Data and RDF have shown that a few excellent RDF libraries are making it easy to put our data to work.

Object Entity Mapping

When writing application code, working within the object-oriented paradigm works extremely well. Objects provide encapsulation of data and functionality so that you can pass around discreet pieces of information from one part of an application to another, such as when utilizing a web application framework that takes advantage of the Model/View/Controller (MVC) pattern.

Our goal is that clients will use WorldCat entities as a data layer to help deliver services to users of bibliographic information. This means the WorldCat Discovery API will serve as the data model and we focused the design of our code libraries on creating bibliographic resource objects. With this pattern driving our design, we wanted to enable clients of the libraries to spend as little code as possible parsing API responses in favor of instantiating objects and working with them directly. Consequently, after configuring the required API key, the client should be able to write code to construct a bibliographic resource object according to typical object oriented patterns:

require 'worldcat/discovery'

wskey = OCLC::Auth::WSKey.new('api-key', 'api-key-secret', :services => ['WorldCatDiscoveryAPI'])
WorldCat::Discovery.configure(wskey, 128807, 128807)

bib = WorldCat::Discovery::Bib.find(255034622)

bib.name           # => "Programming Ruby."
bib.id             # => #<RDF::URI:0x3feb33057c70 URI:http://www.worldcat.org/oclc/255034622>
bib.id.to_s        # => "http://www.worldcat.org/oclc/255034622"
bib.type           # => #<RDF::URI:0x3feb3300fe84 URI:http://schema.org/Book>
bib.type.to_s      # => "http://schema.org/Book" 
bib.author         # => <WorldCat::Discovery::Person:70279384406040 @subject: http://viaf.org/viaf/107579098> 
bib.author.name    # => "Thomas, David."
bib.author.id.to_s # => "http://viaf.org/viaf/107579098"
bib.contributors.map{|contributor| contributor.name} # => [" Fowler, Chad.", "Hunt, Andrew."]

So what does this code do? After requiring this library, we simply set up the API key object and configure the gem to use it to request and manage active access tokens. We are using our own OCLC::Auth Ruby gem.

After that we can just construct a bibliographic resource object by passing an OCLC number to its constructor. Just as an Object Relational Mapping (ORM) system would then fetch data from a relational database, this code library makes the HTTP request to its data source, the Discovery API. Now we have instantiated an instance of the WorldCat::Discovery::Bib class and can call the getter methods for various data members.

Object/RDF Mapping

As we discussed last time, Resource Description Framework, or RDF, data is based on a set of simple data assertions. For example, we could make the following useful claims about the thing identified by the URI http://www.worldcat.org/oclc/255034622:

  • It is a book,
  • It’s name is /Programming Ruby/,
  • It’s creator is a person identified by the URI http://viaf.org/viaf/107579098 and has the name David Thomas.

In RDF, you would produce data that look like this

<http://www.worldcat.org/oclc/255034622> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Book> .
<http://www.worldcat.org/oclc/255034622> <http://schema.org/name> "Programming Ruby." .
<http://www.worldcat.org/oclc/255034622> <http://schema.org/creator> <http://viaf.org/viaf/107579098> .
<http://viaf.org/viaf/107579098> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
<http://viaf.org/viaf/107579098> <http://schema.org/name> "Thomas, David." .

Since there are good RDF parsers in every language, we want to load this small graph of data into into one of them and end up with code like the example above. We are working with Ruby and using the RDF.rb core library to work with the graph-based RDF data. To map this data into Bib and Person objects, the WorldCat::Discovery gem is utilizing the Spira extension to RDF.rb.

Spira is a framework for using the information in RDF.rb repositories as model objects. It gives you the ability to work in a resource-oriented way without losing access to statement-oriented nature of linked data, if you so choose. It can be used either to access existing RDF data in a resource-oriented way, or to create a new store of RDF data based on simple defaults.
https://github.com/ruby-rdf/spira

Using this small sample data above, we can write a class that converts the RDF data into code objects that are easy to work with:

require 'spira'
require 'rdf/ntriples'

# Create a few constants for the URIs used in the data
NAME    = RDF::URI.new("http://schema.org/name")
CREATOR = RDF::URI.new("http://schema.org/creator")
BOOK    = RDF::URI.new("http://schema.org/Book")

# A class that represents a person.
class Person < Spira::Base

  property :name, :predicate => NAME, :type => XSD.string

end

# A class that represents a bibliographic resource, such as a book.
class Bib < Spira::Base
  
  property :name, :predicate => NAME, :type => XSD.string
  property :creator, :predicate => CREATOR, :type => 'Person'

end

# We'll just hard code some data into this example script.
data = %q{
<http://www.worldcat.org/oclc/255034622> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Book> .
<http://www.worldcat.org/oclc/255034622> <http://schema.org/name> "Programming Ruby." .
<http://www.worldcat.org/oclc/255034622> <http://schema.org/creator> <http://viaf.org/viaf/107579098> .
<http://viaf.org/viaf/107579098> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
<http://viaf.org/viaf/107579098> <http://schema.org/name> "Thomas, David." .
}

# Load the data into a repository in memory
Spira.repository = RDF::Repository.new.from_ntriples(data)

# Query the repository for things that have the type http://schema.org/Book
# Get the first RDF::Statemnt returned from querying the repository.
# Instantiate an object of class Bib using the subject of the RDF::Statement
book_results = Spira.repository.query(:predicate => RDF.type, :object => BOOK)
first_book = book_results.first
bib = first_book.subject.as(Bib)

puts bib.name         # Prints "Programming Ruby."
puts bib.creator.name # Prints "Thomas, David."

This script is essentially a microcosm of the WorldCat::Discovery gem. It works by taking advantage of the Object/RDF Mapping features of the Spira library and the RDF concept of concise bounded description (CBD). An overly simplified way of looking at CBD is to gather all the statements in an RDF graph that share the same subject. So in our working example, you could divide the graph into two distinct parts:

<http://www.worldcat.org/oclc/255034622> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Book> .
<http://www.worldcat.org/oclc/255034622> <http://schema.org/name> "Programming Ruby." .
<http://www.worldcat.org/oclc/255034622> <http://schema.org/creator> <http://viaf.org/viaf/107579098> .

and

<http://viaf.org/viaf/107579098> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
<http://viaf.org/viaf/107579098> <http://schema.org/name> "Thomas, David." .

Divided into these parts, you have the data required to instantiate each of the bibliographic resource and person objects. What’s more, the way that the schema:creator property of the bibliographic resource references the subject/ID of the person maps well to the way the property of one object can be a pointer to another object in memory.

The Spira library uses the following syntax to instantiate a Bib object:

bib = first_book.subject.as(Bib)

This code roughly translates to, “Using the subject first_bib RDF statement, load a Bib object by gathering all the other statements that share the same subject.”

By defining the two classes, Bib and Person, as sub-classes of the Spira::Base class, we can then take advantage of the built in getter/setter function property for mapping RDF predicates to attribute accessors. For example, the line:

  property :name, :predicate => NAME, :type => XSD.string

within the Bib class enables us to map the property http://schema.org/name to a name method on the class. Which is why, once constructed, the instance can simply use the simple dot-notation to work with the data in an object-oriented fashion.

To see the broader use of the Spira repository in the WorldCat::Discovery gem, take a look at the WorldCat::Discovery::Bib class. By looking at its property list and the static methods for instantiating individual objects, search and find, you can see how all the pieces fit together.

Bonus Material

If you want to see an example of the code library put to work, see the WorldCat Discovery API Sinatra Demo. Using the API and WorldCat::Discovery gem, you can see how you might begin building a total discovery interface on top of the WorldCat Discovery API.

Search Example Powered by the WorldCat Discovery API

We hope you have enjoyed this series of deep dives into the WorldCat Discovery API. This API is currently available as a beta for a select number of libraries using WorldCat Discovery Services. Interested in participating in the beta? Contact us today.

  • Karen Coombs

    Karen Coombs

    Senior Product Analyst