Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ var User = modella('User');
User.use(mongo('Account')); // Uses db.Account
```

## Attribute Configuration

### `{'unique': true}`

This will create a unique index for the attribute

False by default

### `{'atomic': true}`

Uses the `'$inc'` update modifier for mongo, allowing a value to be (de)incremented as needed, rather than using `'$set'` each time.

This only works for number attributes.

False by default

## API

Expand Down
80 changes: 71 additions & 9 deletions lib/mongo.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use strict';
/**
* Module dependencies
*/
Expand All @@ -7,6 +8,9 @@ var debug = require('debug')('modella:mongo'),
maggregate = require('./maggregate'),
sync = {};

var FLOAT_REGEXP = /[0-9]*\.[0-9]*/;
var SCIENTIFIC_REGEXP = /[0-9.]+e[0-9]+/;

/**
* Export `Mongo`
*/
Expand All @@ -16,24 +20,37 @@ module.exports = function(url) {

// Use alternate collection name, defaults to modelName
return function(Model) {
return ('string' == typeof Model) ? plugin.bind(null, Model) : plugin(Model.modelName, Model);
return ('string' === typeof Model) ? plugin.bind(null, Model) : plugin(Model.modelName, Model);
};

function plugin(collection, Model) {
var db = mongo.collection(collection);
Model.db = db;

db.open(function(err, col) {
db.open(function(err) {
if (err) throw err;
mquery(Model, db.collection);
maggregate(Model, db.collection);
});

Model.index = db.ensureIndex.bind(db);

Model.prototype.oldAtomics = {};

Model.on('change', function(instance, name, value, previous) {
console.log('changed %s: %s -> %s', name, previous, value);
var options = Model.attrs[name];
if (options.atomic && instance.oldAtomics[name] === undefined) {
instance.oldAtomics[name] = previous;
}
});

Model.once('initialize', function() {
for(var attr in Model.attrs) {
var options = Model.attrs[attr];
if (options.unique) Model.index(attr, { w: 0, unique: true, sparse: true });
if (Model.attrs.hasOwnProperty(attr)) {
var options = Model.attrs[attr];
if (options.unique) Model.index(attr, { w: 0, unique: true, sparse: true });
}
}
});

Expand All @@ -43,7 +60,7 @@ module.exports = function(url) {
var doc = docs ? docs[0] : null;
if(err) {
// Check for duplicate index
if(err.code == 11000) {
if(err.code === 11000) {
var attr = err.message.substring(err.message.indexOf('$') + 1, err.message.indexOf('_1'));
self.error(attr, 'has already been taken');
}
Expand All @@ -67,10 +84,55 @@ module.exports = function(url) {

if(Object.keys(changed).length === 0) { return cb(null, this._attrs); }

return db.findAndModify({_id: id}, {}, {$set: changed}, {new: true}, function(err, doc) {
// set up empty update document
var updateDoc = {
};

// loop through each changed key to see if it has been configured as "atomic"
Object.keys(changed).forEach(function(changedKey) {
var options = Model.attrs[changedKey];
if (options.atomic) {
// if atomic, try parsing it as a number
var numString = changed[changedKey].toString();
var number = NaN;
// detect float strings
if (FLOAT_REGEXP.test(numString) || SCIENTIFIC_REGEXP.test(numString)) {
number = parseFloat(numString);
} else {
// assume base 10?
number = parseInt(numString, 10);
}
// if not actually a number return an error
if (isNaN(number)) {
var errorString = "Atomic property " + changedKey + " set to NaN";
self.error(changedKey, errorString);
return cb(new Error(errorString));
}
// get the old value of the atomic variable is available
if (self.oldAtomics[changedKey] !== undefined) {
// get the difference and update the $inc doc on the updateDoc
var delta = number - self.oldAtomics[changedKey];
if (!updateDoc.$inc) updateDoc.$inc = {};
updateDoc.$inc[changedKey] = delta;
} else {
// if there is no old value, just $set it
if (!updateDoc.$set) updateDoc.$set = {};
updateDoc.$set[changedKey] = number;
}
// set the old atomic value to the new value
self.oldAtomics[changedKey] = changed[changedKey];
} else {
if (!updateDoc.$set) updateDoc.$set = {};
updateDoc.$set[changedKey] = changed[changedKey];
}
});

console.log("update doc", updateDoc);

return db.findAndModify({_id: id}, {}, updateDoc, {new: true}, function(err, doc) {
if(err) {
// Check for duplicate index
if(err.code == 11000) {
if(err.code === 11000) {
var attr = err.message.substring(err.message.indexOf('$') + 1, err.message.indexOf('_1'));
self.error(attr, 'has already been taken');
}
Expand Down Expand Up @@ -108,7 +170,7 @@ module.exports = function(url) {

if(!query) return fn(null, false);

if(typeof query == 'string')
if(typeof query === 'string')
query = {_id: db.id(query)};
else
convertStringToIds(query);
Expand All @@ -129,7 +191,7 @@ module.exports = function(url) {
};

function convertStringToIds(query) {
if(typeof query == 'object' && query._id && typeof query._id == 'string')
if(typeof query === 'object' && query._id && typeof query._id === 'string')
query._id = db.id(query._id);
}
}
Expand Down