"use strict";
// Import external names locally
var Shared,
Db,
DbInit,
Collection;
Shared = require('./Shared');
/**
* Creates a new collection group. Collection groups allow single operations to be
* propagated to multiple collections at once. CRUD operations against a collection
* group are in fed to the group's collections. Useful when separating out slightly
* different data into multiple collections but querying as one collection.
* @constructor
*/
var CollectionGroup = function () {
this.init.apply(this, arguments);
};
CollectionGroup.prototype.init = function (name) {
var self = this;
self._name = name;
self._data = new Collection('__FDB__cg_data_' + self._name);
self._collections = [];
self._view = [];
};
Shared.addModule('CollectionGroup', CollectionGroup);
Shared.mixin(CollectionGroup.prototype, 'Mixin.Common');
Shared.mixin(CollectionGroup.prototype, 'Mixin.ChainReactor');
Shared.mixin(CollectionGroup.prototype, 'Mixin.Constants');
Shared.mixin(CollectionGroup.prototype, 'Mixin.Triggers');
Collection = require('./Collection');
Db = Shared.modules.Db;
DbInit = Shared.modules.Db.prototype.init;
CollectionGroup.prototype.on = function () {
this._data.on.apply(this._data, arguments);
};
CollectionGroup.prototype.off = function () {
this._data.off.apply(this._data, arguments);
};
CollectionGroup.prototype.emit = function () {
this._data.emit.apply(this._data, arguments);
};
/**
* Gets / sets the primary key for this collection group.
* @param {String=} keyName The name of the primary key.
* @returns {*}
*/
CollectionGroup.prototype.primaryKey = function (keyName) {
if (keyName !== undefined) {
this._primaryKey = keyName;
return this;
}
return this._primaryKey;
};
/**
* Gets / sets the current state.
* @param {String=} val The name of the state to set.
* @returns {*}
*/
Shared.synthesize(CollectionGroup.prototype, 'state');
/**
* Gets / sets the db instance the collection group belongs to.
* @param {Db=} db The db instance.
* @returns {*}
*/
Shared.synthesize(CollectionGroup.prototype, 'db');
/**
* Gets / sets the instance name.
* @param {Name=} name The new name to set.
* @returns {*}
*/
Shared.synthesize(CollectionGroup.prototype, 'name');
CollectionGroup.prototype.addCollection = function (collection) {
if (collection) {
if (this._collections.indexOf(collection) === -1) {
//var self = this;
// Check for compatible primary keys
if (this._collections.length) {
if (this._primaryKey !== collection.primaryKey()) {
throw(this.logIdentifier() + ' All collections in a collection group must have the same primary key!');
}
} else {
// Set the primary key to the first collection added
this.primaryKey(collection.primaryKey());
}
// Add the collection
this._collections.push(collection);
collection._groups = collection._groups || [];
collection._groups.push(this);
collection.chain(this);
// Hook the collection's drop event to destroy group data
collection.on('drop', function () {
// Remove collection from any group associations
if (collection._groups && collection._groups.length) {
var groupArr = [],
i;
// Copy the group array because if we call removeCollection on a group
// it will alter the groups array of this collection mid-loop!
for (i = 0; i < collection._groups.length; i++) {
groupArr.push(collection._groups[i]);
}
// Loop any groups we are part of and remove ourselves from them
for (i = 0; i < groupArr.length; i++) {
collection._groups[i].removeCollection(collection);
}
}
delete collection._groups;
});
// Add collection's data
this._data.insert(collection.find());
}
}
return this;
};
CollectionGroup.prototype.removeCollection = function (collection) {
if (collection) {
var collectionIndex = this._collections.indexOf(collection),
groupIndex;
if (collectionIndex !== -1) {
collection.unChain(this);
this._collections.splice(collectionIndex, 1);
collection._groups = collection._groups || [];
groupIndex = collection._groups.indexOf(this);
if (groupIndex !== -1) {
collection._groups.splice(groupIndex, 1);
}
collection.off('drop');
}
if (this._collections.length === 0) {
// Wipe the primary key
delete this._primaryKey;
}
}
return this;
};
CollectionGroup.prototype._chainHandler = function (chainPacket) {
//sender = chainPacket.sender;
switch (chainPacket.type) {
case 'setData':
// Decouple the data to ensure we are working with our own copy
chainPacket.data = this.decouple(chainPacket.data);
// Remove old data
this._data.remove(chainPacket.options.oldData);
// Add new data
this._data.insert(chainPacket.data);
break;
case 'insert':
// Decouple the data to ensure we are working with our own copy
chainPacket.data = this.decouple(chainPacket.data);
// Add new data
this._data.insert(chainPacket.data);
break;
case 'update':
// Update data
this._data.update(chainPacket.data.query, chainPacket.data.update, chainPacket.options);
break;
case 'remove':
this._data.remove(chainPacket.data.query, chainPacket.options);
break;
default:
break;
}
};
CollectionGroup.prototype.insert = function () {
this._collectionsRun('insert', arguments);
};
CollectionGroup.prototype.update = function () {
this._collectionsRun('update', arguments);
};
CollectionGroup.prototype.updateById = function () {
this._collectionsRun('updateById', arguments);
};
CollectionGroup.prototype.remove = function () {
this._collectionsRun('remove', arguments);
};
CollectionGroup.prototype._collectionsRun = function (type, args) {
for (var i = 0; i < this._collections.length; i++) {
this._collections[i][type].apply(this._collections[i], args);
}
};
CollectionGroup.prototype.find = function (query, options) {
return this._data.find(query, options);
};
/**
* Helper method that removes a document that matches the given id.
* @param {String} id The id of the document to remove.
*/
CollectionGroup.prototype.removeById = function (id) {
// Loop the collections in this group and apply the remove
for (var i = 0; i < this._collections.length; i++) {
this._collections[i].removeById(id);
}
};
/**
* Uses the passed query to generate a new collection with results
* matching the query parameters.
*
* @param query
* @param options
* @returns {*}
*/
CollectionGroup.prototype.subset = function (query, options) {
var result = this.find(query, options);
return new Collection()
.subsetOf(this)
.primaryKey(this._primaryKey)
.setData(result);
};
/**
* Drops a collection group from the database.
* @returns {boolean} True on success, false on failure.
*/
CollectionGroup.prototype.drop = function () {
if (!this.isDropped()) {
var i,
collArr,
viewArr;
if (this._debug) {
console.log(this.logIdentifier() + ' Dropping');
}
this._state = 'dropped';
if (this._collections && this._collections.length) {
collArr = [].concat(this._collections);
for (i = 0; i < collArr.length; i++) {
this.removeCollection(collArr[i]);
}
}
if (this._view && this._view.length) {
viewArr = [].concat(this._view);
for (i = 0; i < viewArr.length; i++) {
this._removeView(viewArr[i]);
}
}
this.emit('drop', this);
}
return true;
};
// Extend DB to include collection groups
Db.prototype.init = function () {
this._collectionGroup = {};
DbInit.apply(this, arguments);
};
Db.prototype.collectionGroup = function (collectionGroupName) {
if (collectionGroupName) {
// Handle being passed an instance
if (collectionGroupName instanceof CollectionGroup) {
return collectionGroupName;
}
this._collectionGroup[collectionGroupName] = this._collectionGroup[collectionGroupName] || new CollectionGroup(collectionGroupName).db(this);
return this._collectionGroup[collectionGroupName];
} else {
// Return an object of collection data
return this._collectionGroup;
}
};
/**
* Returns an array of collection groups the DB currently has.
* @returns {Array} An array of objects containing details of each collection group
* the database is currently managing.
*/
Db.prototype.collectionGroups = function () {
var arr = [],
i;
for (i in this._collectionGroup) {
if (this._collectionGroup.hasOwnProperty(i)) {
arr.push({
name: i
});
}
}
return arr;
};
module.exports = CollectionGroup;