diff --git a/lib/dao.js b/lib/dao.js index c536592a..2cc873c0 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -85,7 +85,7 @@ DataAccessObject.create = function (data, callback) { data = {}; } - if (data instanceof Array) { + if (Array.isArray(data)) { var instances = []; var errors = Array(data.length); var gotError = false; @@ -117,7 +117,7 @@ DataAccessObject.create = function (data, callback) { var obj; // if we come from save - if (data instanceof Model && !data.id) { + if (data instanceof Model && !getIdValue(this, data)) { obj = data; } else { obj = new Model(data); @@ -182,6 +182,16 @@ function stillConnecting(dataSource, obj, args) { return true; }; +function getIdValue(m, data) { + return data && data[m.dataSource.idName(m.modelName)]; +} + +function setIdValue(m, data, value) { + if(data) { + data[m.dataSource.idName(m.modelName)] = value; + } +} + /** * Update or insert */ @@ -189,7 +199,7 @@ DataAccessObject.upsert = DataAccessObject.updateOrCreate = function upsert(data if (stillConnecting(this.dataSource, this, arguments)) return; var Model = this; - if (!data.id) return this.create(data, callback); + if (!getIdValue(this, data)) return this.create(data, callback); if (this.dataSource.connector.updateOrCreate) { var inst = new Model(data); this.dataSource.connector.updateOrCreate(Model.modelName, inst.toObject(true), function (err, data) { @@ -203,7 +213,7 @@ DataAccessObject.upsert = DataAccessObject.updateOrCreate = function upsert(data callback(err, obj); }); } else { - this.findById(data.id, function (err, inst) { + this.findById(getIdValue(this, data), function (err, inst) { if (err) return callback(err); if (inst) { inst.updateAttributes(data, callback); @@ -276,8 +286,8 @@ DataAccessObject.findById = function find(id, cb) { this.dataSource.connector.find(this.modelName, id, function (err, data) { var obj = null; if (data) { - if (!data.id) { - data.id = id; + if (!getIdValue(this, data)) { + setIdValue(this, data, id); } obj = new this(); obj._initProperties(data, false); diff --git a/lib/datasource.js b/lib/datasource.js index 52552468..979ecb9f 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -132,6 +132,7 @@ DataSource.prototype._setupConnector = function () { // Set up the dataSource if the connector doesn't do so this.connector.dataSource = this; } + var dataSource = this; this.connector.log = function (query, start) { dataSource.log(query, start); }; diff --git a/lib/model-builder.js b/lib/model-builder.js index 6f58a86a..f5b0902e 100644 --- a/lib/model-builder.js +++ b/lib/model-builder.js @@ -7,7 +7,6 @@ var ModelBaseClass = require('./model.js'); var List = require('./list.js'); var EventEmitter = require('events').EventEmitter; var util = require('util'); -var GeoPoint = require('./geo').GeoPoint; /** * Export public API @@ -21,36 +20,6 @@ exports.Schema = exports.ModelBuilder = ModelBuilder; */ var slice = Array.prototype.slice; -/** - * Schema types - */ -ModelBuilder.Text = function Text() {}; // Text type -ModelBuilder.JSON = function JSON() {}; // JSON Object -ModelBuilder.Any = function Any() {}; // Any Type - -ModelBuilder.schemaTypes = {}; -ModelBuilder.registerType = function (type, names) { - names = names || []; - names = names.concat([type.name]); - for (var n = 0; n < names.length; n++) { - this.schemaTypes[names[n].toLowerCase()] = type; - } -}; - -ModelBuilder.registerType(ModelBuilder.Text); -ModelBuilder.registerType(ModelBuilder.JSON); -ModelBuilder.registerType(ModelBuilder.Any); - -ModelBuilder.registerType(String); -ModelBuilder.registerType(Number); -ModelBuilder.registerType(Boolean); -ModelBuilder.registerType(Date); -ModelBuilder.registerType(Buffer, ['Binary']); -ModelBuilder.registerType(Array); -ModelBuilder.registerType(GeoPoint); -ModelBuilder.registerType(Object); - - /** * ModelBuilder - Data Model Definition */ @@ -62,6 +31,8 @@ function ModelBuilder() { util.inherits(ModelBuilder, EventEmitter); +// Set up types +require('./types')(ModelBuilder); /** * Define class @@ -221,7 +192,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett if(!DataType) { throw new Error('Invalid type for property ' + attr); } - if (DataType instanceof Array) { + if (Array.isArray(DataType)) { DataType = List; } else if (DataType.name === 'Date') { var OrigDate = Date; @@ -261,6 +232,8 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett if(DataType === List) { this.__data[attr] = DataType(value, properties[attr].type, this.__data); } else { + // Assume the type constructor handles Constructor() call + // If not, we should call new DataType(value).valueOf(); this.__data[attr] = DataType(value); } } diff --git a/lib/model.js b/lib/model.js index 78a5d9e6..8960032f 100644 --- a/lib/model.js +++ b/lib/model.js @@ -34,12 +34,7 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) { var self = this; var ctor = this.constructor; - var ds = { - properties: ctor.properties, - setters: ctor.settings - }; - - var properties = ds.properties; + var properties = ctor.properties; data = data || {}; Object.defineProperty(this, '__cachedRelations', { @@ -67,18 +62,29 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) { this.__cachedRelations = data['__cachedRelations']; } + // Check if the strict option is set to false for the model + var strict = ctor.settings.strict; + for (var i in data) { if (i in properties) { this.__data[i] = this.__dataWas[i] = data[i]; } else if (i in ctor.relations) { this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo]; this.__cachedRelations[i] = data[i]; + } else { + if(strict === false) { + this.__data[i] = this.__dataWas[i] = data[i]; + } else if(strict === 'throw') { + throw new Error('Unknown property: ' + i); + } } } if (applySetters === true) { Object.keys(data).forEach(function (attr) { - self[attr] = data[attr]; + if((attr in properties) || (attr in ctor.relations) || strict === false) { + self[attr] = data[attr]; + } }); } @@ -170,12 +176,13 @@ ModelBaseClass.prototype.toObject = function (onlySchema) { var properties = ds.properties; var self = this; + var schemaless = this.constructor.settings.strict === false || !onlySchema; this.constructor.forEachProperty(function (attr) { if (self[attr] instanceof List) { - data[attr] = self[attr].toObject(onlySchema); + data[attr] = self[attr].toObject(!schemaless); } else if (self.__data.hasOwnProperty(attr)) { if(self[attr] !== undefined && self[attr]!== null && self[attr].toObject) { - data[attr] = self[attr].toObject(onlySchema); + data[attr] = self[attr].toObject(!schemaless); } else { data[attr] = self[attr]; } @@ -184,10 +191,14 @@ ModelBaseClass.prototype.toObject = function (onlySchema) { } }); - if (!onlySchema) { + if (schemaless) { Object.keys(self).forEach(function (attr) { if (!data.hasOwnProperty(attr)) { - data[attr] = this[attr]; + if(self[attr] !== undefined && self[attr]!== null && self[attr].toObject) { + data[attr] = self[attr].toObject(!schemaless); + } else { + data[attr] = self[attr]; + } } }); } diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 00000000..4eee88ac --- /dev/null +++ b/lib/types.js @@ -0,0 +1,61 @@ +module.exports = function (Types) { + + var List = require('./list.js'); + var GeoPoint = require('./geo').GeoPoint; + + /** + * Schema types + */ + Types.Text = function Text(value) { + if (!(this instanceof Text)) { + return new Text(value); + } + this.value = value; + }; // Text type + + Types.Text.prototype.toObject = Types.Text.prototype.toJSON = function () { + return this.value; + }; + + Types.JSON = function JSON() { + if (!(this instanceof JSON)) { + return new JSON(value); + } + this.value = value; + }; // JSON Object + Types.JSON.prototype.toObject = Types.JSON.prototype.toJSON = function () { + return this.value; + }; + + Types.Any = function Any() { + if (!(this instanceof Any)) { + return new Any(value); + } + this.value = value; + }; // Any Type + Types.Any.prototype.toObject = Types.Any.prototype.toJSON = function () { + return this.value; + }; + + Types.schemaTypes = {}; + Types.registerType = function (type, names) { + names = names || []; + names = names.concat([type.name]); + for (var n = 0; n < names.length; n++) { + this.schemaTypes[names[n].toLowerCase()] = type; + } + }; + + Types.registerType(Types.Text); + Types.registerType(Types.JSON); + Types.registerType(Types.Any); + + Types.registerType(String); + Types.registerType(Number); + Types.registerType(Boolean); + Types.registerType(Date); + Types.registerType(Buffer, ['Binary']); + Types.registerType(Array); + Types.registerType(GeoPoint); + Types.registerType(Object); +} \ No newline at end of file diff --git a/test/jugglingdb.test.js b/test/loopback-data.test.js similarity index 100% rename from test/jugglingdb.test.js rename to test/loopback-data.test.js diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index ca8ea8b4..45abf3fb 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -39,6 +39,43 @@ describe('ModelBuilder define model', function () { done(null, User); }); + it('should not take unknown properties in strict mode', function (done) { + var modelBuilder = new ModelBuilder(); + + // simplier way to describe model + var User = modelBuilder.define('User', {name: String, bio: String}, {strict: true}); + + var user = new User({name: 'Joe', age: 20}); + // console.log(user); + + User.modelName.should.equal('User'); + user.should.be.a('object'); + // console.log(user); + assert(user.name === 'Joe'); + assert(user.age === undefined); + assert(user.toObject().age === undefined); + assert(user.toObject(true).age === undefined); + assert(user.bio === undefined); + done(null, User); + }); + + it('should be able to define open models', function (done) { + var modelBuilder = new ModelBuilder(); + + // simplier way to describe model + var User = modelBuilder.define('User', {}, {strict: false}); + + var user = new User({name: 'Joe', age: 20}); + // console.log(user); + + User.modelName.should.equal('User'); + user.should.be.a('object').and.have.property('name', 'Joe'); + user.should.have.property('name', 'Joe'); + user.should.have.property('age', 20); + user.should.not.have.property('bio'); + done(null, User); + }); + it('should be able to define nesting models', function (done) { var modelBuilder = new ModelBuilder();