Using Datomic from Ruby

We are all very jazzed at Relevance about Datomic. But so far, the people working on JVM projects have had all the fun, as Datomic's peer capabilities are exposed as a JVM library.

That changed a few weeks ago when the REST API for Datomic was announced. Suddenly, we have client access to Datomic from any language we choose. We quickly came up with a Ruby client for Datomic and an edn data reader and writer for Ruby. (edn is the data format Datomic uses for input and output, and by implementing edn in Ruby, we get to use native Ruby data to talk with Datomic.

Over the last two days, I've created a sample application using Ruby and Datomic. It is a wiki which saves all its data in Datomic and can read the history of pages from there, using only a few simple queries. The really interesting part is how easily we were able to use Ruby data structures in the place of edn to make easy-to-manipulate and easy-to-read schemas and queries. Here's the DB schema in Ruby:

[{:"db/id" => id(:"db.part/db"),
  :"db/ident" => :"page/name",
  :"db/valueType" => :"db.type/string",
  :"db/cardinality" => :"db.cardinality/one",
  :"db/fulltext" => true,
  :"db/doc" => "The name of a page.",
  :"db/unique" => :"db.unique/value",
  :"db/index" => true,
  :"db.install/_attribute" => ":db.part/db"
},
{:"db/id" => id(:"db.part/db"),
 :"db/ident" => :"page/text",
 :"db/valueType" => :"db.type/string",
 :"db/cardinality" => :"db.cardinality/one",
 :"db/fulltext" => true,
 :"db/doc" => "The text of a page.",
 :"db.install/_attribute" => ":db.part/db"
}]

There are only a few differences between edn and this: mainly, edn keywords allow more characters than Ruby symbol literals, so we have to quote the symbols in Ruby.

Queries are even more fun:

datomic.query(dbname, [:find, ~"?id", ~"?name", ~"?text",
                       :in, ~"$db", ~"?name",
                       :where,
                         [~"$db", ~"?id", :"page/name", ~"?name"],
                         [~"$db", ~"?id", :"page/text", ~"?text"]],
                      :args => [{:"db/alias" => "example/wiki"}, name])

Given a variable name, this will pull back the DB id, name, and text for the page named that in Datomic. In this data structure, you'll notice one thing that probably isn't familiar to most Rubyists. The tilde (~) is used as a unary operator on strings to create edn symbols, which don't have an analog in Ruby. This capability is added by edn-ruby, our edn reader and writer.

To see the entire history of a page, we can write this query:

datomic.query(dbname, [:find, ~"?v", ~"?time", ~"?tx",
                       :in, ~"$cur", ~"$hist", ~"?uniq-attr", ~"?uniq-val", ~"?hist-attr",
                       :where,
                         [~"$cur",  ~"?e",  ~"?uniq-attr", ~"?uniq-val"],
                         [~"$cur",  ~"?tx", ~":db/txInstant", ~"?time"],
                         [~"$hist", ~"?e",  ~"?hist-attr", ~"?v", ~"?tx", true]],
                      :args => [
                        {:"db/alias" => dbalias},
                        {:"db/alias" => dbalias, :history => true},
                        :"page/name",
                        name,
                        :"page/text"])

Is this particularly Ruby-ish right now? No, not yet. Is it very cool? Yes, definitely. Datomic queries are built out of data structures: arrays, hashes, and other primitive data types, which allows us to easily use map, inject, and other Enumerable methods with them. A significant source of power and concision in queries comes from the use of not one, but two distinct data types for identifiers: symbols and keywords. Most languages cannot come close to this expressiveness, but Ruby handles it with ease. Ruby symbols can act as both identifier types, with the sly introduction of a unary operator "~" to distinguish them.

We have just begun to see what kind of amazing applications we can build with Datomic. Follow the Ruby Datomic client and the sample Ruby/Datomic wiki on Github to see what we come up with next.