Browse files

Add meta variables to the database for each model (version of last mo…

…del used, idgenerator (toString) and properties (json))

Add version of the model that was used to save it to each instance
  • Loading branch information...
1 parent 67692b2 commit bbfcee85902b92e841876b91630acecddf2ea340 @maritz committed Jun 25, 2012
Showing with 297 additions and 32 deletions.
  1. +5 −1 lib/helpers.js
  2. +75 −20 lib/nohm.js
  3. +6 −5 lib/store.js
  4. +1 −1 test/featureTests.js
  5. +209 −0 test/metaTests.js
  6. +0 −3 test/testArgs.js
  7. +1 −2 test/tests.js
View
6 lib/helpers.js
@@ -121,7 +121,11 @@ Helper.getPrefix = function (defaultPrefix) {
index: prefix + ':index:',
relations: prefix + ':relations:',
relationKeys: prefix + ':relationKeys:',
- meta: prefix + ':meta:',
+ meta: {
+ version: prefix + ':meta:version:',
+ idGenerator: prefix + ':meta:idGenerator:',
+ properties: prefix + ':meta:properties:',
+ },
channel: prefix + ':channel:'
};
return obj;
View
95 lib/nohm.js
@@ -1,5 +1,6 @@
var h = require(__dirname + '/helpers');
var async = require('async');
+var crypto = require('crypto');
/**
* The Nohm object used for some general configuration and model creation.
@@ -32,11 +33,10 @@ Nohm.client = null;
/**
* Whether to store the meta values about models.
* This is used for example by the admin app.
- * Defaults to false, since it's a little faster.
+ * Defaults to true.
* @static
*/
-Nohm.meta = false; // check if this should be defaulted to true.
-Nohm.meta_saved_models = [];
+Nohm.meta = true;
/**
* Model cache
@@ -50,15 +50,31 @@ var models = {};
* @static
*/
Nohm.model = function (name, options) {
+ if ( ! name ) {
+ this.logError('When creating a new model you have to provide a name!');
+ }
+
var obj = function (id, cb) {
- this.init(name, options);
+ this.init(options);
// if this is changed, check if the factory needs to be changed as well!
if(typeof(id) !== 'undefined' && typeof(cb) === 'function') {
this.load(id, cb);
}
};
obj.prototype = new Nohm();
+
+ obj.prototype.modelName = name;
+ obj.prototype.idGenerator = options.idGenerator || 'default';
+
+ var meta = {
+ inDb: false
+ };
+ meta.properties = options.properties;
+ meta.version = _meta_version(meta.properties, obj);
+
+ obj.prototype.meta = meta;
+
// this creates a few functions for short-form like: SomeModel.load(1, function (err, props) { /* `this` is someModelInstance here */ });
var shortFormFuncs = ['load', 'find', 'save', 'sort', 'subscribe', 'subscribeOnce', 'unsubscribe'];
shortFormFuncs.forEach(function (val) {
@@ -80,6 +96,16 @@ Nohm.model = function (name, options) {
return obj;
};
+var _meta_version = function (properties, obj) {
+ var hash = crypto.createHash('sha1');
+
+ hash.update(JSON.stringify(properties));
+ hash.update(JSON.stringify(obj.prototype.modelName));
+ hash.update(obj.prototype.idGenerator.toString());
+
+ return hash.digest('hex');
+};
+
/**
* Factory to produce instances of models
*
@@ -206,28 +232,26 @@ var addMethods = function (methods) {
}
};
-Nohm.prototype.init = function (name, options) {
- if ( ! name ) {
- this.logError('When creating a new model you have to provide a name!');
- }
-
+Nohm.prototype.init = function (options) {
if (typeof(options.client) === 'undefined' && Nohm.client === null) {
- Nohm.logError('Did not find a viable redis client in Nohm or the model: '+name);
+ Nohm.logError('Did not find a viable redis client in Nohm or the model: '+this.modelName);
return false;
}
+
+ if ( ! this.meta.inDb) {
+ __updateMeta.call(this);
+ }
if (typeof(options.client) !== 'undefined') {
this.client = options.client;
}
- this.modelName = name;
- this.idGenerator = options.idGenerator || 'default';
-
this.properties = {};
this.errors = {};
// initialize the properties
if (options.hasOwnProperty('properties')) {
+
for (var p in options.properties) {
if (options.properties.hasOwnProperty(p)) {
this.properties[p] = h.$extend(true, {}, options.properties[p]); // deep copy
@@ -243,16 +267,10 @@ Nohm.prototype.init = function (name, options) {
}
this.__resetProp(p);
this.errors[p] = [];
-
- if ( Nohm.meta && ! Nohm.meta_saved_models.hasOwnProperty(this.modelName)) {
- // try saving the meta data of this model
- var metaargs = [Nohm.prefix.meta + this.modelName, p, JSON.stringify(this.properties[p])];
- this.getClient().hmset(metaargs);
- }
}
}
}
-
+
if (options.hasOwnProperty('methods')) {
addMethods.call(this, options.methods);
}
@@ -268,6 +286,43 @@ Nohm.prototype.init = function (name, options) {
this.__loaded = false;
};
+
+
+var __updateMeta = function () {
+ if ( ! Nohm.meta) {
+ return false;
+ }
+ var self = this;
+
+ var version_key = Nohm.prefix.meta.version + this.modelName;
+ var idGenerator_key = Nohm.prefix.meta.idGenerator + this.modelName;
+ var properties_key = Nohm.prefix.meta.properties + this.modelName;
+
+ this.getClient().get(version_key, function (err, db_version) {
+ if (err) {
+ Nohm.logError(err);
+ } else if (self.meta.version !== db_version) {
+ async.parallel({
+ version: function (next) {
+ self.getClient().set(version_key, self.meta.version, next);
+ },
+ idGenerator: function (next) {
+ self.getClient().set(idGenerator_key, self.idGenerator.toString(), next);
+ },
+ properties: function (next) {
+ self.getClient().set(properties_key, JSON.stringify(self.meta.properties), next);
+ }
+ }, function (err) {
+ if (err) {
+ Nohm.logError(err);
+ } else {
+ self.meta.inDb = true;
+ }
+ });
+ }
+ });
+};
+
/**
* DO NOT USE THIS UNLESS YOU ARE ABSOLUTELY SURE ABOUT IT!
*
View
11 lib/store.js
@@ -3,10 +3,9 @@ exports.setNohm = function (originalNohm) {
Nohm = originalNohm;
};
-var async = require('async'),
- h = require(__dirname + '/helpers');
-
-var noop = function () {};
+var async = require('async');
+var h = require(__dirname + '/helpers');
+var crypto = require('crypto');
/**
* Saves the object by either creating, or updating it.
@@ -176,6 +175,8 @@ var __update = function __update(all, options, callback) {
}
if (hmsetArgs.length > 1) {
+ hmsetArgs.push('__meta_version');
+ hmsetArgs.push(this.meta.version);
multi.hmset.apply(multi, hmsetArgs);
}
@@ -192,7 +193,7 @@ var __update = function __update(all, options, callback) {
}
}
}
-
+
multi.exec(function (err) {
if (typeof callback !== 'function' && err) {
Nohm.logError('Nohm: Updating an object resulted in a client error: ' + err);
View
2 test/featureTests.js
@@ -950,7 +950,7 @@ exports["no key left behind"] = function (t) {
t.ok(!err, 'Unexpected saving error');
redis.keys(prefix + ':*', function (err, keys) {
t.ok(!err, 'Unexpected saving error');
- t.same(keys.length, 1, 'Not all keys were removed from the database'); // we keep the idsets, so it should be 1 here.
+ t.same(keys.length, 1, 'Not all keys were removed from the database'); // we keep the idsets and meta keys (version, idgenerator and properties), so it should be 4 here.
t.done();
});
}
View
209 test/metaTests.js
@@ -0,0 +1,209 @@
+var async = require('async');
+var nohm = require(__dirname + '/../lib/nohm').Nohm;
+var h = require(__dirname + '/helper.js');
+var args = require(__dirname + '/testArgs.js');
+var redis = args.redis;
+var crypto = require('crypto');
+
+var prefix = args.prefix;
+
+nohm.model('UserMetaMockup', {
+ properties: {
+ name: {
+ type: 'string',
+ defaultValue: 'testName',
+ index: true,
+ validations: [
+ 'notEmpty'
+ ]
+ },
+ email: {
+ type: 'string',
+ defaultValue: 'testMail@test.de',
+ unique: true,
+ validations: [
+ 'email',
+ function (vals, old, cb) {
+ cb(vals !== 'thisisnoemail');
+ }
+ ]
+ },
+ gender: {
+ type: 'string'
+ },
+ json: {
+ type: 'json',
+ defaultValue: '{}'
+ },
+ number: {
+ type: 'integer',
+ defaultValue: 1,
+ index: true
+ },
+ bool: {
+ type: 'bool',
+ defaultValue: false
+ }
+ },
+ idGenerator: 'increment'
+});
+
+nohm.model('CommentMetaMockup', {
+ properties: {
+ name: {
+ type: 'string',
+ defaultValue: 'testName',
+ index: true,
+ validations: [
+ 'notEmpty'
+ ]
+ }
+ },
+ idGenerator: function (cb) {
+ return cb(+new Date());
+ }
+});
+
+var errLogger = function(err) {
+ if (err) {
+ console.dir(err);
+ }
+};
+
+var createUsers = function(props, modelName, callback) {
+ if (typeof(modelName) === 'function') {
+ callback = modelName;
+ modelName = 'UserMetaMockup';
+ }
+ var makeSeries = function(prop) {
+ return function(next) {
+ var user = nohm.factory(modelName);
+ user.p(prop);
+ user.save(function (err) {
+ next(err, user);
+ });
+ };
+ };
+
+ var series = props.map(function(prop) {
+ return makeSeries(prop);
+ });
+
+ async.series(series, function(err, users) {
+ var ids = users.map(function (user) {
+ return user.id;
+ });
+ callback(users, ids);
+ });
+};
+
+var users_created = false;
+
+exports.meta = {
+
+ setUp: function(next) {
+ if (!nohm.client) {
+ nohm.setClient(redis);
+ }
+ var t = this;
+
+ if ( ! users_created) {
+ createUsers([{
+ name: 'metatestsone',
+ email: 'metatestsone@hurgel.de',
+ gender: 'male',
+ number: 3
+ }, {
+ name: 'metateststwo',
+ email: 'numericindextest2@hurgel.de',
+ gender: 'male',
+ number: 4
+ }], function(users, ids) {
+ var comment = nohm.factory('CommentMetaMockup');
+ users_created = true;
+ users[0].link(comment);
+ users[0].save(function () {
+ t.users = users;
+ t.userIds = ids;
+ next();
+ });
+ });
+ } else {
+ next();
+ }
+ },
+
+ version: function(t) {
+ var user = nohm.factory('UserMetaMockup');
+ t.expect(1);
+
+ var hash = crypto.createHash('sha1');
+
+ hash.update(JSON.stringify(user.meta.properties));
+ hash.update(JSON.stringify(user.modelName));
+ hash.update(user.idGenerator.toString());
+
+ redis.get(prefix+':meta:version:UserMetaMockup', function (err, version) {
+ errLogger(err);
+ t.same(hash.digest('hex'), version, 'Version of the metadata did not match.');
+ t.done();
+ });
+ },
+
+ "version in instance": function(t) {
+ var user = nohm.factory('UserMetaMockup');
+ t.expect(1);
+
+ redis.hget(prefix+':hash:UserMetaMockup:1', '__meta_version', function (err, version) {
+ errLogger(err);
+ t.same(user.meta.version, version, 'Version of the instance did not match metaData.');
+ t.done();
+ });
+ },
+
+ idGenerator: function(t) {
+ var user = nohm.factory('UserMetaMockup');
+ var comment = nohm.factory('CommentMetaMockup');
+ t.expect(2);
+
+ async.parallel([
+ function (next) {
+ redis.get(prefix+':meta:idGenerator:UserMetaMockup', function (err, generator) {
+ errLogger(err);
+ t.same(user.idGenerator.toString(), generator, 'idGenerator of the user did not match.');
+ next();
+ });
+ },
+ function (next) {
+ redis.get(prefix+':meta:idGenerator:CommentMetaMockup', function (err, generator) {
+ errLogger(err);
+ t.same(comment.idGenerator.toString(), generator, 'idGenerator of the comment did not match.');
+ next();
+ });
+ }
+ ], t.done);
+ },
+
+ properties: function(t) {
+ var user = nohm.factory('UserMetaMockup');
+ var comment = nohm.factory('CommentMetaMockup');
+ t.expect(2);
+
+ async.parallel([
+ function (next) {
+ redis.get(prefix+':meta:properties:UserMetaMockup', function (err, properties) {
+ errLogger(err);
+ t.same(JSON.stringify(user.meta.properties), properties, 'Properties of the user did not match.');
+ next();
+ });
+ },
+ function (next) {
+ redis.get(prefix+':meta:properties:CommentMetaMockup', function (err, properties) {
+ errLogger(err);
+ t.same(JSON.stringify(comment.meta.properties), properties, 'Properties of the comment did not match.');
+ next();
+ });
+ }
+ ], t.done);
+ }
+};
View
3 test/testArgs.js
@@ -11,9 +11,6 @@ process.argv.forEach(function (val, index) {
if (val === '--no-cleanup') {
exports.noCleanup = true;
}
- if (val === '--set-meta') {
- exports.setMeta = true;
- }
if (val === '--redis-host') {
exports.redis_host = process.argv[index + 1];
}
View
3 test/tests.js
@@ -20,7 +20,7 @@ var args = require(__dirname+'/testArgs.js');
var runner = function () {
process.chdir(__dirname);
- run(['featureTests.js', 'validationTests.js', 'relationTests.js', 'findTests.js', 'connectTests.js', 'pubsubTests.js']);
+ run(['featureTests.js', 'validationTests.js', 'relationTests.js', 'findTests.js', 'connectTests.js', 'metaTests.js', 'pubsubTests.js']);
};
@@ -32,5 +32,4 @@ var redis = args.redis,
},
Nohm = require(__dirname+'/../lib/nohm').Nohm;
Nohm.setPrefix(args.prefix);
-Nohm.meta = args.setMeta;
cleanup(runner, true);

0 comments on commit bbfcee8

Please sign in to comment.