I’m starting a new project with Node.JS and Cassandra. Coming from Rails and Mongoid, I was looking for a solid ORM / ODM and discovered Apollo-Cassandra. To be honest, the Node.JS + Cassandra ORM offerings are very limited. I ended up choosing Apollo because it is written on top of the data-stax/nodejs-driver, an officially supported javascript driver for Cassandra, and because the GitHub seemed fairly active.

Aside:

An object-relational-mapping abstracts writing SQL queries by allowing you to call methods on an object, to simplify updating records. My last project used Amazon’s DynamoDB without an ORM and writing database statements and managing model state was not fun or easily maintainable.

Apollo currently does a good job at handling the basics of model management. It allows you to save, create, delete, and update a record; it references a schema for each table; it tracks changes; and it even handles basic validations.

The most immediate features it seems to be lacking are filters / callbacks, and also the ability to override setters and getters of properties. Custom setters and getters are a great place to enforce data structure and DRY your code. Some examples and then my solution are shown below.

Example use-case for DRYing code:

model.prototype.setEmail = function(x) { if (_.isString(x)) { this.email = x.toLowerCase(); } else { this.email = x; } }

My solution for creating custom setters and getters. It creates get___() and set___(value) methods, like setEmail() and getEmail(), for every field defined in your schema. In your code, you should call these set and get methods instead of modifying the model’s properties directly. This gives you the opportunity to override the default getter or setter later on.

To handle mass-assignment, when you create a new record and update an existing record from an object hash, I wrote get(‘field’) and set({field:value}) methods. The set() method can also be called with a single field and value: set('email’, 'someemail@domain.com’).

var _ = require('underscore'); var _s = require('underscore.string'); function fieldFunctionNameWithPrefix(prefix, field) { return prefix + _s(field).camelize().capitalize().value(); } var exports = { extend: function(model, schema) { // define setters and getters _.each(_.keys(schema.fields), function(field) { model.prototype[fieldFunctionNameWithPrefix("set", field)] = function(x) { this[field] = x; } model.prototype[fieldFunctionNameWithPrefix("get", field)] = function() { return this[field]; } }); // define set('field', value) and set({ field: value }) model.prototype.set = function(arg1, arg2) { if (_.isString(arg1)) { this[fieldFunctionNameWithPrefix("set", arg1)](arg2); } else if (_.isObject(arg1)) { var self = this; _.mapObject(arg1, function(value, field) { if (!_.isUndefined(schema.fields[field])) { self[fieldFunctionNameWithPrefix("set", field)](value); } }); } else { throw "Invalid arguments." } } // define get('field') model.prototype.get = function(field) { if (!_.isUndefined(schema.fields[field])) { return this[fieldFunctionNameWithPrefix("get", field)](); } else { throw "Invalid field." } } } } module.exports = exports;

Here’s an example of how you’d use this extension:

var schema = { fields: { // timestamps created_at: { type: 'timestamp', default: { $db_function: 'dateOf(now())' } }, // uuid id: { type: 'uuid', default: { $db_function: 'uuid()' } }, // data name: { type: 'text', rule: Validations.String.Present('name') }, email: { type: 'text', rule: Validations.String.Present('email') }, phone: { type: 'text', rule: Validations.String.Present('phone') }, location: 'text', biography: 'text', picture_url: 'text', websites: 'text', slug: 'text', // password password_hash: { type: 'text', rule: Validations.String.Present('password hash') } }, key: ['email'] }; // ========= // = Model = // ========= var model = apollo.add_model('user', schema); Extension.extend(model, schema); // ============= // = Overrides = // ============= model.prototype.setEmail = function(x) { if (_.isString(x)) { this.email = x.toLowerCase(); } else { this.email = x; } }

var u = new Models.User(); // mass assignment u.set({ name: "Alexander Wong", email: "email@example.com" }); // use custom setter, which can be overridden later u.setName("Alex Wong); // use custom getters u.getName(); u.get('name');