Skip to content

Commit

Permalink
Added foreign key functionality to schema id arrays. Also added a dep…
Browse files Browse the repository at this point in the history
…th & filter for when expanding foreign key objects during serialization
  • Loading branch information
MKHenson committed May 26, 2016
1 parent a8a94f2 commit 4b522c3
Show file tree
Hide file tree
Showing 6 changed files with 363 additions and 98 deletions.
13 changes: 12 additions & 1 deletion server/dist/src/models/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,28 @@ class Model {
var foreignModel;
var optionalDependencies = instance.dbEntry._optionalDependencies;
var requiredDependencies = instance.dbEntry._requiredDependencies;
var arrayDependencies = instance.dbEntry._arrayDependencies;
var promises = [];
// Nullify all dependencies that are optional
if (optionalDependencies)
for (var i = 0, l = optionalDependencies.length; i < l; i++) {
foreignModel = Model.getByName(optionalDependencies[i].collection);
if (!foreignModel)
continue;
var setToken = { $set: {} };
let setToken = { $set: {} };
setToken.$set[optionalDependencies[i].propertyName] = null;
promises.push(foreignModel.collection.updateOne({ _id: optionalDependencies[i]._id }, setToken));
}
// Remove any dependencies that are in arrays
if (arrayDependencies)
for (var i = 0, l = arrayDependencies.length; i < l; i++) {
foreignModel = Model.getByName(arrayDependencies[i].collection);
if (!foreignModel)
continue;
let pullToken = { $pull: {} };
pullToken.$pull[arrayDependencies[i].propertyName] = instance._id;
promises.push(foreignModel.collection.updateMany({ _id: arrayDependencies[i]._id }, pullToken));
}
// For those dependencies that are required, we delete the instances
if (requiredDependencies)
for (var i = 0, l = requiredDependencies.length; i < l; i++) {
Expand Down
45 changes: 22 additions & 23 deletions server/dist/src/models/schema-items/schema-foreign-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const schema_item_1 = require("./schema-item");
const model_1 = require("../model");
const mongodb_1 = require("mongodb");
const utils_1 = require("../../utils");
const schema_id_array_1 = require("./schema-id-array");
/**
* Represents a mongodb ObjectID of a document in separate collection.
* Foreign keys are used as a way of relating models to one another. They can be required or optional.
Expand Down Expand Up @@ -116,32 +117,30 @@ class SchemaForeignKey extends schema_item_1.SchemaItem {
*/
getValue(options) {
return __awaiter(this, void 0, Promise, function* () {
if (options.expandForeignKeys && options.expandMaxDepth === undefined)
throw new Error("You cannot set expandForeignKeys and not specify the expandMaxDepth");
if (!options.expandForeignKeys)
return this.value;
else {
var model = model_1.Model.getByName(this.targetCollection);
if (model) {
if (!this.value)
return null;
// Make sure the current level is not beyond the max depth
if (options.expandMaxDepth !== undefined) {
if (this.curLevel > options.expandMaxDepth)
return this.value;
}
else
options.expandMaxDepth = 1;
var result = yield model.findOne({ _id: this.value });
// Get the models items are increase their level - this ensures we dont go too deep
var items = result.schema.getItems();
var nextLevel = this.curLevel + 1;
for (var i = 0, l = items.length; i < l; i++)
if (items[i] instanceof SchemaForeignKey)
items[i].curLevel = nextLevel;
return yield result.schema.getAsJson(result.dbEntry._id, options);
}
else
throw new Error(`${this.name} references a foreign key '${this.targetCollection}' which doesn't seem to exist`);
if (options.expandSchemaBlacklist && options.expandSchemaBlacklist.indexOf(this.name) != -1)
return this.value;
var model = model_1.Model.getByName(this.targetCollection);
if (!model)
throw new Error(`${this.name} references a foreign key '${this.targetCollection}' which doesn't seem to exist`);
if (!this.value)
return null;
// Make sure the current level is not beyond the max depth
if (options.expandMaxDepth !== undefined) {
if (this.curLevel > options.expandMaxDepth)
return this.value;
}
var result = yield model.findOne({ _id: this.value });
// Get the models items are increase their level - this ensures we dont go too deep
var items = result.schema.getItems();
var nextLevel = this.curLevel + 1;
for (var i = 0, l = items.length; i < l; i++)
if (items[i] instanceof SchemaForeignKey || items[i] instanceof schema_id_array_1.SchemaIdArray)
items[i].curLevel = nextLevel;
return yield result.schema.getAsJson(result.dbEntry._id, options);
});
}
}
Expand Down
157 changes: 133 additions & 24 deletions server/dist/src/models/schema-items/schema-id-array.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments)).next());
});
};
const schema_item_1 = require("./schema-item");
const schema_foreign_key_1 = require("./schema-foreign-key");
const model_1 = require("../model");
const mongodb_1 = require("mongodb");
const utils_1 = require("../../utils");
/**
* A n ID array scheme item for use in Models
*/
* An ID array scheme item for use in Models. Optionally can be used as a foreign key array
* and return objects of the specified ids. In order for the array to return objects you must
* specify the targetCollection property. This tells the schema from which model the ids belong to.
* Currently we only support Id lookups that exist in the same model - i.e. if the ids are of objects
* in different models we cannot get the object values.
*/
class SchemaIdArray extends schema_item_1.SchemaItem {
/**
* Creates a new schema item that holds an array of id items
* @param {string} name The name of this item
* @param {Array<string|ObjectID>} val The array of ids for this schema item
* @param {number} minItems [Optional] Specify the minimum number of items that can be allowed
* @param {number} maxItems [Optional] Specify the maximum number of items that can be allowed
*/
constructor(name, val, minItems = 0, maxItems = 10000) {
* Creates a new schema item that holds an array of id items
* @param {string} name The name of this item
* @param {Array<string|ObjectID>} val The array of ids for this schema item
* @param {number} minItems [Optional] Specify the minimum number of items that can be allowed
* @param {number} maxItems [Optional] Specify the maximum number of items that can be allowed
* @param {string} targetCollection [Optional] Specify the model name to which all the ids belong. If set
* the item can expand objects on retreival.
*/
constructor(name, val, minItems = 0, maxItems = 10000, targetCollection = "") {
super(name, val);
this.maxItems = maxItems;
this.minItems = minItems;
this.targetCollection = targetCollection;
this.curLevel = 1;
}
/**
* Creates a clone of this item
Expand All @@ -28,29 +46,120 @@ class SchemaIdArray extends schema_item_1.SchemaItem {
super.clone(copy);
copy.maxItems = this.maxItems;
copy.minItems = this.minItems;
copy.targetCollection = this.targetCollection;
return copy;
}
/**
* Checks the value stored to see if its correct in its current form
* @returns {Promise<boolean|Error>} Returns true if successful or an error message string if unsuccessful
*/
validate() {
var transformedValue = this.value;
for (var i = 0, l = transformedValue.length; i < l; i++) {
if (typeof this.value[i] == "string") {
if (utils_1.Utils.isValidObjectID(this.value[i]))
transformedValue[i] = new mongodb_1.ObjectID(this.value[i]);
else if (this.value[i].trim() != "")
return Promise.reject(new Error(`Please use a valid ID for '${this.name}'`));
else
return Promise.reject(new Error(`Please use a valid ID for '${this.name}'`));
return __awaiter(this, void 0, Promise, function* () {
var transformedValue = this.value;
for (var i = 0, l = transformedValue.length; i < l; i++) {
if (typeof this.value[i] == "string") {
if (utils_1.Utils.isValidObjectID(this.value[i]))
transformedValue[i] = new mongodb_1.ObjectID(this.value[i]);
else if (this.value[i].trim() != "")
throw new Error(`Please use a valid ID for '${this.name}'`);
else
throw new Error(`Please use a valid ID for '${this.name}'`);
}
}
if (transformedValue.length < this.minItems)
throw new Error(`You must select at least ${this.minItems} item${(this.minItems == 1 ? "" : "s")} for ${this.name}`);
if (transformedValue.length > this.maxItems)
throw new Error(`You have selected too many items for ${this.name}, please only use up to ${this.maxItems}`);
// If no collection - then return
if (this.targetCollection == "")
return true;
if (this.value.length == 0)
return true;
// If they collection is not empty, then it must exist
var model = model_1.Model.getByName(this.targetCollection);
if (!model)
throw new Error(`${this.name} references a foreign key '${this.targetCollection}' which doesn't seem to exist`);
// We can assume the value is object id by this point
var query = { $or: [] };
var arr = this.value;
for (var i = 0, l = arr.length; i < l; i++)
query.$or.push({ _id: arr[i] });
var result = yield model.findInstances(query);
this._targetDocs = result;
return true;
});
}
/**
* Called once a schema has been validated and inserted into the database. Useful for
* doing any post update/insert operations
* @param {ModelInstance<T extends Modepress.IModelEntry>} instance The model instance that was inserted or updated
* @param {string} collection The DB collection that the model was inserted into
*/
postValidation(instance, collection) {
return __awaiter(this, void 0, Promise, function* () {
if (!this._targetDocs)
return;
// If they key is required then it must exist
var model = model_1.Model.getByName(this.targetCollection);
var promises = [];
for (var i = 0, l = this._targetDocs.length; i < l; i++) {
let arrDeps = this._targetDocs[i].dbEntry._arrayDependencies || [];
arrDeps.push({ _id: instance.dbEntry._id, collection: collection, propertyName: this.name });
promises.push(model.collection.updateOne({ _id: this._targetDocs[i].dbEntry._id }, {
$set: { _arrayDependencies: arrDeps }
}));
}
yield Promise.all(promises);
// Nullify the target doc cache
this._targetDocs = null;
return;
});
}
/**
* Gets the value of this item
* @param {ISchemaOptions} options [Optional] A set of options that can be passed to control how the data must be returned
* @returns {Promise<Array<string | ObjectID | Modepress.IModelEntry>>}
*/
getValue(options) {
return __awaiter(this, void 0, Promise, function* () {
if (options.expandForeignKeys && options.expandMaxDepth === undefined)
throw new Error("You cannot set expandForeignKeys and not specify the expandMaxDepth");
if (!options.expandForeignKeys)
return this.value;
if (options.expandSchemaBlacklist && options.expandSchemaBlacklist.indexOf(this.name) != -1)
return this.value;
if (this.targetCollection == "")
return this.value;
var model = model_1.Model.getByName(this.targetCollection);
if (!model)
throw new Error(`${this.name} references a foreign key '${this.targetCollection}' which doesn't seem to exist`);
// Make sure the current level is not beyond the max depth
if (options.expandMaxDepth !== undefined) {
if (this.curLevel > options.expandMaxDepth)
return this.value;
}
if (this.value.length == 0)
return this.value;
// Create the query for fetching the instances
var query = { $or: [] };
for (var i = 0, l = this.value.length; i < l; i++)
query.$or.push({ _id: this.value[i] });
var instances = yield model.findInstances(query);
var instance;
var toReturn = [];
var promises = [];
// Get the models items are increase their level - this ensures we dont go too deep
for (var i = 0, l = instances.length; i < l; i++) {
instance = instances[i];
var items = instance.schema.getItems();
var nextLevel = this.curLevel + 1;
for (var ii = 0, il = items.length; ii < il; ii++)
if (items[ii] instanceof schema_foreign_key_1.SchemaForeignKey || items[ii] instanceof SchemaIdArray)
items[ii].curLevel = nextLevel;
promises.push(instance.schema.getAsJson(instance.dbEntry._id, options));
}
}
if (transformedValue.length < this.minItems)
return Promise.reject(new Error(`You must select at least ${this.minItems} item${(this.minItems == 1 ? "" : "s")} for ${this.name}`));
if (transformedValue.length > this.maxItems)
return Promise.reject(new Error(`You have selected too many items for ${this.name}, please only use up to ${this.maxItems}`));
return Promise.resolve(true);
return yield Promise.all(promises);
});
}
}
exports.SchemaIdArray = SchemaIdArray;
19 changes: 18 additions & 1 deletion server/src/models/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ export abstract class Model
var foreignModel : Model;
var optionalDependencies = instance.dbEntry._optionalDependencies;
var requiredDependencies = instance.dbEntry._requiredDependencies;
var arrayDependencies = instance.dbEntry._arrayDependencies;

var promises : Array<Promise<any>> = [];

// Nullify all dependencies that are optional
Expand All @@ -271,11 +273,24 @@ export abstract class Model
if (!foreignModel)
continue;

var setToken = { $set : {} };
let setToken = { $set : {} };
setToken.$set[optionalDependencies[i].propertyName] = null;
promises.push( foreignModel.collection.updateOne( <IModelEntry>{ _id : optionalDependencies[i]._id }, setToken ) );
}

// Remove any dependencies that are in arrays
if (arrayDependencies)
for ( var i = 0, l = arrayDependencies.length; i < l; i++ )
{
foreignModel = Model.getByName( arrayDependencies[i].collection );
if (!foreignModel)
continue;

let pullToken = { $pull : {} };
pullToken.$pull[arrayDependencies[i].propertyName] = instance._id;
promises.push( foreignModel.collection.updateMany( <IModelEntry>{ _id : arrayDependencies[i]._id }, pullToken ) );
}

// For those dependencies that are required, we delete the instances
if (requiredDependencies)
for ( var i = 0, l = requiredDependencies.length; i < l; i++ )
Expand All @@ -287,6 +302,8 @@ export abstract class Model
promises.push( foreignModel.deleteInstances( <IModelEntry>{ _id : requiredDependencies[i]._id } ) );
}



var dependenciesResults = await Promise.all(promises);

// Remove the original instance from the DB
Expand Down
Loading

0 comments on commit 4b522c3

Please sign in to comment.