Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: lazy hooks #8

Merged
merged 3 commits into from
Feb 9, 2021
Merged
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
14 changes: 9 additions & 5 deletions lib/embeddedDocument.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ var EmbeddedDocument = function(options) {
};

EmbeddedDocument.prototype.getUniqGroupId = function() {
return [
this.collection.collectionName,
this.key,
utils.stringifyProjection(this.projection)
].join('.');
return utils.getUniqGroupId(this);
};

EmbeddedDocument.prototype.getString = function() {
Expand All @@ -47,4 +43,12 @@ EmbeddedDocument.prototype.toJSON = function() {
return this.identifier;
};

EmbeddedDocument.prototype.setEmbeddedValue = function(embeddedValue) {
if (embeddedValue && embeddedValue[this.key] === this.identifier) {
this.embeddedValue = embeddedValue;
} else {
throw new Error('Invalid embedded value');
}
};

module.exports = EmbeddedDocument;
6 changes: 5 additions & 1 deletion lib/embedderHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ var getFindParamsHash = function(object) {
if (!Array.isArray(value) && !utils.isSimpleObject(value)) return;

if (value instanceof EmbeddedDocument) {
if (value.embeddedValue) return;

var uniqGroupId = value.getUniqGroupId();

var params = findParamsHash[uniqGroupId];
Expand Down Expand Up @@ -149,7 +151,9 @@ var replaceEmbeddedDocuments = function(object, documentsHash) {
if (value instanceof EmbeddedDocument) {
var uniqGroupId = value.getUniqGroupId();

var embeddedDocument = documentsHash[uniqGroupId][value.identifier];
var embeddedDocument = value.embeddedValue
? value.embeddedValue
: documentsHash[uniqGroupId][value.identifier];

if (!embeddedDocument) {
throw new Error(
Expand Down
92 changes: 73 additions & 19 deletions lib/relationHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,54 @@ var setupRelationHooks = function(relatedCollection, relation) {
});
};

var getEmbeddedDocuments = function(identifiers, callback) {
var projectionKeys = Object.keys(relation.projection);
if (projectionKeys.length === 1 && relation.projection[relation.key]) {
var documents = identifiers.map(function(identifier) {
return utils.createObject(relation.key, identifier);
});
callback(null, documents);
} else {
var condition = utils.createObject(relation.key, {$in: identifiers});
relation.collection.find(condition, relation.projection).toArray(callback);
}
};

var getEmbeddedDocumentsHash = function(identifiers, callback) {
getEmbeddedDocuments(identifiers, function(err, documents) {
if (err) return callback(err);

var documentsHash = utils.indexBy(documents, relation.key);

callback(null, documentsHash);
});
};

var beforeUpdate = function(params, callback) {
if (relation.onUpdate === 'cascade') {
var uniqGroupId = utils.getUniqGroupId(relation);
params.meta.cascadeUpdateParamsHash = params.meta.cascadeUpdateParamsHash || {};

if (
relation.onUpdate === 'cascade' &&
!params.meta.cascadeUpdateParamsHash[uniqGroupId]
) {
var cascadeUpdateParams = params.meta.cascadeUpdateParamsHash[uniqGroupId] = {};

getModifiedIdentifiers(params.condition, function(err, identifiers) {
if (err) return callback(err);

params.meta.modifiedIdentifiers = identifiers;
cascadeUpdateParams.modifiedIdentifiers = identifiers;

if (!identifiers.length) return callback();

// fetch current embedded documents to skip unnecessary updates later
getEmbeddedDocumentsHash(identifiers, function(err, documentsHash) {
if (err) return callback(err);

cascadeUpdateParams.originalEmbeddedDocumentsHash = documentsHash;

callback();
callback();
});
});
} else {
callback();
Expand All @@ -39,36 +79,50 @@ var setupRelationHooks = function(relatedCollection, relation) {
relation.collection.on('beforeUpdateMany', beforeUpdate);

var afterUpdate = function(params, callback) {
var identifiers = params.meta.modifiedIdentifiers || [];
var uniqGroupId = utils.getUniqGroupId(relation);
var cascadeUpdateParams = params.meta.cascadeUpdateParamsHash[uniqGroupId] || {};
var identifiers = cascadeUpdateParams.modifiedIdentifiers || [];
var originalDocumentsHash = cascadeUpdateParams.originalEmbeddedDocumentsHash || {};

if (!identifiers.length) return callback();

if (relation.onUpdate === 'cascade') {
// in cascade mode we need to update each updated identifier
var funcs = identifiers.map(function(identifier) {
return function(callback) {
var projectionKeys = Object.keys(relation.projection);
if (projectionKeys.length === 1 && relation.projection[relation.key]) {
callback();
} else {
getEmbeddedDocumentsHash(identifiers, function(err, newDocumentsHash) {
if (err) return callback(err);

// in cascade mode we need to update each updated identifier
var funcs = identifiers.map(function(identifier) {
return function(callback) {
var originalDocument = originalDocumentsHash[identifier];
var newDocument = newDocumentsHash[identifier];
if (
originalDocument && newDocument &&
utils.isDeepStrictEqual(originalDocument, newDocument)
) {
return callback();
}

var condition = utils.createObject(
relation.paths.identifier,
identifier
);

var embeddedDocument = relation.embedder(identifier);

if (newDocument) {
embeddedDocument.setEmbeddedValue(newDocument);
}

var modifier = {
$set: utils.createObject(
relation.paths.modifier,
relation.embedder(identifier)
)
$set: utils.createObject(relation.paths.modifier, embeddedDocument)
};

relatedCollection.updateMany(condition, modifier, callback);
}
};
});
};
});

utils.asyncParallel(funcs, callback);
utils.asyncParallel(funcs, callback);
});
} else {
callback();
}
Expand Down
19 changes: 18 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use strict';

var util = require('util');
var defaults = require('./defaults');

var isObject = exports.isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
Expand Down Expand Up @@ -47,6 +50,12 @@ exports.deepSet = function(object, field, value) {
return object;
};

exports.isDeepStrictEqual = function(val1, val2) {
return util.isDeepStrictEqual
? util.isDeepStrictEqual(val1, val2)
: JSON.stringify(val1) === JSON.stringify(val2);
};

exports.isModifier = function(modifier) {
var keys = Object.keys(modifier);
return keys.length && (/^\$/).test(keys[0]);
Expand Down Expand Up @@ -99,7 +108,7 @@ exports.asyncParallel = function(funcs, callback, context) {
});
};

exports.stringifyProjection = function(projection) {
var stringifyProjection = exports.stringifyProjection = function(projection) {
var parts = [];

Object.keys(projection).sort().forEach(function(key) {
Expand All @@ -118,3 +127,11 @@ exports.stringifyProjection = function(projection) {

return '{' + parts.join(',') + '}';
};

exports.getUniqGroupId = function(relation) {
return [
relation.collection.collectionName,
relation.key || defaults.key,
stringifyProjection(relation.projection || defaults.projection)
].join('.');
};