Replicate ReQL API in your own classes posted on 04 February 2015

ReQL is embedded in the host language. If you use JavaScript, you do not have to concatenate and escape SQL strings, or build JSON objects with some special keys; your queries are plain JavaScript.

For example with rethinkdbdash, updating all the users that are at least 18 years old with a field isAdult set to true (in one round trip), can be written like this:

var promise = r.table('users').filter(function(user) {
  return user('age').gt(18)
}).update({ isAdult: true }).run();

RethinkDB ships with a data explorer where you can get test your queries. Because ReQL is composed of chainable commands, the data explorer can provide you with suggestions and auto-completion, making learning ReQL easy and fun.

Replicate the API

But this is not the only advantage of having an embedded chainable language; you can import the query language in your own classes by just copying the commands. This is what thinky (a Node.js ORM) does and this has multiple benefits:

  • It is easy to do and require very little work.
  • Because the API is the same, the learning curve is a Heaviside step.

This article explains how to replicate ReQL API using rethinkdbdash. The same operation can be done with the official driver or in another flexible language like Python, Ruby etc. The only thing to know about rethinkdbdash, is that all commands return an instance of the class Term, and that Term implements all the methods like filter, get, update etc.

Suppose that you have a class User.

function User(data) {
  this.id = data.id;
  this.name = data.name;
  this.email = data.email;
  this.ecp = data.ecp || null; // id of the emergency contact person
};

You can import all the methods in User by looping over all the keys in Term.

var r = require('rethinkdbdash')();
var Term = r.expr(1).__proto__;

for(var key in Term) {
  (function(key) { // this immediately invoked function is required
    User.prototype[key] = function() {
      return r.table('users')[key].apply(table, arguments);
    }
  })(key)l
}

Now you can write:

// Set the name of the user with id 1
Users.get(1).update({name: "Michel"}).run().then(...).error(...);

// Set all the users as "not verified"
Users.update({verified: false}).run().then(...).error(...);

// Perform a simple join to retrieve the emergency contact person
Users.get(1).merge(function(user) {
  return { ecpPerson: Users.get(user.ecp) }
}).run().then(...).error(...);

So if thinky is too cumbersome for your project (if you do not need relations, hooks etc.) you can easily replicate the same API.

Define your own ReQL methods

You can also define methods on Users by simply adding them in Users.prototype, but you can also create your own methods on the queries by wrapping the ReQL queries:

var r = require('rethinkdbdash')();
var Term = r.expr(1).__proto__;

function Query(query) {
  this._query = query; // an instance of Term
};

for(var key in Term) {
  (function(key) {
    if (key === "run") {
      Query.prototype[key] = function() {
        // We want to return the promise returned by the ReQL query here
        return this._query.run.apply(this._query, arguments));
      }
    } else {
      Query.prototype[key] = function() {
        return new Query(this._query[key].apply(this._query, arguments));
      }
    }

    User.prototype[key] = function() {
      return r.table('users')[key].apply(table, arguments)
    }
  })(key);
}

A few notes about this code:

  • You can copy all the methods on Query.prototype except run since for this method you want to return the promise itself.
  • Each method returns a new Query object to enable forking:
var Adults = Users.filter(function(user) { return user("age").gt(18) });
Adults.update({isAdult: true}).run().then(...).error(...);
Adults.filter({location: "US"}).run().then(...).error(...);

This is it! You can now define your own methods:

Query.prototype.isAdult = function() {
  return new Query(this._query.filter(function(user) {
    return user("age").gt(18)
  }));
};
User.prototype.isAdult = function() {
  var table = r.table('users');
  return new Query(table).isAdult();
};

// Retrieve all the adults
var promise = Users.filter({location: "UK"}).isAdult().run();

Questions? Feedback? Shoot me a mail at [email protected] or ping me on Twitter @neumino