Skip to content
Open
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
107 changes: 106 additions & 1 deletion lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ function Document(model, options) {
});
}


/**
* Return the options of the document, not the instance of Document.
* @return {Object=}
Expand Down Expand Up @@ -176,6 +175,109 @@ Document.prototype._generateDefault = function() {
}
}

function getShadowObject() {
if ((this._shadowObject === undefined) || (this._shadowObject === null)) {
this._shadowObject = {};
}
return this._shadowObject;
}

Document.prototype.getShadowObject = getShadowObject

Document.prototype._overrideProperties = function(typeObj, obj) {
if ((obj === undefined) || (obj === null)) {
return;
}
var _this = this;
var isArrayKey = false;
var schema = null;
if (type.isArray(typeObj)) {
schema = typeObj[0]._schema;
} else {
schema = typeObj._schema;
}
//for each key in the schema, if they have _methods present override the getter/setter
Object.keys(schema).forEach(function(schemaKey) {
var schemaDef = schema[schemaKey];
if (schemaDef === undefined) {
return;
}
if (type.isArray(schemaDef)) {
if (schemaDef._schema === undefined) {
return;
}
schemaDef = schemaDef._schema;
if (schemaDef._schema === undefined) {
return;
}
if ((schemaDef._methods === undefined)
|| (Object.keys(schemaDef._methods).length === 0)) {
return;
}
var methods = Object.assign({
getShadowObject: getShadowObject
}, schemaDef._methods);
//need to override the arrays push method if the value is set
var existingValue = obj[schemaKey];
Object.defineProperty(obj, schemaKey, {
set: function(value) {
var shadow = this.getShadowObject();
shadow[schemaKey] = value;
if ((value === null) || (value === undefined)) {
//nothing to do
return;
}
var currentPush = shadow[schemaKey].push;
var newPush = function() {
var args = Array.prototype.slice.call(arguments);
args.forEach(function(value) {
if ((value !== null) && (value !== undefined)) {
util.changeProto(value, methods);
_this._overrideProperties(schemaDef, value);
}
});
return currentPush.apply(this, args);
}
shadow[schemaKey].push = newPush;
shadow[schemaKey].forEach(function(value) {
util.changeProto(value, methods);
_this._overrideProperties(schemaDef, value);
});
},
get: function() {
return this.getShadowObject()[schemaKey];
},
enumerable: true
});
if (existingValue !== undefined) {
obj[schemaKey] = existingValue;
}
} else if ((schemaDef._methods !== undefined) && (Object.keys(schemaDef._methods).length > 0)) {
//override the getters and setters to we can inject the custom functions
var methods = Object.assign({
getShadowObject: getShadowObject
}, schemaDef._methods);
var existingValue = obj[schemaKey];
Object.defineProperty(obj, schemaKey, {
set: function(value) {
var shadow = this.getShadowObject();
shadow[schemaKey] = value;
if ((value !== null) && (value !== undefined)) {
util.changeProto(value, methods);
_this._overrideProperties(schemaDef, value);
}
},
get: function() {
return this.getShadowObject()[schemaKey];
},
enumerable: true
});
if (existingValue !== undefined) {
obj[schemaKey] = existingValue;
}
}
});
}

/*
* Validate this document against the schema of its model and triggers all the hooks.
Expand Down Expand Up @@ -473,6 +575,9 @@ Document.prototype.__makeSavableCopy = function(doc, schema, options, model, r)
if (util.isPlainObject(doc) && (doc instanceof Buffer === false)) {
result = {};
util.loopKeys(doc, function(doc, key) {
if (key === '_shadowObject') {
return true;
}
copyFlag = true;
if ((util.isPlainObject(model) === false) || (model._joins[key] === undefined)) { // We do not copy joined documents
if ((schema !== undefined) && (schema._schema !== undefined) && (type.isVirtual(schema._schema[key]) === true)) {
Expand Down
9 changes: 9 additions & 0 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ Model.new = function(name, schema, options, thinky) {

util.changeProto(doc, new Document(model, options));

doc._overrideProperties(model._schema, doc);

// Create joins document. We do it here because `options` are easily available
util.loopKeys(proto._joins, function(joins, key) {
if (doc[key] != null) {
Expand Down Expand Up @@ -162,6 +164,13 @@ Model.new = function(name, schema, options, thinky) {

model.__proto__ = proto;


if (model._schema._methods !== undefined) {
Object.keys(model._schema._methods).forEach(function(key) {
model.define(key, model._schema._methods[key]);
});
}

if (options.init !== false) {
// Setup the model's table.
model.tableReady().then();
Expand Down
21 changes: 19 additions & 2 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ module.exports.generateDefault = generateDefault;
function parse(schema, prefix, options, model) {
var result;

var reservedFunctionKeys = ['default', 'validator']
var dataTypeFunctions = [String, Number, Boolean, Date, Buffer, Object, Array];

if ((prefix === '') && (type.isObject(schema) === false) && (util.isPlainObject(schema) === false)) {
throw new Errors.ValidationError("The schema must be a plain object.")
}
Expand Down Expand Up @@ -164,7 +167,14 @@ function parse(schema, prefix, options, model) {
result = type.object().options(options).validator(schema.validator);
if (schema.default !== undefined) { result.default(schema.default); }
util.loopKeys(schema.schema, function(_schema, key) {
result.setKey(key, parse(_schema[key], prefix+"["+key+"]", options));
if ((typeof _schema[key] === 'function') && (dataTypeFunctions.indexOf(_schema[key]) === -1)) {
if (result._methods === undefined) {
result._methods = {};
}
result._methods[key] = _schema[key];
} else {
result.setKey(key, parse(_schema[key], prefix+"["+key+"]", options));
}
})
if (prefix === '') {
result._setModel(model)
Expand Down Expand Up @@ -233,7 +243,14 @@ function parse(schema, prefix, options, model) {
else {
result = type.object().options(options);
util.loopKeys(schema, function(_schema, key) {
result.setKey(key, parse(_schema[key], prefix+"["+key+"]", options));
if ((typeof _schema[key] === 'function') && (dataTypeFunctions.indexOf(_schema[key]) === -1)) {
if (result._methods === undefined) {
result._methods = {};
}
result._methods[key] = _schema[key];
} else {
result.setKey(key, parse(_schema[key], prefix+"["+key+"]", options));
}
})
if (prefix === '') {
result._setModel(model)
Expand Down
10 changes: 10 additions & 0 deletions lib/type/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ TypeObject.prototype.validator = function(fn) {

TypeObject.prototype.validate = function(object, prefix, options) {
var self = this;

var localOptions = util.mergeOptions(this._options, options);

if (util.validateIfUndefined(object, prefix, "object", localOptions)) return;
Expand All @@ -133,13 +134,19 @@ TypeObject.prototype.validate = function(object, prefix, options) {
}
else {
util.loopKeys(self._schema, function(schema, key) {
if (key === '_shadowObject') {
return true;
}
schema[key].validate(object[key], prefix+"["+key+"]", options);
});

// We clean extra fields in validate, for a use case, see:
// https://github.com/neumino/thinky/pull/123#issuecomment-56254682
if (localOptions.enforce_extra === "remove") {
util.loopKeys(object, function(object, key) {
if (key === '_shadowObject') {
return true;
}
if ((self._model === undefined || self._model._joins.hasOwnProperty(key) === false)
&& (self._schema[key] === undefined)) {
delete object[key];
Expand All @@ -148,6 +155,9 @@ TypeObject.prototype.validate = function(object, prefix, options) {
}
else if (localOptions.enforce_extra === "strict") {
util.loopKeys(object, function(object, key) {
if (key === '_shadowObject') {
return true;
}
if ((self._model === undefined || self._model._joins.hasOwnProperty(key) === false)
&& (self._schema[key] === undefined)) {
util.extraField(prefix, key);
Expand Down
Loading