From 803821e736478febf414053350b151b2c530d5e4 Mon Sep 17 00:00:00 2001 From: Giustino Borzacchiello Date: Mon, 20 Jan 2014 10:47:23 +0100 Subject: [PATCH 1/6] Fix jsdoc code examples formatting Some examples in the docs were not showing as code snippets, but as regular text. --- docs/datasource-connector.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/datasource-connector.md b/docs/datasource-connector.md index f2770534..a059f9ff 100644 --- a/docs/datasource-connector.md +++ b/docs/datasource-connector.md @@ -58,12 +58,15 @@ The `DataSource` constructor is available from `loopback-datasource-juggler` mod - connector: The name or instance of the connector module - settings: An object of properties to configure the connector - var dataSource = new DataSource({ - connector: require('loopback-connector-mongodb'), - host: 'localhost', - port: 27017, - database: 'mydb' - }); + +``` +var dataSource = new DataSource({ + connector: require('loopback-connector-mongodb'), + host: 'localhost', + port: 27017, + database: 'mydb' +}); +``` #### connector @@ -75,10 +78,11 @@ The `connector` argument passed the DataSource constructor can be one of the fol to 'loopback-connector-' * A local module under ./connectors/ folder - - var ds1 = new DataSource('memory'); - var ds2 = new DataSource('loopback-connector-mongodb')); - var ds3 = new DataSource(require('loopback-connector-oracle')); +``` +var ds1 = new DataSource('memory'); +var ds2 = new DataSource('loopback-connector-mongodb')); +var ds3 = new DataSource(require('loopback-connector-oracle')); +``` **Note**: LoopBack provides a built-in connector named as `memory` to use in-memory store for CRUD operations. From dbb7c6d9ae91ed2c7ca42b961277742f70afbbb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 21 Jan 2014 16:31:11 +0100 Subject: [PATCH 2/6] Fill ModelClass.http.path Set the HTTP route to `'/' + pluralModelName` so that we don't have to duplicate this bit of logic in strong-remoting and other places. --- lib/model-builder.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/model-builder.js b/lib/model-builder.js index 59a62a17..15c55323 100644 --- a/lib/model-builder.js +++ b/lib/model-builder.js @@ -109,7 +109,8 @@ ModelBuilder.prototype.getModelDefinition = function(name) { ModelBuilder.prototype.define = function defineClass(className, properties, settings, parent) { var modelBuilder = this; var args = slice.call(arguments); - var pluralName = settings && settings.plural; + var pluralName = (settings && settings.plural) || + inflection.pluralize(className); if (!className) { throw new Error('Class name required'); @@ -186,8 +187,9 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett // Add metadata to the ModelClass hiddenProperty(ModelClass, 'modelBuilder', modelBuilder); hiddenProperty(ModelClass, 'dataSource', modelBuilder); // Keep for back-compatibility - hiddenProperty(ModelClass, 'pluralModelName', pluralName || inflection.pluralize(className)); + hiddenProperty(ModelClass, 'pluralModelName', pluralName); hiddenProperty(ModelClass, 'relations', {}); + hiddenProperty(ModelClass, 'http', { path: '/' + pluralName }); // inherit ModelBaseClass static methods for (var i in ModelBaseClass) { From c374cc89cdc4381b7edc29565ffc8a3a247a7719 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 21 Jan 2014 09:47:32 -0800 Subject: [PATCH 3/6] Use the primary key type for the generated foreign key --- lib/datasource.js | 12 ++++++++++-- test/loopback-dl.test.js | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/datasource.js b/lib/datasource.js index f3a239ca..425796cb 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -1430,11 +1430,19 @@ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key // quit if key already defined if (this.getModelDefinition(className).rawProperties[key]) return; + var defaultType = Number; + if(foreignClassName) { + var foreignModel = this.getModelDefinition(foreignClassName); + var pkName = foreignModel && foreignModel.idName(); + if(pkName) { + defaultType = foreignModel.properties[pkName].type; + } + } if (this.connector.defineForeignKey) { var cb = function (err, keyType) { if (err) throw err; // Add the foreign key property to the data source _models - this.defineProperty(className, key, {type: keyType || Number}); + this.defineProperty(className, key, {type: keyType || defaultType}); }.bind(this); switch (this.connector.defineForeignKey.length) { case 4: @@ -1447,7 +1455,7 @@ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key } } else { // Add the foreign key property to the data source _models - this.defineProperty(className, key, {type: Number}); + this.defineProperty(className, key, {type: defaultType}); } }; diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index 81b7278c..65c0f892 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -510,6 +510,19 @@ describe('Load models with relations', function () { done(); }); + it('should set up foreign key with the correct type', function (done) { + var ds = new DataSource('memory'); + + var User = ds.define('User', {name: String, id: {type: String, id: true}}); + var Post = ds.define('Post', {content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + + var fk = Post.definition.properties['userId']; + assert(fk, 'The foreign key should be added'); + assert(fk.type === String, 'The foreign key should be the same type as primary key'); + assert(Post.relations['user'], 'User relation should be set'); + done(); + }); + it('should set up hasMany and belongsTo relations', function (done) { var ds = new DataSource('memory'); From 9391c6e14eb025e43f85c4cee160d6573458f942 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 23 Jan 2014 11:00:12 -0800 Subject: [PATCH 4/6] Improve links to docs --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c906219..1a14aa30 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ LoopBack DataSource Juggler is an ORM that provides a common set of interfaces for interacting with databases, REST APIs, and other data sources. It was initially forked from [JugglingDB](https://github.com/1602/jugglingdb). -For full documentation, see [StrongLoop Suite Documentation](http://docs.strongloop.com/display/DOC/Data+Source+Juggler). +**For full documentation, see the official StrongLoop documentation**: + * [Data sources and connectors](http://docs.strongloop.com/display/DOC/Data+sources+and+connectors) + * [Data Source Juggler](http://docs.strongloop.com/display/DOC/Data+Source+Juggler). ## Installation From 2b8c1ebaeec3d8be87da38236db3d8210bca6230 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 24 Jan 2014 09:09:53 -0800 Subject: [PATCH 5/6] Reformat the code --- examples/app-noschema.js | 90 +- examples/app.js | 26 +- examples/datasource-app.js | 72 +- examples/jdb-schemas.json | 26 +- examples/load-schemas.js | 26 +- examples/nesting-schema.js | 42 +- examples/relations.js | 81 +- examples/schemas.json | 158 +-- lib/connector.js | 64 +- lib/connectors/cradle.js | 495 ++++---- lib/connectors/http.js | 258 ++-- lib/connectors/memory.js | 500 ++++---- lib/connectors/neo4j.js | 571 ++++----- lib/connectors/riak.js | 122 +- lib/dao.js | 1130 +++++++++--------- lib/datasource.js | 1786 ++++++++++++++-------------- lib/geo.js | 76 +- lib/hooks.js | 56 +- lib/include.js | 246 ++-- lib/introspection.js | 104 +- lib/jutil.js | 136 +-- lib/list.js | 365 +++--- lib/model-builder.js | 840 +++++++------- lib/model-definition.js | 359 +++--- lib/model.js | 334 +++--- lib/relations.js | 451 +++---- lib/scope.js | 366 +++--- lib/sql.js | 249 ++-- lib/types.js | 104 +- lib/utils.js | 161 +-- lib/validations.js | 548 ++++----- test/basic-querying.test.js | 598 +++++----- test/common_test.js | 2065 +++++++++++++++++---------------- test/datatype.test.js | 108 +- test/defaults.test.js | 50 +- test/hooks.test.js | 750 ++++++------ test/include.test.js | 349 +++--- test/init.js | 24 +- test/introspection.test.js | 158 +-- test/json.test.js | 54 +- test/loopback-data.test.js | 8 +- test/loopback-dl.test.js | 1233 ++++++++++---------- test/manipulation.test.js | 468 ++++---- test/model-definition.test.js | 405 ++++--- test/performance.coffee | 127 +- test/relations.test.js | 460 ++++---- test/schema.test.js | 92 +- test/scope.test.js | 96 +- test/spec_helper.js | 66 +- test/test1-schemas.json | 26 +- test/test2-schemas.json | 158 +-- test/util.test.js | 144 ++- test/validations.test.js | 336 +++--- 53 files changed, 8825 insertions(+), 8792 deletions(-) diff --git a/examples/app-noschema.js b/examples/app-noschema.js index 9187159f..27fb4ec1 100644 --- a/examples/app-noschema.js +++ b/examples/app-noschema.js @@ -8,56 +8,56 @@ var ds = new DataSource('memory'); var Application = ds.createModel('Schemaless', {}, {strict: false}); var application = { - owner: 'rfeng', - name: 'MyApp1', - description: 'My first app', - pushSettings: [ - { "platform": "apns", - "apns": { - "pushOptions": { - "gateway": "gateway.sandbox.push.apple.com", - "cert": "credentials/apns_cert_dev.pem", - "key": "credentials/apns_key_dev.pem" - }, + owner: 'rfeng', + name: 'MyApp1', + description: 'My first app', + pushSettings: [ + { "platform": "apns", + "apns": { + "pushOptions": { + "gateway": "gateway.sandbox.push.apple.com", + "cert": "credentials/apns_cert_dev.pem", + "key": "credentials/apns_key_dev.pem" + }, - "feedbackOptions": { - "gateway": "feedback.sandbox.push.apple.com", - "cert": "credentials/apns_cert_dev.pem", - "key": "credentials/apns_key_dev.pem", - "batchFeedback": true, - "interval": 300 - } - }} - ]} + "feedbackOptions": { + "gateway": "feedback.sandbox.push.apple.com", + "cert": "credentials/apns_cert_dev.pem", + "key": "credentials/apns_key_dev.pem", + "batchFeedback": true, + "interval": 300 + } + }} + ]} console.log(new Application(application).toObject()); Application.create(application, function (err, app1) { - console.log('Created: ', app1.toObject()); - Application.findById(app1.id, function (err, app2) { - console.log('Found: ', app2.toObject()); - }); + console.log('Created: ', app1.toObject()); + Application.findById(app1.id, function (err, app2) { + console.log('Found: ', app2.toObject()); + }); }); // Instance JSON document var user = { - name: 'Joe', - age: 30, - birthday: new Date(), - vip: true, - address: { - street: '1 Main St', - city: 'San Jose', - state: 'CA', - zipcode: '95131', - country: 'US' - }, - friends: ['John', 'Mary'], - emails: [ - {label: 'work', id: 'x@sample.com'}, - {label: 'home', id: 'x@home.com'} - ], - tags: [] + name: 'Joe', + age: 30, + birthday: new Date(), + vip: true, + address: { + street: '1 Main St', + city: 'San Jose', + state: 'CA', + zipcode: '95131', + country: 'US' + }, + friends: ['John', 'Mary'], + emails: [ + {label: 'work', id: 'x@sample.com'}, + {label: 'home', id: 'x@home.com'} + ], + tags: [] }; // Introspect the JSON document to generate a schema @@ -72,10 +72,10 @@ var obj = new User(user); console.log(obj.toObject()); User.create(user, function (err, u1) { - console.log('Created: ', u1.toObject()); - User.findById(u1.id, function (err, u2) { - console.log('Found: ', u2.toObject()); - }); + console.log('Created: ', u1.toObject()); + User.findById(u1.id, function (err, u2) { + console.log('Found: ', u2.toObject()); + }); }); diff --git a/examples/app.js b/examples/app.js index a69d2cb9..f07719c0 100644 --- a/examples/app.js +++ b/examples/app.js @@ -2,27 +2,29 @@ var ModelBuilder = require('../../loopback-datasource-juggler').ModelBuilder; var modelBuilder = new ModelBuilder(); // define models var Post = modelBuilder.define('Post', { - title: { type: String, length: 255 }, - content: { type: ModelBuilder.Text }, - date: { type: Date, default: function () { return new Date;} }, - timestamp: { type: Number, default: Date.now }, - published: { type: Boolean, default: false, index: true } + title: { type: String, length: 255 }, + content: { type: ModelBuilder.Text }, + date: { type: Date, default: function () { + return new Date(); + } }, + timestamp: { type: Number, default: Date.now }, + published: { type: Boolean, default: false, index: true } }); -// simplier way to describe model +// simpler way to describe model var User = modelBuilder.define('User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number }); var Group = modelBuilder.define('Group', {group: String}); // define any custom method User.prototype.getNameAndAge = function () { - return this.name + ', ' + this.age; + return this.name + ', ' + this.age; }; var user = new User({name: 'Joe'}); diff --git a/examples/datasource-app.js b/examples/datasource-app.js index d22b0691..55f40cd0 100644 --- a/examples/datasource-app.js +++ b/examples/datasource-app.js @@ -4,29 +4,29 @@ var ds = new DataSource('memory'); // define models var Post = ds.define('Post', { - title: { type: String, length: 255 }, - content: { type: DataSource.Text }, - date: { type: Date, default: function () { - return new Date; - } }, - timestamp: { type: Number, default: Date.now }, - published: { type: Boolean, default: false, index: true } + title: { type: String, length: 255 }, + content: { type: DataSource.Text }, + date: { type: Date, default: function () { + return new Date; + } }, + timestamp: { type: Number, default: Date.now }, + published: { type: Boolean, default: false, index: true } }); // simplier way to describe model var User = ds.define('User', { - name: String, - bio: DataSource.Text, - approved: Boolean, - joinedAt: Date, - age: Number + name: String, + bio: DataSource.Text, + approved: Boolean, + joinedAt: Date, + age: Number }); var Group = ds.define('Group', {name: String}); // define any custom method User.prototype.getNameAndAge = function () { - return this.name + ', ' + this.age; + return this.name + ', ' + this.age; }; var user = new User({name: 'Joe'}); @@ -53,48 +53,48 @@ User.hasAndBelongsToMany('groups'); var user2 = new User({name: 'Smith'}); user2.save(function (err) { - console.log(user2); - var post = user2.posts.build({title: 'Hello world'}); - post.save(function(err, data) { - console.log(err ? err: data); - }); + console.log(user2); + var post = user2.posts.build({title: 'Hello world'}); + post.save(function (err, data) { + console.log(err ? err : data); + }); }); Post.findOne({where: {published: false}, order: 'date DESC'}, function (err, data) { - console.log(data); + console.log(data); }); User.create({name: 'Jeff'}, function (err, data) { - if (err) { - console.log(err); - return; - } - console.log(data); - var post = data.posts.build({title: 'My Post'}); - console.log(post); + if (err) { + console.log(err); + return; + } + console.log(data); + var post = data.posts.build({title: 'My Post'}); + console.log(post); }); User.create({name: 'Ray'}, function (err, data) { - console.log(data); + console.log(data); }); User.scope('minors', {age: {le: 16}}); -User.minors(function(err, kids) { - console.log('Kids: ', kids); +User.minors(function (err, kids) { + console.log('Kids: ', kids); }); var Article = ds.define('Article', {title: String}); var Tag = ds.define('Tag', {name: String}); Article.hasAndBelongsToMany('tags'); -Article.create(function(e, article) { - article.tags.create({name: 'popular'}, function (err, data) { - Article.findOne(function(e, article) { - article.tags(function(e, tags) { - console.log(tags); - }); - }); +Article.create(function (e, article) { + article.tags.create({name: 'popular'}, function (err, data) { + Article.findOne(function (e, article) { + article.tags(function (e, tags) { + console.log(tags); + }); }); + }); }); // should be able to attach a data source to an existing model diff --git a/examples/jdb-schemas.json b/examples/jdb-schemas.json index 05dd5079..e18f0f89 100644 --- a/examples/jdb-schemas.json +++ b/examples/jdb-schemas.json @@ -1,15 +1,15 @@ { - "title": { - "type": "String" - }, - "author": { - "type": "String", - "default": "Raymond" - }, - "body": "String", - "date": { - "type": "Date" - }, - "hidden": "Boolean", - "comments": ["String"] + "title": { + "type": "String" + }, + "author": { + "type": "String", + "default": "Raymond" + }, + "body": "String", + "date": { + "type": "Date" + }, + "hidden": "Boolean", + "comments": ["String"] } \ No newline at end of file diff --git a/examples/load-schemas.js b/examples/load-schemas.js index b181f249..75bf05d0 100644 --- a/examples/load-schemas.js +++ b/examples/load-schemas.js @@ -1,6 +1,6 @@ var path = require('path'), - fs = require('fs'), - DataSource = require('../lib/datasource').DataSource; + fs = require('fs'), + DataSource = require('../lib/datasource').DataSource; /** * Load LDL schemas from a json doc @@ -8,27 +8,27 @@ var path = require('path'), * @returns A map of schemas keyed by name */ function loadSchemasSync(schemaFile, dataSource) { - // Set up the data source - if(!dataSource) { - dataSource = new DataSource('memory'); - } + // Set up the data source + if (!dataSource) { + dataSource = new DataSource('memory'); + } - // Read the dataSource JSON file - var schemas = JSON.parse(fs.readFileSync(schemaFile)); + // Read the dataSource JSON file + var schemas = JSON.parse(fs.readFileSync(schemaFile)); - return dataSource.buildModels(schemas); + return dataSource.buildModels(schemas); } var models = loadSchemasSync(path.join(__dirname, 'jdb-schemas.json')); for (var s in models) { - var m = models[s]; - console.log(m.modelName, new m()); + var m = models[s]; + console.log(m.modelName, new m()); } models = loadSchemasSync(path.join(__dirname, 'schemas.json')); for (var s in models) { - var m = models[s]; - console.log(m.modelName, new m()); + var m = models[s]; + console.log(m.modelName, new m()); } diff --git a/examples/nesting-schema.js b/examples/nesting-schema.js index bbb85cd6..3ae83684 100644 --- a/examples/nesting-schema.js +++ b/examples/nesting-schema.js @@ -3,26 +3,30 @@ var modelBuilder = new ModelBuilder(); // simplier way to describe model var User = modelBuilder.define('User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number, - address: { - street: String, - city: String, - state: String, - zipCode: String, - country: String - }, - emails: [{ - label: String, - email: String - }], - friends: [String] + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number, + address: { + street: String, + city: String, + state: String, + zipCode: String, + country: String + }, + emails: [ + { + label: String, + email: String + } + ], + friends: [String] }); var user = new User({name: 'Joe', age: 20, address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}, - emails: [{label: 'work', email: 'xyz@sample.com'}], - friends: ['John', 'Mary']}); + emails: [ + {label: 'work', email: 'xyz@sample.com'} + ], + friends: ['John', 'Mary']}); console.log(user.toObject()); diff --git a/examples/relations.js b/examples/relations.js index 3a717dfe..04973f90 100644 --- a/examples/relations.js +++ b/examples/relations.js @@ -2,58 +2,56 @@ var DataSource = require('../index').DataSource; var ds = new DataSource('memory'); var Order = ds.createModel('Order', { - customerId: Number, - orderDate: Date + customerId: Number, + orderDate: Date }); var Customer = ds.createModel('Customer', { - name: String + name: String }); Order.belongsTo(Customer); Customer.create({name: 'John'}, function (err, customer) { - Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) { - order.customer(console.log); - order.customer(true, console.log); + Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) { + order.customer(console.log); + order.customer(true, console.log); - Customer.create({name: 'Mary'}, function (err, customer2) { - order.customer(customer2); - order.customer(console.log); - }); + Customer.create({name: 'Mary'}, function (err, customer2) { + order.customer(customer2); + order.customer(console.log); }); + }); }); - Customer.hasMany(Order, {as: 'orders', foreignKey: 'customerId'}); Customer.create({name: 'Ray'}, function (err, customer) { - Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) { - customer.orders(console.log); - customer.orders.create({orderDate: new Date()}, function(err, order) { - console.log(order); - Customer.include([customer], 'orders', function(err, results) { - console.log('Results: ', results); - }); - customer.orders.findById('2', console.log); - customer.orders.destroy('2', console.log); - }); + Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) { + customer.orders(console.log); + customer.orders.create({orderDate: new Date()}, function (err, order) { + console.log(order); + Customer.include([customer], 'orders', function (err, results) { + console.log('Results: ', results); + }); + customer.orders.findById('2', console.log); + customer.orders.destroy('2', console.log); }); + }); }); - var Physician = ds.createModel('Physician', { - name: String + name: String }); var Patient = ds.createModel('Patient', { - name: String + name: String }); var Appointment = ds.createModel('Appointment', { - physicianId: Number, - patientId: Number, - appointmentDate: Date + physicianId: Number, + patientId: Number, + appointmentDate: Date }); Appointment.belongsTo(Patient); @@ -63,33 +61,32 @@ Physician.hasMany(Patient, {through: Appointment}); Patient.hasMany(Physician, {through: Appointment}); Physician.create({name: 'Smith'}, function (err, physician) { - Patient.create({name: 'Mary'}, function (err, patient) { - Appointment.create({appointmentDate: new Date(), physicianId: physician.id, patientId: patient.id}, - function (err, appt) { - physician.patients(console.log); - patient.physicians(console.log); - }); - }); + Patient.create({name: 'Mary'}, function (err, patient) { + Appointment.create({appointmentDate: new Date(), physicianId: physician.id, patientId: patient.id}, + function (err, appt) { + physician.patients(console.log); + patient.physicians(console.log); + }); + }); }); - var Assembly = ds.createModel('Assembly', { - name: String + name: String }); var Part = ds.createModel('Part', { - partNumber: String + partNumber: String }); Assembly.hasAndBelongsToMany(Part); Part.hasAndBelongsToMany(Assembly); Assembly.create({name: 'car'}, function (err, assembly) { - Part.create({partNumber: 'engine'}, function (err, part) { - assembly.parts.add(part, function(err) { - assembly.parts(console.log); - }); - + Part.create({partNumber: 'engine'}, function (err, part) { + assembly.parts.add(part, function (err) { + assembly.parts(console.log); }); + + }); }); diff --git a/examples/schemas.json b/examples/schemas.json index 0ca01ff5..c8de32ba 100644 --- a/examples/schemas.json +++ b/examples/schemas.json @@ -1,83 +1,83 @@ [ - { - "name": "Address", - "properties": { - "label": "string", - "street": "string", - "city": "string", - "zipCode": "string" - } - }, - - { - "name": "Account", - "properties": { - "id": "string", - "customer": { - "type": "Customer", - "relation": { - "type": "belongsTo", - "as": "account" - } - }, - "balance": "number" - } - }, - - { - "name": "Customer", - "options": { - "oracle": { - "owner": "STRONGLOOP", - "table": "CUSTOMER" - } - }, - "properties": { - "id": { - "type": "number", - "id": true, - "doc": "Customer ID" - }, - "firstName": { - "type": "string", - "trim": true, - "required": true, - "oracle": { - "column": "FNAME", - "type": "VARCHAR", - "length": 32 - } - }, - "lastName": { - "type": "string", - "trim": true, - "required": true, - "oracle": { - "column": "LNAME", - "type": "VARCHAR", - "length": 32 - } - }, - "vip": { - "type": "boolean", - "doc": "indicate if the customer is a VIP", - "oracle": { - "column": "VIP", - "type": "CHAR", - "length": 1 - } - }, - "emails": [ - { - "type": "string", - "trim": true - } - ], - "address": { - "type": "Address" - }, - "account": "Account" - } + { + "name": "Address", + "properties": { + "label": "string", + "street": "string", + "city": "string", + "zipCode": "string" } + }, + + { + "name": "Account", + "properties": { + "id": "string", + "customer": { + "type": "Customer", + "relation": { + "type": "belongsTo", + "as": "account" + } + }, + "balance": "number" + } + }, + + { + "name": "Customer", + "options": { + "oracle": { + "owner": "STRONGLOOP", + "table": "CUSTOMER" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "doc": "Customer ID" + }, + "firstName": { + "type": "string", + "trim": true, + "required": true, + "oracle": { + "column": "FNAME", + "type": "VARCHAR", + "length": 32 + } + }, + "lastName": { + "type": "string", + "trim": true, + "required": true, + "oracle": { + "column": "LNAME", + "type": "VARCHAR", + "length": 32 + } + }, + "vip": { + "type": "boolean", + "doc": "indicate if the customer is a VIP", + "oracle": { + "column": "VIP", + "type": "CHAR", + "length": 1 + } + }, + "emails": [ + { + "type": "string", + "trim": true + } + ], + "address": { + "type": "Address" + }, + "account": "Account" + } + } ] \ No newline at end of file diff --git a/lib/connector.js b/lib/connector.js index 9587dda0..30acc763 100644 --- a/lib/connector.js +++ b/lib/connector.js @@ -6,9 +6,9 @@ module.exports = Connector; * @constructor */ function Connector(name, settings) { - this._models = {}; - this.name = name; - this.settings = settings || {}; + this._models = {}; + this.name = name; + this.settings = settings || {}; } /** @@ -24,21 +24,20 @@ Connector.prototype.relational = false; * @param {Function} [callback] The callback function */ Connector.prototype.execute = function (command, params, callback) { - throw new Error('query method should be declared in connector'); + throw new Error('query method should be declared in connector'); }; - /** * Look up the data source by model name * @param {String} model The model name * @returns {DataSource} The data source */ -Connector.prototype.getDataSource = function(model) { - var m = this._models[model]; - if(!m) { - console.trace('Model not found: ' + model); - } - return m && m.model.dataSource; +Connector.prototype.getDataSource = function (model) { + var m = this._models[model]; + if (!m) { + console.trace('Model not found: ' + model); + } + return m && m.model.dataSource; }; /** @@ -47,7 +46,7 @@ Connector.prototype.getDataSource = function(model) { * @returns {String} The id property name */ Connector.prototype.idName = function (model) { - return this.getDataSource(model).idName(model); + return this.getDataSource(model).idName(model); }; /** @@ -56,10 +55,9 @@ Connector.prototype.idName = function (model) { * @returns {[String]} The id property names */ Connector.prototype.idNames = function (model) { - return this.getDataSource(model).idNames(model); + return this.getDataSource(model).idNames(model); }; - /** * Get the id index (sequence number, starting from 1) * @param {String} model The model name @@ -67,11 +65,11 @@ Connector.prototype.idNames = function (model) { * @returns {Number} The id index, undefined if the property is not part of the primary key */ Connector.prototype.id = function (model, prop) { - var p = this._models[model].properties[prop]; - if(!p) { - console.trace('Property not found: ' + model +'.' + prop); - } - return p.id; + var p = this._models[model].properties[prop]; + if (!p) { + console.trace('Property not found: ' + model + '.' + prop); + } + return p.id; }; /** @@ -79,10 +77,10 @@ Connector.prototype.id = function (model, prop) { * @param {Object} modelDefinition The model definition */ Connector.prototype.define = function (modelDefinition) { - if (!modelDefinition.settings) { - modelDefinition.settings = {}; - } - this._models[modelDefinition.model.modelName] = modelDefinition; + if (!modelDefinition.settings) { + modelDefinition.settings = {}; + } + this._models[modelDefinition.model.modelName] = modelDefinition; }; /** @@ -92,14 +90,14 @@ Connector.prototype.define = function (modelDefinition) { * @param {Object} propertyDefinition The object for property metadata */ Connector.prototype.defineProperty = function (model, propertyName, propertyDefinition) { - this._models[model].properties[propertyName] = propertyDefinition; + this._models[model].properties[propertyName] = propertyDefinition; }; /** * Disconnect from the connector */ Connector.prototype.disconnect = function disconnect(cb) { - // NO-OP + // NO-OP }; /** @@ -109,8 +107,8 @@ Connector.prototype.disconnect = function disconnect(cb) { * @returns {*} The id value * */ -Connector.prototype.getIdValue = function(model, data) { - return data && data[this.idName(model)]; +Connector.prototype.getIdValue = function (model, data) { + return data && data[this.idName(model)]; }; /** @@ -120,10 +118,14 @@ Connector.prototype.getIdValue = function(model, data) { * @param {*} value The id value * */ -Connector.prototype.setIdValue = function(model, data, value) { - if(data) { - data[this.idName(model)] = value; - } +Connector.prototype.setIdValue = function (model, data, value) { + if (data) { + data[this.idName(model)] = value; + } +}; + +Connector.prototype.getType = function () { + return this.type; }; diff --git a/lib/connectors/cradle.js b/lib/connectors/cradle.js index e34cbc4f..89d9d8ed 100644 --- a/lib/connectors/cradle.js +++ b/lib/connectors/cradle.js @@ -9,328 +9,335 @@ var cradle = safeRequire('cradle'); * Private functions for internal use */ function CradleAdapter(client) { - this._models = {}; - this.client = client; + this._models = {}; + this.client = client; } function createdbif(client, callback) { - client.exists(function (err, exists) { - if(err) callback(err); - if (!exists) { client.create(function() { callback(); }); } - else { callback(); } - }); + client.exists(function (err, exists) { + if (err) callback(err); + if (!exists) { + client.create(function () { + callback(); + }); + } + else { + callback(); + } + }); } function naturalize(data, model) { - data.nature = model; - //TODO: maybe this is not a really good idea - if(data.date) data.date = data.date.toString(); - return data; + data.nature = model; + //TODO: maybe this is not a really good idea + if (data.date) data.date = data.date.toString(); + return data; } function idealize(data) { - data.id = data._id; - return data; + data.id = data._id; + return data; } function stringify(data) { - return data ? data.toString() : data + return data ? data.toString() : data } function errorHandler(callback, func) { - return function(err, res) { - if (err) { - console.log('cradle', err); - callback(err); - } else { - if(func) { - func(res, function(res) { - callback(null, res); - }); - } else { - callback(null, res); - } - } + return function (err, res) { + if (err) { + console.log('cradle', err); + callback(err); + } else { + if (func) { + func(res, function (res) { + callback(null, res); + }); + } else { + callback(null, res); + } } + } }; function synchronize(functions, args, callback) { - if(functions.length === 0) callback(); - if(functions.length > 0 && args.length === functions.length) { - functions[0](args[0][0], args[0][1], function(err, res) { - if(err) callback(err); - functions.splice(0, 1); - args.splice(0, 1); - synchronize(functions, args, callback); - }); - } + if (functions.length === 0) callback(); + if (functions.length > 0 && args.length === functions.length) { + functions[0](args[0][0], args[0][1], function (err, res) { + if (err) callback(err); + functions.splice(0, 1); + args.splice(0, 1); + synchronize(functions, args, callback); + }); + } }; function applyFilter(filter) { - if (typeof filter.where === 'function') { - return filter.where; - } - var keys = Object.keys(filter.where); - return function (obj) { - var pass = true; - keys.forEach(function (key) { - if (!test(filter.where[key], obj[key])) { - pass = false; - } - }); - return pass; - } + if (typeof filter.where === 'function') { + return filter.where; + } + var keys = Object.keys(filter.where); + return function (obj) { + var pass = true; + keys.forEach(function (key) { + if (!test(filter.where[key], obj[key])) { + pass = false; + } + }); + return pass; + } - function test(example, value) { - if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { - return value.match(example); - } - // not strict equality - return example == value; + function test(example, value) { + if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { + return value.match(example); } + // not strict equality + return example == value; + } } function numerically(a, b) { - return a[this[0]] - b[this[0]]; + return a[this[0]] - b[this[0]]; } function literally(a, b) { - return a[this[0]] > b[this[0]]; + return a[this[0]] > b[this[0]]; } function filtering(res, model, filter, instance) { - if(model) { - if(filter == null) filter = {}; - if(filter.where == null) filter.where = {}; - filter.where.nature = model; - } - // do we need some filtration? - if (filter.where) { - res = res ? res.filter(applyFilter(filter)) : res; - } + if (model) { + if (filter == null) filter = {}; + if (filter.where == null) filter.where = {}; + filter.where.nature = model; + } + // do we need some filtration? + if (filter.where) { + res = res ? res.filter(applyFilter(filter)) : res; + } - // do we need some sorting? - if (filter.order) { - var props = instance[model].properties; - var allNumeric = true; - var orders = filter.order; - var reverse = false; - if (typeof filter.order === "string") { - orders = [filter.order]; - } + // do we need some sorting? + if (filter.order) { + var props = instance[model].properties; + var allNumeric = true; + var orders = filter.order; + var reverse = false; + if (typeof filter.order === "string") { + orders = [filter.order]; + } - orders.forEach(function (key, i) { - var m = key.match(/\s+(A|DE)SC$/i); - if (m) { - key = key.replace(/\s+(A|DE)SC/i, ''); - if (m[1] === 'DE') reverse = true; - } - orders[i] = key; - if (props[key].type.name !== 'Number') { - allNumeric = false; - } - }); - if (allNumeric) { - res = res.sort(numerically.bind(orders)); - } else { - res = res.sort(literally.bind(orders)); + orders.forEach(function (key, i) { + var m = key.match(/\s+(A|DE)SC$/i); + if (m) { + key = key.replace(/\s+(A|DE)SC/i, ''); + if (m[1] === 'DE') reverse = true; } - if (reverse) res = res.reverse(); - } - return res; + orders[i] = key; + if (props[key].type.name !== 'Number') { + allNumeric = false; + } + }); + if (allNumeric) { + res = res.sort(numerically.bind(orders)); + } else { + res = res.sort(literally.bind(orders)); + } + if (reverse) res = res.reverse(); + } + return res; } /** * Connection/Disconnection */ -exports.initialize = function(dataSource, callback) { - if (!cradle) return; +exports.initialize = function (dataSource, callback) { + if (!cradle) return; - // when using cradle if we dont wait for the dataSource to be connected, the models fails to load correctly. - dataSource.waitForConnect = true; - if (!dataSource.settings.url) { - var host = dataSource.settings.host || 'localhost'; - var port = dataSource.settings.port || '5984'; - var options = dataSource.settings.options || { - cache: true, - raw: false - }; - if (dataSource.settings.username) { - options.auth = {}; - options.auth.username = dataSource.settings.username; - if (dataSource.settings.password) { - options.auth.password = dataSource.settings.password; - } - } - var database = dataSource.settings.database || 'loopback-datasource-juggler'; - - dataSource.settings.host = host; - dataSource.settings.port = port; - dataSource.settings.database = database; - dataSource.settings.options = options; + // when using cradle if we dont wait for the dataSource to be connected, the models fails to load correctly. + dataSource.waitForConnect = true; + if (!dataSource.settings.url) { + var host = dataSource.settings.host || 'localhost'; + var port = dataSource.settings.port || '5984'; + var options = dataSource.settings.options || { + cache: true, + raw: false + }; + if (dataSource.settings.username) { + options.auth = {}; + options.auth.username = dataSource.settings.username; + if (dataSource.settings.password) { + options.auth.password = dataSource.settings.password; + } } - dataSource.client = new(cradle.Connection)(dataSource.settings.host, dataSource.settings.port,dataSource.settings.options).database(dataSource.settings.database); + var database = dataSource.settings.database || 'loopback-datasource-juggler'; - createdbif( - dataSource.client, - errorHandler(callback, function() { - dataSource.connector = new CradleAdapter(dataSource.client); - process.nextTick(callback); - })); + dataSource.settings.host = host; + dataSource.settings.port = port; + dataSource.settings.database = database; + dataSource.settings.options = options; + } + dataSource.client = new (cradle.Connection)(dataSource.settings.host, dataSource.settings.port, dataSource.settings.options).database(dataSource.settings.database); + + createdbif( + dataSource.client, + errorHandler(callback, function () { + dataSource.connector = new CradleAdapter(dataSource.client); + process.nextTick(callback); + })); }; -CradleAdapter.prototype.disconnect = function() { +CradleAdapter.prototype.disconnect = function () { }; /** * Write methods */ -CradleAdapter.prototype.define = function(descr) { - this._models[descr.model.modelName] = descr; +CradleAdapter.prototype.define = function (descr) { + this._models[descr.model.modelName] = descr; }; -CradleAdapter.prototype.create = function(model, data, callback) { - this.client.save( - stringify(data.id), - naturalize(data, model), - errorHandler(callback, function(res, cb) { - cb(res.id); - }) - ); +CradleAdapter.prototype.create = function (model, data, callback) { + this.client.save( + stringify(data.id), + naturalize(data, model), + errorHandler(callback, function (res, cb) { + cb(res.id); + }) + ); }; -CradleAdapter.prototype.save = function(model, data, callback) { - this.client.save( - stringify(data.id), - naturalize(data, model), - errorHandler(callback) - ) +CradleAdapter.prototype.save = function (model, data, callback) { + this.client.save( + stringify(data.id), + naturalize(data, model), + errorHandler(callback) + ) }; -CradleAdapter.prototype.updateAttributes = function(model, id, data, callback) { - this.client.merge( - stringify(id), - data, - errorHandler(callback, function(doc, cb) { - cb(idealize(doc)); - }) - ); +CradleAdapter.prototype.updateAttributes = function (model, id, data, callback) { + this.client.merge( + stringify(id), + data, + errorHandler(callback, function (doc, cb) { + cb(idealize(doc)); + }) + ); }; -CradleAdapter.prototype.updateOrCreate = function(model, data, callback) { - this.client.get( - stringify(data.id), - function (err, doc) { - if(err) { - this.create(model, data, callback); - } else { - this.updateAttributes(model, data.id, data, callback); - } - }.bind(this) - ) +CradleAdapter.prototype.updateOrCreate = function (model, data, callback) { + this.client.get( + stringify(data.id), + function (err, doc) { + if (err) { + this.create(model, data, callback); + } else { + this.updateAttributes(model, data.id, data, callback); + } + }.bind(this) + ) }; /** * Read methods */ -CradleAdapter.prototype.exists = function(model, id, callback) { - this.client.get( - stringify(id), - errorHandler(callback, function(doc, cb) { - cb(!!doc); - }) - ); +CradleAdapter.prototype.exists = function (model, id, callback) { + this.client.get( + stringify(id), + errorHandler(callback, function (doc, cb) { + cb(!!doc); + }) + ); }; -CradleAdapter.prototype.find = function(model, id, callback) { - this.client.get( - stringify(id), - errorHandler(callback, function(doc, cb) { - cb(idealize(doc)); - }) - ); +CradleAdapter.prototype.find = function (model, id, callback) { + this.client.get( + stringify(id), + errorHandler(callback, function (doc, cb) { + cb(idealize(doc)); + }) + ); }; -CradleAdapter.prototype.count = function(model, callback, where) { - this.models( - model, - {where: where}, - callback, - function(docs, cb) { - cb(docs.length); - } - ); -}; - -CradleAdapter.prototype.models = function(model, filter, callback, func) { - var limit = 200; - var skip = 0; - if (filter != null) { - limit = filter.limit || limit; - skip = filter.skip ||skip; +CradleAdapter.prototype.count = function (model, callback, where) { + this.models( + model, + {where: where}, + callback, + function (docs, cb) { + cb(docs.length); } - - var self = this; - - self.client.save('_design/'+model, { - views : { - all : { - map : 'function(doc) { if (doc.nature == "'+model+'") { emit(doc._id, doc); } }' - } - } - }, function() { - self.client.view(model+'/all', {include_docs:true, limit:limit, skip:skip}, errorHandler(callback, function(res, cb) { - var docs = res.map(function(doc) { - return idealize(doc); - }); - var filtered = filtering(docs, model, filter, this._models) - - func ? func(filtered, cb) : cb(filtered); - }.bind(self))); - }); + ); }; -CradleAdapter.prototype.all = function(model, filter, callback) { - this.models( - model, - filter, - callback - ); +CradleAdapter.prototype.models = function (model, filter, callback, func) { + var limit = 200; + var skip = 0; + if (filter != null) { + limit = filter.limit || limit; + skip = filter.skip || skip; + } + + var self = this; + + self.client.save('_design/' + model, { + views: { + all: { + map: 'function(doc) { if (doc.nature == "' + model + '") { emit(doc._id, doc); } }' + } + } + }, function () { + self.client.view(model + '/all', {include_docs: true, limit: limit, skip: skip}, + errorHandler(callback, function (res, cb) { + var docs = res.map(function (doc) { + return idealize(doc); + }); + var filtered = filtering(docs, model, filter, this._models) + + func ? func(filtered, cb) : cb(filtered); + }.bind(self))); + }); +}; + +CradleAdapter.prototype.all = function (model, filter, callback) { + this.models( + model, + filter, + callback + ); }; /** * Detroy methods */ -CradleAdapter.prototype.destroy = function(model, id, callback) { - this.client.remove( - stringify(id), - function (err, doc) { - callback(err); - } - ); +CradleAdapter.prototype.destroy = function (model, id, callback) { + this.client.remove( + stringify(id), + function (err, doc) { + callback(err); + } + ); }; -CradleAdapter.prototype.destroyAll = function(model, callback) { - this.models( - model, - null, - callback, - function(docs, cb) { - var docIds = docs.map(function(doc) { - return doc.id; - }); - this.client.get(docIds, function(err, res) { - if(err) cb(err); +CradleAdapter.prototype.destroyAll = function (model, callback) { + this.models( + model, + null, + callback, + function (docs, cb) { + var docIds = docs.map(function (doc) { + return doc.id; + }); + this.client.get(docIds, function (err, res) { + if (err) cb(err); - var funcs = res.map(function(doc) { - return this.client.remove.bind(this.client); - }.bind(this)); + var funcs = res.map(function (doc) { + return this.client.remove.bind(this.client); + }.bind(this)); - var args = res.map(function(doc) { - return [doc._id, doc._rev]; - }); + var args = res.map(function (doc) { + return [doc._id, doc._rev]; + }); - synchronize(funcs, args, cb); - }.bind(this)); - }.bind(this) - ); + synchronize(funcs, args, cb); + }.bind(this)); + }.bind(this) + ); }; diff --git a/lib/connectors/http.js b/lib/connectors/http.js index 456290f4..22f9f314 100644 --- a/lib/connectors/http.js +++ b/lib/connectors/http.js @@ -1,188 +1,190 @@ exports.initialize = function initializeSchema(dataSource, callback) { - dataSource.connector = new WebService(); - process.nextTick(callback); + dataSource.connector = new WebService(); + process.nextTick(callback); }; function WebService() { - this._models = {}; - this.cache = {}; - this.ids = {}; + this._models = {}; + this.cache = {}; + this.ids = {}; } WebService.prototype.installPostProcessor = function installPostProcessor(descr) { - var dates = []; - Object.keys(descr.properties).forEach(function(column) { - if (descr.properties[column].type.name === 'Date') { - dates.push(column); - } - }); + var dates = []; + Object.keys(descr.properties).forEach(function (column) { + if (descr.properties[column].type.name === 'Date') { + dates.push(column); + } + }); - var postProcessor = function(model) { - var max = dates.length; - for (var i = 0; i < max; i++) { - var column = dates[i]; - if (model[column]) { - model[column] = new Date(model[column]); - } - }; - }; + var postProcessor = function (model) { + var max = dates.length; + for (var i = 0; i < max; i++) { + var column = dates[i]; + if (model[column]) { + model[column] = new Date(model[column]); + } + } + ; + }; - descr.postProcessor = postProcessor; + descr.postProcessor = postProcessor; }; WebService.prototype.preProcess = function preProcess(data) { - var result = {}; - Object.keys(data).forEach(function(key) { - if (data[key] != null) { - result[key] = data[key]; - } - }) - return result; + var result = {}; + Object.keys(data).forEach(function (key) { + if (data[key] != null) { + result[key] = data[key]; + } + }) + return result; }; WebService.prototype.postProcess = function postProcess(model, data) { - var postProcessor = this._models[model].postProcessor; - if (postProcessor && data) { - postProcessor(data); - } + var postProcessor = this._models[model].postProcessor; + if (postProcessor && data) { + postProcessor(data); + } }; WebService.prototype.postProcessMultiple = function postProcessMultiple(model, data) { - var postProcessor = this._models[model].postProcessor; - if (postProcessor) { - var max = data.length; - for (var i = 0; i < max; i++) { - if (data[i]) { - postProcessor(data[i]); - } - }; + var postProcessor = this._models[model].postProcessor; + if (postProcessor) { + var max = data.length; + for (var i = 0; i < max; i++) { + if (data[i]) { + postProcessor(data[i]); + } } + ; + } }; WebService.prototype.define = function defineModel(descr) { - var m = descr.model.modelName; - this.installPostProcessor(descr); - this._models[m] = descr; + var m = descr.model.modelName; + this.installPostProcessor(descr); + this._models[m] = descr; }; WebService.prototype.getResourceUrl = function getResourceUrl(model) { - var url = this._models[model].settings.restPath; - if (!url) throw new Error('Resource url (restPath) for ' + model + ' is not defined'); - return url; + var url = this._models[model].settings.restPath; + if (!url) throw new Error('Resource url (restPath) for ' + model + ' is not defined'); + return url; }; WebService.prototype.getBlankReq = function () { - if (!this.csrfToken) { - this.csrfToken = $('meta[name=csrf-token]').attr('content'); - this.csrfParam = $('meta[name=csrf-param]').attr('content'); - } - var req = {}; - req[this.csrfParam] = this.csrfToken; - return req; + if (!this.csrfToken) { + this.csrfToken = $('meta[name=csrf-token]').attr('content'); + this.csrfParam = $('meta[name=csrf-param]').attr('content'); + } + var req = {}; + req[this.csrfParam] = this.csrfToken; + return req; } WebService.prototype.create = function create(model, data, callback) { - var req = this.getBlankReq(); - req[model] = this.preProcess(data); - $.post(this.getResourceUrl(model) + '.json', req, function (res) { - if (res.code === 200) { - callback(null, res.data.id); - } else { - callback(res.error); - } - }, 'json'); - // this.cache[model][id] = data; + var req = this.getBlankReq(); + req[model] = this.preProcess(data); + $.post(this.getResourceUrl(model) + '.json', req, function (res) { + if (res.code === 200) { + callback(null, res.data.id); + } else { + callback(res.error); + } + }, 'json'); + // this.cache[model][id] = data; }; WebService.prototype.updateOrCreate = function (model, data, callback) { - var mem = this; - this.exists(model, data.id, function (err, exists) { - if (exists) { - mem.save(model, data, callback); - } else { - mem.create(model, data, function (err, id) { - data.id = id; - callback(err, data); - }); - } - }); + var mem = this; + this.exists(model, data.id, function (err, exists) { + if (exists) { + mem.save(model, data, callback); + } else { + mem.create(model, data, function (err, id) { + data.id = id; + callback(err, data); + }); + } + }); }; WebService.prototype.save = function save(model, data, callback) { - var _this = this; - var req = this.getBlankReq(); - req._method = 'PUT'; - req[model] = this.preProcess(data); - $.post(this.getResourceUrl(model) + '/' + data.id + '.json', req, function (res) { - if (res.code === 200) { - _this.postProcess(model, res.data); - callback(null, res.data); - } else { - callback(res.error); - } - }, 'json'); + var _this = this; + var req = this.getBlankReq(); + req._method = 'PUT'; + req[model] = this.preProcess(data); + $.post(this.getResourceUrl(model) + '/' + data.id + '.json', req, function (res) { + if (res.code === 200) { + _this.postProcess(model, res.data); + callback(null, res.data); + } else { + callback(res.error); + } + }, 'json'); }; WebService.prototype.exists = function exists(model, id, callback) { - $.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) { - if (res.code === 200) { - callback(null, true); - } else if (res.code === 404) { - callback(null, false); - } else { - callback(res.error); - } - }); + $.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) { + if (res.code === 200) { + callback(null, true); + } else if (res.code === 404) { + callback(null, false); + } else { + callback(res.error); + } + }); }; WebService.prototype.find = function find(model, id, callback) { - var _this = this; - $.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) { - if (res.code === 200) { - _this.postProcess(model, res.data); - callback(null, res.data); - } else { - callback(res.error); - } - }); + var _this = this; + $.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) { + if (res.code === 200) { + _this.postProcess(model, res.data); + callback(null, res.data); + } else { + callback(res.error); + } + }); }; WebService.prototype.destroy = function destroy(model, id, callback) { - var _this = this; - var req = this.getBlankReq(); - req._method = 'DELETE'; - $.post(this.getResourceUrl(model) + '/' + id + '.json', req, function (res) { - if (res.code === 200) { - //delete _this.cache[model][id]; - callback(null, res.data); - } else { - callback(res.error); - } - }, 'json'); + var _this = this; + var req = this.getBlankReq(); + req._method = 'DELETE'; + $.post(this.getResourceUrl(model) + '/' + id + '.json', req, function (res) { + if (res.code === 200) { + //delete _this.cache[model][id]; + callback(null, res.data); + } else { + callback(res.error); + } + }, 'json'); }; WebService.prototype.all = function all(model, filter, callback) { - var _this = this; - $.getJSON(this.getResourceUrl(model) + '.json?query=' + encodeURIComponent(JSON.stringify(filter)), function (res) { - if (res.code === 200) { - _this.postProcessMultiple(model, res.data); - callback(null, res.data); - } else { - callback(res.error); - } - }); + var _this = this; + $.getJSON(this.getResourceUrl(model) + '.json?query=' + encodeURIComponent(JSON.stringify(filter)), function (res) { + if (res.code === 200) { + _this.postProcessMultiple(model, res.data); + callback(null, res.data); + } else { + callback(res.error); + } + }); }; WebService.prototype.destroyAll = function destroyAll(model, callback) { - throw new Error('Not supported'); + throw new Error('Not supported'); }; WebService.prototype.count = function count(model, callback, where) { - throw new Error('Not supported'); + throw new Error('Not supported'); }; WebService.prototype.updateAttributes = function (model, id, data, callback) { - data.id = id; - this.save(model, data, callback); + data.id = id; + this.save(model, data, callback); }; diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index 61aae072..72e0e125 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -10,317 +10,317 @@ var utils = require('../utils'); * @param {Function} [callback] The callback function */ exports.initialize = function initializeDataSource(dataSource, callback) { - dataSource.connector = new Memory(); - dataSource.connector.connect(callback); + dataSource.connector = new Memory(); + dataSource.connector.connect(callback); }; exports.Memory = Memory; function Memory(m) { - if (m) { - this.isTransaction = true; - this.cache = m.cache; - this.ids = m.ids; - this.constructor.super_.call(this, 'memory'); - this._models = m._models; - } else { - this.isTransaction = false; - this.cache = {}; - this.ids = {}; - this.constructor.super_.call(this, 'memory'); - } + if (m) { + this.isTransaction = true; + this.cache = m.cache; + this.ids = m.ids; + this.constructor.super_.call(this, 'memory'); + this._models = m._models; + } else { + this.isTransaction = false; + this.cache = {}; + this.ids = {}; + this.constructor.super_.call(this, 'memory'); + } } util.inherits(Memory, Connector); -Memory.prototype.connect = function(callback) { - if (this.isTransaction) { - this.onTransactionExec = callback; - } else { - process.nextTick(callback); - } +Memory.prototype.connect = function (callback) { + if (this.isTransaction) { + this.onTransactionExec = callback; + } else { + process.nextTick(callback); + } }; Memory.prototype.define = function defineModel(definition) { - this.constructor.super_.prototype.define.apply(this, [].slice.call(arguments)); - var m = definition.model.modelName; - this.cache[m] = {}; - this.ids[m] = 1; + this.constructor.super_.prototype.define.apply(this, [].slice.call(arguments)); + var m = definition.model.modelName; + this.cache[m] = {}; + this.ids[m] = 1; }; Memory.prototype.create = function create(model, data, callback) { - // FIXME: [rfeng] We need to generate unique ids based on the id type - // FIXME: [rfeng] We don't support composite ids yet - var currentId = this.ids[model]; - if(currentId === undefined) { - // First time - this.ids[model] = 1; - currentId = 1; - } - var id = this.getIdValue(model, data) || currentId; - if(id > currentId) { - // If the id is passed in and the value is greater than the current id - currentId = id; - } - this.ids[model] = Number(currentId) + 1; + // FIXME: [rfeng] We need to generate unique ids based on the id type + // FIXME: [rfeng] We don't support composite ids yet + var currentId = this.ids[model]; + if (currentId === undefined) { + // First time + this.ids[model] = 1; + currentId = 1; + } + var id = this.getIdValue(model, data) || currentId; + if (id > currentId) { + // If the id is passed in and the value is greater than the current id + currentId = id; + } + this.ids[model] = Number(currentId) + 1; - var props = this._models[model].properties; - var idName = this.idName(model); - id = (props[idName] && props[idName].type && props[idName].type(id)) || id; - this.setIdValue(model, data, id); - this.cache[model][id] = JSON.stringify(data); - process.nextTick(function() { - callback(null, id); - }); + var props = this._models[model].properties; + var idName = this.idName(model); + id = (props[idName] && props[idName].type && props[idName].type(id)) || id; + this.setIdValue(model, data, id); + this.cache[model][id] = JSON.stringify(data); + process.nextTick(function () { + callback(null, id); + }); }; Memory.prototype.updateOrCreate = function (model, data, callback) { - var self = this; - this.exists(model, self.getIdValue(model, data), function (err, exists) { - if (exists) { - self.save(model, data, callback); - } else { - self.create(model, data, function (err, id) { - self.setIdValue(model, data, id); - callback(err, data); - }); - } - }); + var self = this; + this.exists(model, self.getIdValue(model, data), function (err, exists) { + if (exists) { + self.save(model, data, callback); + } else { + self.create(model, data, function (err, id) { + self.setIdValue(model, data, id); + callback(err, data); + }); + } + }); }; Memory.prototype.save = function save(model, data, callback) { - this.cache[model][this.getIdValue(model, data)] = JSON.stringify(data); - process.nextTick(function () { - callback(null, data); - }); + this.cache[model][this.getIdValue(model, data)] = JSON.stringify(data); + process.nextTick(function () { + callback(null, data); + }); }; Memory.prototype.exists = function exists(model, id, callback) { - process.nextTick(function () { - callback(null, this.cache[model] && this.cache[model].hasOwnProperty(id)); - }.bind(this)); + process.nextTick(function () { + callback(null, this.cache[model] && this.cache[model].hasOwnProperty(id)); + }.bind(this)); }; Memory.prototype.find = function find(model, id, callback) { - var self = this; - process.nextTick(function () { - callback(null, id in this.cache[model] && this.fromDb(model, this.cache[model][id])); - }.bind(this)); + var self = this; + process.nextTick(function () { + callback(null, id in this.cache[model] && this.fromDb(model, this.cache[model][id])); + }.bind(this)); }; Memory.prototype.destroy = function destroy(model, id, callback) { - delete this.cache[model][id]; - process.nextTick(callback); + delete this.cache[model][id]; + process.nextTick(callback); }; -Memory.prototype.fromDb = function(model, data) { - if (!data) return null; - data = JSON.parse(data); - var props = this._models[model].properties; - for(var key in data) { - var val = data[key]; - if (val === undefined || val === null) { - continue; - } - if (props[key]) { - switch(props[key].type.name) { - case 'Date': - val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); - break; - case 'Boolean': - val = Boolean(val); - break; - case 'Number': - val = Number(val); - break; - } - } - data[key] = val; +Memory.prototype.fromDb = function (model, data) { + if (!data) return null; + data = JSON.parse(data); + var props = this._models[model].properties; + for (var key in data) { + var val = data[key]; + if (val === undefined || val === null) { + continue; } - return data; + if (props[key]) { + switch (props[key].type.name) { + case 'Date': + val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); + break; + case 'Boolean': + val = Boolean(val); + break; + case 'Number': + val = Number(val); + break; + } + } + data[key] = val; + } + return data; }; Memory.prototype.all = function all(model, filter, callback) { - var self = this; - var nodes = Object.keys(this.cache[model]).map(function (key) { - return this.fromDb(model, this.cache[model][key]); - }.bind(this)); + var self = this; + var nodes = Object.keys(this.cache[model]).map(function (key) { + return this.fromDb(model, this.cache[model][key]); + }.bind(this)); - if (filter) { - // do we need some sorting? - if (filter.order) { - var orders = filter.order; - if (typeof filter.order === "string") { - orders = [filter.order]; - } - orders.forEach(function (key, i) { - var reverse = 1; - var m = key.match(/\s+(A|DE)SC$/i); - if (m) { - key = key.replace(/\s+(A|DE)SC/i, ''); - if (m[1].toLowerCase() === 'de') reverse = -1; - } - orders[i] = {"key": key, "reverse": reverse}; - }); - nodes = nodes.sort(sorting.bind(orders)); + if (filter) { + // do we need some sorting? + if (filter.order) { + var orders = filter.order; + if (typeof filter.order === "string") { + orders = [filter.order]; + } + orders.forEach(function (key, i) { + var reverse = 1; + var m = key.match(/\s+(A|DE)SC$/i); + if (m) { + key = key.replace(/\s+(A|DE)SC/i, ''); + if (m[1].toLowerCase() === 'de') reverse = -1; } - - var nearFilter = geo.nearFilter(filter.where); - - // geo sorting - if(nearFilter) { - nodes = geo.filter(nodes, nearFilter); - } - - // do we need some filtration? - if (filter.where) { - nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes; - } - - // field selection - if(filter.fields) { - nodes = nodes.map(utils.selectFields(filter.fields)); - } - - // limit/skip - filter.skip = filter.skip || 0; - filter.limit = filter.limit || nodes.length; - nodes = nodes.slice(filter.skip, filter.skip + filter.limit); + orders[i] = {"key": key, "reverse": reverse}; + }); + nodes = nodes.sort(sorting.bind(orders)); } - process.nextTick(function () { - if (filter && filter.include) { - self._models[model].model.include(nodes, filter.include, callback); - } else { - callback(null, nodes); - } - }); + var nearFilter = geo.nearFilter(filter.where); - function sorting(a, b) { - for (var i=0, l=this.length; i b[this[i].key]) { - return 1*this[i].reverse; - } else if (a[this[i].key] < b[this[i].key]) { - return -1*this[i].reverse; - } - } - return 0; + // geo sorting + if (nearFilter) { + nodes = geo.filter(nodes, nearFilter); } + + // do we need some filtration? + if (filter.where) { + nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes; + } + + // field selection + if (filter.fields) { + nodes = nodes.map(utils.selectFields(filter.fields)); + } + + // limit/skip + filter.skip = filter.skip || 0; + filter.limit = filter.limit || nodes.length; + nodes = nodes.slice(filter.skip, filter.skip + filter.limit); + } + + process.nextTick(function () { + if (filter && filter.include) { + self._models[model].model.include(nodes, filter.include, callback); + } else { + callback(null, nodes); + } + }); + + function sorting(a, b) { + for (var i = 0, l = this.length; i < l; i++) { + if (a[this[i].key] > b[this[i].key]) { + return 1 * this[i].reverse; + } else if (a[this[i].key] < b[this[i].key]) { + return -1 * this[i].reverse; + } + } + return 0; + } }; function applyFilter(filter) { - if (typeof filter.where === 'function') { - return filter.where; - } - var keys = Object.keys(filter.where); - return function (obj) { - var pass = true; - keys.forEach(function (key) { - if (!test(filter.where[key], obj && obj[key])) { - pass = false; - } - }); - return pass; - } + if (typeof filter.where === 'function') { + return filter.where; + } + var keys = Object.keys(filter.where); + return function (obj) { + var pass = true; + keys.forEach(function (key) { + if (!test(filter.where[key], obj && obj[key])) { + pass = false; + } + }); + return pass; + } - function test(example, value) { - if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { - return value.match(example); - } - if (typeof example === 'undefined') return undefined; - if (typeof value === 'undefined') return undefined; - if (typeof example === 'object') { - // ignore geo near filter - if(example.near) return true; - - if (example.inq) { - if (!value) return false; - for (var i = 0; i < example.inq.length; i += 1) { - if (example.inq[i] == value) return true; - } - return false; - } - - if(isNum(example.gt) && example.gt < value) return true; - if(isNum(example.gte) && example.gte <= value) return true; - if(isNum(example.lt) && example.lt > value) return true; - if(isNum(example.lte) && example.lte >= value) return true; - } - // not strict equality - return (example !== null ? example.toString() : example) == (value !== null ? value.toString() : value); + function test(example, value) { + if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { + return value.match(example); } - - function isNum(n) { - return typeof n === 'number'; + if (typeof example === 'undefined') return undefined; + if (typeof value === 'undefined') return undefined; + if (typeof example === 'object') { + // ignore geo near filter + if (example.near) return true; + + if (example.inq) { + if (!value) return false; + for (var i = 0; i < example.inq.length; i += 1) { + if (example.inq[i] == value) return true; + } + return false; + } + + if (isNum(example.gt) && example.gt < value) return true; + if (isNum(example.gte) && example.gte <= value) return true; + if (isNum(example.lt) && example.lt > value) return true; + if (isNum(example.lte) && example.lte >= value) return true; } + // not strict equality + return (example !== null ? example.toString() : example) == (value !== null ? value.toString() : value); + } + + function isNum(n) { + return typeof n === 'number'; + } } Memory.prototype.destroyAll = function destroyAll(model, where, callback) { - if(!callback && 'function' === typeof where) { - callback = where; - where = undefined; + if (!callback && 'function' === typeof where) { + callback = where; + where = undefined; + } + var cache = this.cache[model]; + var filter = null; + if (where) { + filter = applyFilter({where: where}); + } + Object.keys(cache).forEach(function (id) { + if (!filter || filter(this.fromDb(model, cache[id]))) { + delete cache[id]; } - var cache = this.cache[model]; - var filter = null; - if (where) { - filter = applyFilter({where: where}); - } - Object.keys(cache).forEach(function (id) { - if(!filter || filter(this.fromDb(model, cache[id]))) { - delete cache[id]; - } - }.bind(this)); - if(!where) { - this.cache[model] = {}; - } - process.nextTick(callback); + }.bind(this)); + if (!where) { + this.cache[model] = {}; + } + process.nextTick(callback); }; Memory.prototype.count = function count(model, callback, where) { - var cache = this.cache[model]; - var data = Object.keys(cache); - if (where) { - var filter = {where: where}; - data = data.map(function (id) { - return this.fromDb(model, cache[id]); - }.bind(this)); - data = data.filter(applyFilter(filter)); - } - process.nextTick(function () { - callback(null, data.length); - }); + var cache = this.cache[model]; + var data = Object.keys(cache); + if (where) { + var filter = {where: where}; + data = data.map(function (id) { + return this.fromDb(model, cache[id]); + }.bind(this)); + data = data.filter(applyFilter(filter)); + } + process.nextTick(function () { + callback(null, data.length); + }); }; Memory.prototype.updateAttributes = function updateAttributes(model, id, data, cb) { - if(!id) { - var err = new Error('You must provide an id when updating attributes!'); - if(cb) { - return cb(err); - } else { - throw err; - } - } - - this.setIdValue(model, data, id); - - var cachedModels = this.cache[model]; - var modelAsString = cachedModels && this.cache[model][id]; - var modelData = modelAsString && JSON.parse(modelAsString); - - if(modelData) { - this.save(model, merge(modelData, data), cb); + if (!id) { + var err = new Error('You must provide an id when updating attributes!'); + if (cb) { + return cb(err); } else { - cb(new Error('Could not update attributes. Object with id ' + id + ' does not exist!')); + throw err; } + } + + this.setIdValue(model, data, id); + + var cachedModels = this.cache[model]; + var modelAsString = cachedModels && this.cache[model][id]; + var modelData = modelAsString && JSON.parse(modelAsString); + + if (modelData) { + this.save(model, merge(modelData, data), cb); + } else { + cb(new Error('Could not update attributes. Object with id ' + id + ' does not exist!')); + } }; Memory.prototype.transaction = function () { - return new Memory(this); + return new Memory(this); }; -Memory.prototype.exec = function(callback) { - this.onTransactionExec(); - setTimeout(callback, 50); +Memory.prototype.exec = function (callback) { + this.onTransactionExec(); + setTimeout(callback, 50); }; Memory.prototype.buildNearFilter = function (filter) { @@ -328,9 +328,9 @@ Memory.prototype.buildNearFilter = function (filter) { } function merge(base, update) { - if (!base) return update; - Object.keys(update).forEach(function (key) { - base[key] = update[key]; - }); - return base; + if (!base) return update; + Object.keys(update).forEach(function (key) { + base[key] = update[key]; + }); + return base; } \ No newline at end of file diff --git a/lib/connectors/neo4j.js b/lib/connectors/neo4j.js index fcb01a91..d4a9c121 100644 --- a/lib/connectors/neo4j.js +++ b/lib/connectors/neo4j.js @@ -6,367 +6,368 @@ var safeRequire = require('../utils').safeRequire; var neo4j = safeRequire('neo4j'); exports.initialize = function initializeSchema(dataSource, callback) { - dataSource.client = new neo4j.GraphDatabase(dataSource.settings.url); - dataSource.connector = new Neo4j(dataSource.client); - process.nextTick(callback); + dataSource.client = new neo4j.GraphDatabase(dataSource.settings.url); + dataSource.connector = new Neo4j(dataSource.client); + process.nextTick(callback); }; function Neo4j(client) { - this._models = {}; - this.client = client; - this.cache = {}; + this._models = {}; + this.client = client; + this.cache = {}; } Neo4j.prototype.define = function defineModel(descr) { - this.mixClassMethods(descr.model, descr.properties); - this.mixInstanceMethods(descr.model.prototype, descr.properties); - this._models[descr.model.modelName] = descr; + this.mixClassMethods(descr.model, descr.properties); + this.mixInstanceMethods(descr.model.prototype, descr.properties); + this._models[descr.model.modelName] = descr; }; Neo4j.prototype.createIndexHelper = function (cls, indexName) { - var db = this.client; - var method = 'findBy' + indexName[0].toUpperCase() + indexName.substr(1); - cls[method] = function (value, cb) { - db.getIndexedNode(cls.modelName, indexName, value, function (err, node) { - if (err) return cb(err); - if (node) { - node.data.id = node.id; - cb(null, new cls(node.data)); - } else { - cb(null, null); - } - }); - }; + var db = this.client; + var method = 'findBy' + indexName[0].toUpperCase() + indexName.substr(1); + cls[method] = function (value, cb) { + db.getIndexedNode(cls.modelName, indexName, value, function (err, node) { + if (err) return cb(err); + if (node) { + node.data.id = node.id; + cb(null, new cls(node.data)); + } else { + cb(null, null); + } + }); + }; }; Neo4j.prototype.mixClassMethods = function mixClassMethods(cls, properties) { - var neo = this; + var neo = this; - Object.keys(properties).forEach(function (name) { - if (properties[name].index) { - neo.createIndexHelper(cls, name); + Object.keys(properties).forEach(function (name) { + if (properties[name].index) { + neo.createIndexHelper(cls, name); + } + }); + + cls.setupCypherQuery = function (name, queryStr, rowHandler) { + cls[name] = function cypherQuery(params, cb) { + if (typeof params === 'function') { + cb = params; + params = []; + } else if (params.constructor.name !== 'Array') { + params = [params]; + } + + var i = 0; + var q = queryStr.replace(/\?/g, function () { + return params[i++]; + }); + + neo.client.query(function (err, result) { + if (err) return cb(err, []); + cb(null, result.map(rowHandler)); + }, q); + }; + }; + + /** + * @param from - id of object to check relation from + * @param to - id of object to check relation to + * @param type - type of relation + * @param direction - all | incoming | outgoing + * @param cb - callback (err, rel || false) + */ + cls.relationshipExists = function relationshipExists(from, to, type, direction, cb) { + neo.node(from, function (err, node) { + if (err) return cb(err); + node._getRelationships(direction, type, function (err, rels) { + if (err && cb) return cb(err); + if (err && !cb) throw err; + var found = false; + if (rels && rels.forEach) { + rels.forEach(function (r) { + if (r.start.id === from && r.end.id === to) { + found = true; + } + }); } + cb && cb(err, found); + }); }); + }; - cls.setupCypherQuery = function (name, queryStr, rowHandler) { - cls[name] = function cypherQuery(params, cb) { - if (typeof params === 'function') { - cb = params; - params = []; - } else if (params.constructor.name !== 'Array') { - params = [params]; - } - - var i = 0; - var q = queryStr.replace(/\?/g, function () { - return params[i++]; - }); - - neo.client.query(function (err, result) { - if (err) return cb(err, []); - cb(null, result.map(rowHandler)); - }, q); - }; - }; - - /** - * @param from - id of object to check relation from - * @param to - id of object to check relation to - * @param type - type of relation - * @param direction - all | incoming | outgoing - * @param cb - callback (err, rel || false) - */ - cls.relationshipExists = function relationshipExists(from, to, type, direction, cb) { - neo.node(from, function (err, node) { - if (err) return cb(err); - node._getRelationships(direction, type, function (err, rels) { - if (err && cb) return cb(err); - if (err && !cb) throw err; - var found = false; - if (rels && rels.forEach) { - rels.forEach(function (r) { - if (r.start.id === from && r.end.id === to) { - found = true; - } - }); - } - cb && cb(err, found); - }); - }); - }; - - cls.createRelationshipTo = function createRelationshipTo(id1, id2, type, data, cb) { - var fromNode, toNode; - neo.node(id1, function (err, node) { - if (err && cb) return cb(err); - if (err && !cb) throw err; - fromNode = node; - ok(); - }); - neo.node(id2, function (err, node) { - if (err && cb) return cb(err); - if (err && !cb) throw err; - toNode = node; - ok(); - }); - function ok() { - if (fromNode && toNode) { - fromNode.createRelationshipTo(toNode, type, cleanup(data), cb); - } - } - }; - - cls.createRelationshipFrom = function createRelationshipFrom(id1, id2, type, data, cb) { - cls.createRelationshipTo(id2, id1, type, data, cb); + cls.createRelationshipTo = function createRelationshipTo(id1, id2, type, data, cb) { + var fromNode, toNode; + neo.node(id1, function (err, node) { + if (err && cb) return cb(err); + if (err && !cb) throw err; + fromNode = node; + ok(); + }); + neo.node(id2, function (err, node) { + if (err && cb) return cb(err); + if (err && !cb) throw err; + toNode = node; + ok(); + }); + function ok() { + if (fromNode && toNode) { + fromNode.createRelationshipTo(toNode, type, cleanup(data), cb); + } } + }; - // only create relationship if it is not exists - cls.ensureRelationshipTo = function (id1, id2, type, data, cb) { - cls.relationshipExists(id1, id2, type, 'outgoing', function (err, exists) { - if (err && cb) return cb(err); - if (err && !cb) throw err; - if (exists) return cb && cb(null); - cls.createRelationshipTo(id1, id2, type, data, cb); - }); - } + cls.createRelationshipFrom = function createRelationshipFrom(id1, id2, type, data, cb) { + cls.createRelationshipTo(id2, id1, type, data, cb); + } + + // only create relationship if it is not exists + cls.ensureRelationshipTo = function (id1, id2, type, data, cb) { + cls.relationshipExists(id1, id2, type, 'outgoing', function (err, exists) { + if (err && cb) return cb(err); + if (err && !cb) throw err; + if (exists) return cb && cb(null); + cls.createRelationshipTo(id1, id2, type, data, cb); + }); + } }; Neo4j.prototype.mixInstanceMethods = function mixInstanceMethods(proto) { - var neo = this; + var neo = this; - /** - * @param obj - Object or id of object to check relation with - * @param type - type of relation - * @param cb - callback (err, rel || false) - */ - proto.isInRelationWith = function isInRelationWith(obj, type, direction, cb) { - this.constructor.relationshipExists(this.id, obj.id || obj, type, 'all', cb); - }; + /** + * @param obj - Object or id of object to check relation with + * @param type - type of relation + * @param cb - callback (err, rel || false) + */ + proto.isInRelationWith = function isInRelationWith(obj, type, direction, cb) { + this.constructor.relationshipExists(this.id, obj.id || obj, type, 'all', cb); + }; }; Neo4j.prototype.node = function find(id, callback) { - if (this.cache[id]) { - callback(null, this.cache[id]); - } else { - this.client.getNodeById(id, function (err, node) { - if (node) { - this.cache[id] = node; - } - callback(err, node); - }.bind(this)); - } + if (this.cache[id]) { + callback(null, this.cache[id]); + } else { + this.client.getNodeById(id, function (err, node) { + if (node) { + this.cache[id] = node; + } + callback(err, node); + }.bind(this)); + } }; Neo4j.prototype.create = function create(model, data, callback) { - data.nodeType = model; - var node = this.client.createNode(); - node.data = cleanup(data); - node.data.nodeType = model; - node.save(function (err) { - if (err) { - return callback(err); - } - this.cache[node.id] = node; - node.index(model, 'id', node.id, function (err) { - if (err) return callback(err); - this.updateIndexes(model, node, function (err) { - if (err) return callback(err); - callback(null, node.id); - }); - }.bind(this)); + data.nodeType = model; + var node = this.client.createNode(); + node.data = cleanup(data); + node.data.nodeType = model; + node.save(function (err) { + if (err) { + return callback(err); + } + this.cache[node.id] = node; + node.index(model, 'id', node.id, function (err) { + if (err) return callback(err); + this.updateIndexes(model, node, function (err) { + if (err) return callback(err); + callback(null, node.id); + }); }.bind(this)); + }.bind(this)); }; Neo4j.prototype.updateIndexes = function updateIndexes(model, node, cb) { - var props = this._models[model].properties; - var wait = 1; - Object.keys(props).forEach(function (key) { - if (props[key].index && node.data[key]) { - wait += 1; - node.index(model, key, node.data[key], done); - } - }); - - done(); - - var error = false; - function done(err) { - error = error || err; - if (--wait === 0) { - cb(error); - } + var props = this._models[model].properties; + var wait = 1; + Object.keys(props).forEach(function (key) { + if (props[key].index && node.data[key]) { + wait += 1; + node.index(model, key, node.data[key], done); } + }); + + done(); + + var error = false; + + function done(err) { + error = error || err; + if (--wait === 0) { + cb(error); + } + } }; Neo4j.prototype.save = function save(model, data, callback) { - var self = this; - - this.node(data.id, function (err, node) { - //delete id property since that's redundant and we use the node.id - delete data.id; - if (err) return callback(err); - node.data = cleanup(data); - node.save(function (err) { - if (err) return callback(err); - self.updateIndexes(model, node, function (err) { - if (err) return console.log(err); - //map node id to the id property being sent back - node.data.id = node.id; - callback(null, node.data); - }); - }); + var self = this; + + this.node(data.id, function (err, node) { + //delete id property since that's redundant and we use the node.id + delete data.id; + if (err) return callback(err); + node.data = cleanup(data); + node.save(function (err) { + if (err) return callback(err); + self.updateIndexes(model, node, function (err) { + if (err) return console.log(err); + //map node id to the id property being sent back + node.data.id = node.id; + callback(null, node.data); + }); }); + }); }; Neo4j.prototype.exists = function exists(model, id, callback) { - delete this.cache[id]; - this.node(id, callback); + delete this.cache[id]; + this.node(id, callback); }; Neo4j.prototype.find = function find(model, id, callback) { - delete this.cache[id]; - this.node(id, function (err, node) { - if (node && node.data) { - node.data.id = id; - } - callback(err, this.readFromDb(model, node && node.data)); - }.bind(this)); + delete this.cache[id]; + this.node(id, function (err, node) { + if (node && node.data) { + node.data.id = id; + } + callback(err, this.readFromDb(model, node && node.data)); + }.bind(this)); }; Neo4j.prototype.readFromDb = function readFromDb(model, data) { - if (!data) return data; - var res = {}; - var props = this._models[model].properties; - Object.keys(data).forEach(function (key) { - if (props[key] && props[key].type.name === 'Date') { - res[key] = new Date(data[key]); - } else { - res[key] = data[key]; - } - }); - return res; + if (!data) return data; + var res = {}; + var props = this._models[model].properties; + Object.keys(data).forEach(function (key) { + if (props[key] && props[key].type.name === 'Date') { + res[key] = new Date(data[key]); + } else { + res[key] = data[key]; + } + }); + return res; }; Neo4j.prototype.destroy = function destroy(model, id, callback) { - var force = true; - this.node(id, function (err, node) { - if (err) return callback(err); - node.delete(function (err) { - if (err) return callback(err); - delete this.cache[id]; - }.bind(this), force); - }); + var force = true; + this.node(id, function (err, node) { + if (err) return callback(err); + node.delete(function (err) { + if (err) return callback(err); + delete this.cache[id]; + }.bind(this), force); + }); }; Neo4j.prototype.all = function all(model, filter, callback) { - this.client.queryNodeIndex(model, 'id:*', function (err, nodes) { - if (nodes) { - nodes = nodes.map(function (obj) { - obj.data.id = obj.id; - return this.readFromDb(model, obj.data); - }.bind(this)); - } - if (filter) { - nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes; - if (filter.order) { - var key = filter.order.split(' ')[0]; - var dir = filter.order.split(' ')[1]; - nodes = nodes.sort(function (a, b) { - return a[key] > b[key]; - }); - if (dir === 'DESC') nodes = nodes.reverse(); - } - } - callback(err, nodes); - }.bind(this)); + this.client.queryNodeIndex(model, 'id:*', function (err, nodes) { + if (nodes) { + nodes = nodes.map(function (obj) { + obj.data.id = obj.id; + return this.readFromDb(model, obj.data); + }.bind(this)); + } + if (filter) { + nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes; + if (filter.order) { + var key = filter.order.split(' ')[0]; + var dir = filter.order.split(' ')[1]; + nodes = nodes.sort(function (a, b) { + return a[key] > b[key]; + }); + if (dir === 'DESC') nodes = nodes.reverse(); + } + } + callback(err, nodes); + }.bind(this)); }; Neo4j.prototype.allNodes = function all(model, callback) { - this.client.queryNodeIndex(model, 'id:*', function (err, nodes) { - callback(err, nodes); - }); + this.client.queryNodeIndex(model, 'id:*', function (err, nodes) { + callback(err, nodes); + }); }; function applyFilter(filter) { - if (typeof filter.where === 'function') { - return filter.where; - } - var keys = Object.keys(filter.where || {}); - return function (obj) { - var pass = true; - keys.forEach(function (key) { - if (!test(filter.where[key], obj[key])) { - pass = false; - } - }); - return pass; - } + if (typeof filter.where === 'function') { + return filter.where; + } + var keys = Object.keys(filter.where || {}); + return function (obj) { + var pass = true; + keys.forEach(function (key) { + if (!test(filter.where[key], obj[key])) { + pass = false; + } + }); + return pass; + } - function test(example, value) { - if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { - return value.match(example); - } - if (typeof value === 'object' && value.constructor.name === 'Date' && typeof example === 'object' && example.constructor.name === 'Date') { - return example.toString() === value.toString(); - } - // not strict equality - return example == value; + function test(example, value) { + if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { + return value.match(example); } + if (typeof value === 'object' && value.constructor.name === 'Date' && typeof example === 'object' && example.constructor.name === 'Date') { + return example.toString() === value.toString(); + } + // not strict equality + return example == value; + } } Neo4j.prototype.destroyAll = function destroyAll(model, callback) { - var wait, error = null; - this.allNodes(model, function (err, collection) { - if (err) return callback(err); - wait = collection.length; - collection && collection.forEach && collection.forEach(function (node) { - node.delete(done, true); - }); + var wait, error = null; + this.allNodes(model, function (err, collection) { + if (err) return callback(err); + wait = collection.length; + collection && collection.forEach && collection.forEach(function (node) { + node.delete(done, true); }); + }); - function done(err) { - error = error || err; - if (--wait === 0) { - callback(error); - } + function done(err) { + error = error || err; + if (--wait === 0) { + callback(error); } + } }; Neo4j.prototype.count = function count(model, callback, conds) { - this.all(model, {where: conds}, function (err, collection) { - callback(err, collection ? collection.length : 0); - }); + this.all(model, {where: conds}, function (err, collection) { + callback(err, collection ? collection.length : 0); + }); }; Neo4j.prototype.updateAttributes = function updateAttributes(model, id, data, cb) { - data.id = id; - this.node(id, function (err, node) { - this.save(model, merge(node.data, data), cb); - }.bind(this)); + data.id = id; + this.node(id, function (err, node) { + this.save(model, merge(node.data, data), cb); + }.bind(this)); }; function cleanup(data) { - if (!data) return null; - - var res = {}; - Object.keys(data).forEach(function (key) { - var v = data[key]; - if (v === null) { - // skip - // console.log('skip null', key); - } else if (v && v.constructor.name === 'Array' && v.length === 0) { - // skip - // console.log('skip blank array', key); - } else if (typeof v !== 'undefined') { - res[key] = v; - } - }); - return res; + if (!data) return null; + + var res = {}; + Object.keys(data).forEach(function (key) { + var v = data[key]; + if (v === null) { + // skip + // console.log('skip null', key); + } else if (v && v.constructor.name === 'Array' && v.length === 0) { + // skip + // console.log('skip blank array', key); + } else if (typeof v !== 'undefined') { + res[key] = v; + } + }); + return res; } function merge(base, update) { - Object.keys(update).forEach(function (key) { - base[key] = update[key]; - }); - return base; + Object.keys(update).forEach(function (key) { + base[key] = update[key]; + }); + return base; } diff --git a/lib/connectors/riak.js b/lib/connectors/riak.js index d9b88bf6..4730fc8b 100644 --- a/lib/connectors/riak.js +++ b/lib/connectors/riak.js @@ -7,104 +7,104 @@ var uuid = require('node-uuid'); var riak = safeRequire('riak-js'); exports.initialize = function initializeSchema(dataSource, callback) { - dataSource.client = riak.getClient({ - host: dataSource.settings.host || '127.0.0.1', - port: dataSource.settings.port || 8091 - }); - dataSource.connector = new Riak(dataSource.client); + dataSource.client = riak.getClient({ + host: dataSource.settings.host || '127.0.0.1', + port: dataSource.settings.port || 8091 + }); + dataSource.connector = new Riak(dataSource.client); }; function Riak(client) { - this._models = {}; - this.client = client; + this._models = {}; + this.client = client; } Riak.prototype.define = function (descr) { - this._models[descr.model.modelName] = descr; + this._models[descr.model.modelName] = descr; }; Riak.prototype.save = function (model, data, callback) { - this.client.save(model, data.id, data, callback); + this.client.save(model, data.id, data, callback); }; Riak.prototype.create = function (model, data, callback) { - data.id = uuid(); - this.save(model, data, function (err) { - if (callback) { - callback(err, data.id); - } - }); + data.id = uuid(); + this.save(model, data, function (err) { + if (callback) { + callback(err, data.id); + } + }); }; Riak.prototype.exists = function (model, id, callback) { - this.client.exists(model, id, function (err, exists, meta) { - if (callback) { - callback(err, exists); - } - }); + this.client.exists(model, id, function (err, exists, meta) { + if (callback) { + callback(err, exists); + } + }); }; Riak.prototype.find = function find(model, id, callback) { - this.client.get(model, id, function (err, data, meta) { - if (data && data.id) { - data.id = id; - } else { - data = null; - } - if (typeof callback === 'function') callback(err, data); - }); + this.client.get(model, id, function (err, data, meta) { + if (data && data.id) { + data.id = id; + } else { + data = null; + } + if (typeof callback === 'function') callback(err, data); + }); }; Riak.prototype.destroy = function destroy(model, id, callback) { - this.client.remove(model, id, function (err) { - callback(err); - }); + this.client.remove(model, id, function (err) { + callback(err); + }); }; Riak.prototype.all = function all(model, filter, callback) { - var opts = {}; - if (filter && filter.where) opts.where = filter.where; - this.client.getAll(model, function (err, result, meta) { - if (err) return callback(err, []); - /// return callback(err, result.map(function (x) { return {id: x}; })); - result = (result || []).map(function (row) { - var record = row.data; - record.id = row.meta.key; - console.log(record); - return record; - }); + var opts = {}; + if (filter && filter.where) opts.where = filter.where; + this.client.getAll(model, function (err, result, meta) { + if (err) return callback(err, []); + /// return callback(err, result.map(function (x) { return {id: x}; })); + result = (result || []).map(function (row) { + var record = row.data; + record.id = row.meta.key; + console.log(record); + return record; + }); - return callback(err, result); - }.bind(this)); + return callback(err, result); + }.bind(this)); }; Riak.prototype.destroyAll = function destroyAll(model, callback) { - var self = this; - this.all(model, {}, function (err, recs) { - if (err) callback(err); + var self = this; + this.all(model, {}, function (err, recs) { + if (err) callback(err); - removeOne(); + removeOne(); - function removeOne(error) { - err = err || error; - var rec = recs.pop(); - if (!rec) return callback(err && err.statusCode != '404' ? err : null); - console.log(rec.id); - self.client.remove(model, rec.id, removeOne); - } + function removeOne(error) { + err = err || error; + var rec = recs.pop(); + if (!rec) return callback(err && err.statusCode != '404' ? err : null); + console.log(rec.id); + self.client.remove(model, rec.id, removeOne); + } - }); + }); }; Riak.prototype.count = function count(model, callback) { - this.client.keys(model + ':*', function (err, keys) { - callback(err, err ? null : keys.length); - }); + this.client.keys(model + ':*', function (err, keys) { + callback(err, err ? null : keys.length); + }); }; Riak.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { - data.id = id; - this.save(model, data, cb); + data.id = id; + this.save(model, data, cb); }; diff --git a/lib/dao.js b/lib/dao.js index 0ea878a7..b556e7b1 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -19,7 +19,6 @@ var utils = require('./utils'); var fieldsToArray = utils.fieldsToArray; var removeUndefined = utils.removeUndefined; - /** * DAO class - base class for all persist objects * provides **common API** to access any database connector. @@ -32,44 +31,44 @@ var removeUndefined = utils.removeUndefined; * @param {Object} data - initial object data */ function DataAccessObject() { - if(DataAccessObject._mixins) { - var self = this; - var args = arguments; - DataAccessObject._mixins.forEach(function(m) { - m.call(self, args); - }); - } + if (DataAccessObject._mixins) { + var self = this; + var args = arguments; + DataAccessObject._mixins.forEach(function (m) { + m.call(self, args); + }); + } } function idName(m) { - return m.getDataSource().idName + return m.getDataSource().idName ? m.getDataSource().idName(m.modelName) : 'id'; } function getIdValue(m, data) { - return data && data[m.getDataSource().idName(m.modelName)]; + return data && data[m.getDataSource().idName(m.modelName)]; } function setIdValue(m, data, value) { - if(data) { - data[idName(m)] = value; - } + if (data) { + data[idName(m)] = value; + } } DataAccessObject._forDB = function (data) { - if(!(this.getDataSource().isRelational && this.getDataSource().isRelational())) { - return data; + if (!(this.getDataSource().isRelational && this.getDataSource().isRelational())) { + return data; + } + var res = {}; + for (var propName in data) { + var type = this.getPropertyType(propName); + if (type === 'JSON' || type === 'Any' || type === 'Object' || data[propName] instanceof Array) { + res[propName] = JSON.stringify(data[propName]); + } else { + res[propName] = data[propName]; } - var res = {}; - for(var propName in data) { - var type = this.getPropertyType(propName); - if (type === 'JSON' || type === 'Any' || type === 'Object' || data[propName] instanceof Array) { - res[propName] = JSON.stringify(data[propName]); - } else { - res[propName] = data[propName]; - } - } - return res; + } + return res; }; /** @@ -83,101 +82,101 @@ DataAccessObject._forDB = function (data) { * - instance (null or Model) */ DataAccessObject.create = function (data, callback) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - var Model = this; - var modelName = Model.modelName; + var Model = this; + var modelName = Model.modelName; - if (typeof data === 'function') { - callback = data; - data = {}; + if (typeof data === 'function') { + callback = data; + data = {}; + } + + if (typeof callback !== 'function') { + callback = function () { + }; + } + + if (!data) { + data = {}; + } + + if (Array.isArray(data)) { + var instances = []; + var errors = Array(data.length); + var gotError = false; + var wait = data.length; + if (wait === 0) callback(null, []); + + var instances = []; + for (var i = 0; i < data.length; i += 1) { + (function (d, i) { + instances.push(Model.create(d, function (err, inst) { + if (err) { + errors[i] = err; + gotError = true; + } + modelCreated(); + })); + })(data[i], i); } - if (typeof callback !== 'function') { - callback = function () {}; + return instances; + + function modelCreated() { + if (--wait === 0) { + callback(gotError ? errors : null, instances); + } } + } - if (!data) { - data = {}; - } + var obj; + // if we come from save + if (data instanceof Model && !getIdValue(this, data)) { + obj = data; + } else { + obj = new Model(data); + } + data = obj.toObject(true); - if (Array.isArray(data)) { - var instances = []; - var errors = Array(data.length); - var gotError = false; - var wait = data.length; - if (wait === 0) callback(null, []); - - var instances = []; - for (var i = 0; i < data.length; i += 1) { - (function(d, i) { - instances.push(Model.create(d, function(err, inst) { - if (err) { - errors[i] = err; - gotError = true; - } - modelCreated(); - })); - })(data[i], i); - } - - return instances; - - function modelCreated() { - if (--wait === 0) { - callback(gotError ? errors : null, instances); - } - } - } - - - var obj; - // if we come from save - if (data instanceof Model && !getIdValue(this, data)) { - obj = data; + // validation required + obj.isValid(function (valid) { + if (valid) { + create(); } else { - obj = new Model(data); + callback(new ValidationError(obj), obj); } - data = obj.toObject(true); + }, data); - // validation required - obj.isValid(function(valid) { - if (valid) { - create(); - } else { - callback(new ValidationError(obj), obj); - } - }, data); + function create() { + obj.trigger('create', function (createDone) { + obj.trigger('save', function (saveDone) { - function create() { - obj.trigger('create', function(createDone) { - obj.trigger('save', function(saveDone) { - - var _idName = idName(Model); - this._adapter().create(modelName, this.constructor._forDB(obj.toObject(true)), function (err, id, rev) { - if (id) { - obj.__data[_idName] = id; - obj.__dataWas[_idName] = id; - defineReadonlyProp(obj, _idName, id); - } - if (rev) { - obj._rev = rev; - } - if (err) { - return callback(err, obj); - } - saveDone.call(obj, function () { - createDone.call(obj, function () { - callback(err, obj); - }); - }); - }, obj); - }, obj); + var _idName = idName(Model); + this._adapter().create(modelName, this.constructor._forDB(obj.toObject(true)), function (err, id, rev) { + if (id) { + obj.__data[_idName] = id; + obj.__dataWas[_idName] = id; + defineReadonlyProp(obj, _idName, id); + } + if (rev) { + obj._rev = rev; + } + if (err) { + return callback(err, obj); + } + saveDone.call(obj, function () { + createDone.call(obj, function () { + callback(err, obj); + }); + }); }, obj); - } + }, obj); + }, obj); + } - // for chaining - return obj; + // for chaining + return obj; }; /*! @@ -187,24 +186,24 @@ DataAccessObject.create = function (data, callback) { * @private */ function setRemoting(fn, options) { - options = options || {}; - for(var opt in options) { - if(options.hasOwnProperty(opt)) { - fn[opt] = options[opt]; - } + options = options || {}; + for (var opt in options) { + if (options.hasOwnProperty(opt)) { + fn[opt] = options[opt]; } - fn.shared = true; + } + fn.shared = true; } setRemoting(DataAccessObject.create, { - description: 'Create a new instance of the model and persist it into the data source', - accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, - returns: {arg: 'data', type: 'object', root: true}, - http: {verb: 'post', path: '/'} + description: 'Create a new instance of the model and persist it into the data source', + accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, + returns: {arg: 'data', type: 'object', root: true}, + http: {verb: 'post', path: '/'} }); function stillConnecting(dataSource, obj, args) { - return dataSource.ready(obj, args); + return dataSource.ready(obj, args); } /** @@ -213,70 +212,70 @@ function stillConnecting(dataSource, obj, args) { * @param {Function} [callback] The callback function */ DataAccessObject.upsert = DataAccessObject.updateOrCreate = function upsert(data, callback) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - var Model = this; - if (!getIdValue(this, data)) return this.create(data, callback); - if (this.getDataSource().connector.updateOrCreate) { - var inst = new Model(data); - this.getDataSource().connector.updateOrCreate(Model.modelName, inst.toObject(true), function (err, data) { - var obj; - if (data) { - inst._initProperties(data, false); - obj = inst; - } else { - obj = null; - } - callback(err, obj); - }); - } else { - this.findById(getIdValue(this, data), function (err, inst) { - if (err) return callback(err); - if (inst) { - inst.updateAttributes(data, callback); - } else { - var obj = new Model(data); - obj.save(data, callback); - } - }); - } + var Model = this; + if (!getIdValue(this, data)) return this.create(data, callback); + if (this.getDataSource().connector.updateOrCreate) { + var inst = new Model(data); + this.getDataSource().connector.updateOrCreate(Model.modelName, inst.toObject(true), function (err, data) { + var obj; + if (data) { + inst._initProperties(data, false); + obj = inst; + } else { + obj = null; + } + callback(err, obj); + }); + } else { + this.findById(getIdValue(this, data), function (err, inst) { + if (err) return callback(err); + if (inst) { + inst.updateAttributes(data, callback); + } else { + var obj = new Model(data); + obj.save(data, callback); + } + }); + } }; // upsert ~ remoting attributes setRemoting(DataAccessObject.upsert, { - description: 'Update an existing model instance or insert a new one into the data source', - accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, - returns: {arg: 'data', type: 'object', root: true}, - http: {verb: 'put', path: '/'} + description: 'Update an existing model instance or insert a new one into the data source', + accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, + returns: {arg: 'data', type: 'object', root: true}, + http: {verb: 'put', path: '/'} }); - /** * Find one record, same as `all`, limited by 1 and return object, not collection, * if not found, create using data provided as second argument - * + * * @param {Object} query - search conditions: {where: {test: 'me'}}. * @param {Object} data - object to create. * @param {Function} cb - callback called with (err, instance) */ DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) { - if (query === undefined) { - query = {where: {}}; - } - if (typeof data === 'function' || typeof data === 'undefined') { - callback = data; - data = query && query.where; - } - if (typeof callback === 'undefined') { - callback = function () {}; - } + if (query === undefined) { + query = {where: {}}; + } + if (typeof data === 'function' || typeof data === 'undefined') { + callback = data; + data = query && query.where; + } + if (typeof callback === 'undefined') { + callback = function () { + }; + } - var t = this; - this.findOne(query, function (err, record) { - if (err) return callback(err); - if (record) return callback(null, record); - t.create(data, callback); - }); + var t = this; + this.findOne(query, function (err, record) { + if (err) return callback(err); + if (record) return callback(null, record); + t.create(data, callback); + }); }; /** @@ -286,21 +285,21 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) { * @param {Function} cb - callbacl called with (err, exists: Bool) */ DataAccessObject.exists = function exists(id, cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - if (id !== undefined && id !== null && id !== '') { - this.dataSource.connector.exists(this.modelName, id, cb); - } else { - cb(new Error('Model::exists requires the id argument')); - } + if (id !== undefined && id !== null && id !== '') { + this.dataSource.connector.exists(this.modelName, id, cb); + } else { + cb(new Error('Model::exists requires the id argument')); + } }; // exists ~ remoting attributes setRemoting(DataAccessObject.exists, { - description: 'Check whether a model instance exists in the data source', - accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, - returns: {arg: 'exists', type: 'any'}, - http: {verb: 'get', path: '/:id/exists'} + description: 'Check whether a model instance exists in the data source', + accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, + returns: {arg: 'exists', type: 'any'}, + http: {verb: 'get', path: '/:id/exists'} }); /** @@ -310,28 +309,28 @@ setRemoting(DataAccessObject.exists, { * @param {Function} cb - callback called with (err, instance) */ DataAccessObject.findById = function find(id, cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - this.getDataSource().connector.find(this.modelName, id, function (err, data) { - var obj = null; - if (data) { - if (!getIdValue(this, data)) { - setIdValue(this, data, id); - } - obj = new this(); - obj._initProperties(data, false); - } - cb(err, obj); - }.bind(this)); + this.getDataSource().connector.find(this.modelName, id, function (err, data) { + var obj = null; + if (data) { + if (!getIdValue(this, data)) { + setIdValue(this, data, id); + } + obj = new this(); + obj._initProperties(data, false); + } + cb(err, obj); + }.bind(this)); }; // find ~ remoting attributes setRemoting(DataAccessObject.findById, { - description: 'Find a model instance by id from the data source', - accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, - returns: {arg: 'data', type: 'any', root: true}, - http: {verb: 'get', path: '/:id'}, - rest: {after: convertNullToNotFoundError} + description: 'Find a model instance by id from the data source', + accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, + returns: {arg: 'data', type: 'any', root: true}, + http: {verb: 'get', path: '/:id'}, + rest: {after: convertNullToNotFoundError} }); function convertNullToNotFoundError(ctx, cb) { @@ -351,109 +350,109 @@ DataAccessObject.all = function () { }; var operators = { - gt: '>', - gte: '>=', - lt: '<', - lte: '<=', - between: 'BETWEEN', - inq: 'IN', - nin: 'NOT IN', - neq: '!=', - like: 'LIKE', - nlike: 'NOT LIKE' + gt: '>', + gte: '>=', + lt: '<', + lte: '<=', + between: 'BETWEEN', + inq: 'IN', + nin: 'NOT IN', + neq: '!=', + like: 'LIKE', + nlike: 'NOT LIKE' }; DataAccessObject._coerce = function (where) { - if (!where) { - return where; - } - - var props = this.getDataSource().getModelDefinition(this.modelName).properties; - for (var p in where) { - var DataType = props[p] && props[p].type; - if (!DataType) { - continue; - } - if (Array.isArray(DataType) || DataType === Array) { - DataType = DataType[0]; - } - if (DataType === Date) { - var OrigDate = Date; - DataType = function Date(arg) { - return new OrigDate(arg); - }; - } else if (DataType === Boolean) { - DataType = function(val) { - if(val === 'true') { - return true; - } else if(val === 'false') { - return false; - } else { - return Boolean(val); - } - }; - } else if(DataType === Number) { - // This fixes a regression in mongodb connector - // For numbers, only convert it produces a valid number - // LoopBack by default injects a number id. We should fix it based - // on the connector's input, for example, MongoDB should use string - // while RDBs typically use number - DataType = function(val) { - var num = Number(val); - return !isNaN(num) ? num : val; - }; - } - - if (!DataType) { - continue; - } - - if (DataType === geo.GeoPoint) { - // Skip the GeoPoint as the near operator breaks the assumption that - // an operation has only one property - // We should probably fix it based on - // http://docs.mongodb.org/manual/reference/operator/query/near/ - // The other option is to make operators start with $ - continue; - } - - var val = where[p]; - if(val === null || val === undefined) { - continue; - } - // Check there is an operator - var operator = null; - if ('object' === typeof val) { - if (Object.keys(val).length !== 1) { - // Skip if there are not only one properties - // as the assumption for operators is not true here - continue; - } - for (var op in operators) { - if (op in val) { - val = val[op]; - operator = op; - break; - } - } - } - // Coerce the array items - if (Array.isArray(val)) { - for (var i = 0; i < val.length; i++) { - val[i] = DataType(val[i]); - } - } else { - val = DataType(val); - } - // Rebuild {property: {operator: value}} - if (operator) { - var value = {}; - value[operator] = val; - val = value; - } - where[p] = val; - } + if (!where) { return where; + } + + var props = this.getDataSource().getModelDefinition(this.modelName).properties; + for (var p in where) { + var DataType = props[p] && props[p].type; + if (!DataType) { + continue; + } + if (Array.isArray(DataType) || DataType === Array) { + DataType = DataType[0]; + } + if (DataType === Date) { + var OrigDate = Date; + DataType = function Date(arg) { + return new OrigDate(arg); + }; + } else if (DataType === Boolean) { + DataType = function (val) { + if (val === 'true') { + return true; + } else if (val === 'false') { + return false; + } else { + return Boolean(val); + } + }; + } else if (DataType === Number) { + // This fixes a regression in mongodb connector + // For numbers, only convert it produces a valid number + // LoopBack by default injects a number id. We should fix it based + // on the connector's input, for example, MongoDB should use string + // while RDBs typically use number + DataType = function (val) { + var num = Number(val); + return !isNaN(num) ? num : val; + }; + } + + if (!DataType) { + continue; + } + + if (DataType === geo.GeoPoint) { + // Skip the GeoPoint as the near operator breaks the assumption that + // an operation has only one property + // We should probably fix it based on + // http://docs.mongodb.org/manual/reference/operator/query/near/ + // The other option is to make operators start with $ + continue; + } + + var val = where[p]; + if (val === null || val === undefined) { + continue; + } + // Check there is an operator + var operator = null; + if ('object' === typeof val) { + if (Object.keys(val).length !== 1) { + // Skip if there are not only one properties + // as the assumption for operators is not true here + continue; + } + for (var op in operators) { + if (op in val) { + val = val[op]; + operator = op; + break; + } + } + } + // Coerce the array items + if (Array.isArray(val)) { + for (var i = 0; i < val.length; i++) { + val[i] = DataType(val[i]); + } + } else { + val = DataType(val); + } + // Rebuild {property: {operator: value}} + if (operator) { + var value = {}; + value[operator] = val; + val = value; + } + where[p] = val; + } + return where; }; /** @@ -475,158 +474,157 @@ DataAccessObject._coerce = function (where) { */ DataAccessObject.find = function find(params, cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - if (arguments.length === 1) { - cb = params; - params = null; - } - var constr = this; - - params = params || {}; + if (arguments.length === 1) { + cb = params; + params = null; + } + var constr = this; - if(params.where) { - params.where = this._coerce(params.where); - } + params = params || {}; - var fields = params.fields; - var near = params && geo.nearFilter(params.where); - var supportsGeo = !!this.getDataSource().connector.buildNearFilter; - - // normalize fields as array of included property names - if(fields) { - params.fields = fieldsToArray(fields, Object.keys(this.definition.properties)); - } + if (params.where) { + params.where = this._coerce(params.where); + } - params = removeUndefined(params); - if(near) { - if(supportsGeo) { - // convert it - this.getDataSource().connector.buildNearFilter(params, near); - } else if(params.where) { - // do in memory query - // using all documents - this.getDataSource().connector.all(this.modelName, {}, function (err, data) { - var memory = new Memory(); - var modelName = constr.modelName; - - if(err) { - cb(err); - } else if(Array.isArray(data)) { - memory.define({ - properties: constr.dataSource.definitions[constr.modelName].properties, - settings: constr.dataSource.definitions[constr.modelName].settings, - model: constr + var fields = params.fields; + var near = params && geo.nearFilter(params.where); + var supportsGeo = !!this.getDataSource().connector.buildNearFilter; + + // normalize fields as array of included property names + if (fields) { + params.fields = fieldsToArray(fields, Object.keys(this.definition.properties)); + } + + params = removeUndefined(params); + if (near) { + if (supportsGeo) { + // convert it + this.getDataSource().connector.buildNearFilter(params, near); + } else if (params.where) { + // do in memory query + // using all documents + this.getDataSource().connector.all(this.modelName, {}, function (err, data) { + var memory = new Memory(); + var modelName = constr.modelName; + + if (err) { + cb(err); + } else if (Array.isArray(data)) { + memory.define({ + properties: constr.dataSource.definitions[constr.modelName].properties, + settings: constr.dataSource.definitions[constr.modelName].settings, + model: constr + }); + + data.forEach(function (obj) { + memory.create(modelName, obj, function () { + // noop }); - - data.forEach(function (obj) { - memory.create(modelName, obj, function () { - // noop - }); - }); - - memory.all(modelName, params, cb); - } else { - cb(null, []); - } - }.bind(this)); - - // already handled - return; - } - } - - this.getDataSource().connector.all(this.modelName, params, function (err, data) { - if (data && data.forEach) { - data.forEach(function (d, i) { - var obj = new constr(); + }); - obj._initProperties(d, false, params.fields); - - if (params && params.include && params.collect) { - data[i] = obj.__cachedRelations[params.collect]; - } else { - data[i] = obj; - } - }); - if (data && data.countBeforeLimit) { - data.countBeforeLimit = data.countBeforeLimit; - } - if(!supportsGeo && near) { - data = geo.filter(data, near); - } - - cb(err, data); + memory.all(modelName, params, cb); + } else { + cb(null, []); } - else - cb(err, []); - }); + }.bind(this)); + + // already handled + return; + } + } + + this.getDataSource().connector.all(this.modelName, params, function (err, data) { + if (data && data.forEach) { + data.forEach(function (d, i) { + var obj = new constr(); + + obj._initProperties(d, false, params.fields); + + if (params && params.include && params.collect) { + data[i] = obj.__cachedRelations[params.collect]; + } else { + data[i] = obj; + } + }); + if (data && data.countBeforeLimit) { + data.countBeforeLimit = data.countBeforeLimit; + } + if (!supportsGeo && near) { + data = geo.filter(data, near); + } + + cb(err, data); + } + else + cb(err, []); + }); }; // all ~ remoting attributes setRemoting(DataAccessObject.find, { - description: 'Find all instances of the model matched by filter from the data source', - accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}, - returns: {arg: 'data', type: 'array', root: true}, - http: {verb: 'get', path: '/'} + description: 'Find all instances of the model matched by filter from the data source', + accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}, + returns: {arg: 'data', type: 'array', root: true}, + http: {verb: 'get', path: '/'} }); /** * Find one record, same as `all`, limited by 1 and return object, not collection - * + * * @param {Object} params - search conditions: {where: {test: 'me'}} * @param {Function} cb - callback called with (err, instance) */ DataAccessObject.findOne = function findOne(params, cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - if (typeof params === 'function') { - cb = params; - params = {}; - } - params = params || {}; - params.limit = 1; - this.find(params, function (err, collection) { - if (err || !collection || !collection.length > 0) return cb(err, null); - cb(err, collection[0]); - }); + if (typeof params === 'function') { + cb = params; + params = {}; + } + params = params || {}; + params.limit = 1; + this.find(params, function (err, collection) { + if (err || !collection || !collection.length > 0) return cb(err, null); + cb(err, collection[0]); + }); }; setRemoting(DataAccessObject.findOne, { - description: 'Find first instance of the model matched by filter from the data source', - accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}, - returns: {arg: 'data', type: 'object', root: true}, - http: {verb: 'get', path: '/findOne'} + description: 'Find first instance of the model matched by filter from the data source', + accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}, + returns: {arg: 'data', type: 'object', root: true}, + http: {verb: 'get', path: '/findOne'} }); - /** * Destroy all matching records * @param {Object} [where] An object that defines the criteria * @param {Function} [cb] - callback called with (err) */ DataAccessObject.remove = -DataAccessObject.deleteAll = -DataAccessObject.destroyAll = function destroyAll(where, cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + DataAccessObject.deleteAll = + DataAccessObject.destroyAll = function destroyAll(where, cb) { + if (stillConnecting(this.getDataSource(), this, arguments)) return; - if(!cb && 'function' === typeof where) { + if (!cb && 'function' === typeof where) { cb = where; where = undefined; - } - if(!where) { + } + if (!where) { this.getDataSource().connector.destroyAll(this.modelName, function (err, data) { - cb && cb(err, data); + cb && cb(err, data); }.bind(this)); - } else { + } else { // Support an optional where object where = removeUndefined(where); where = this._coerce(where); this.getDataSource().connector.destroyAll(this.modelName, where, function (err, data) { - cb && cb(err, data); + cb && cb(err, data); }.bind(this)); - } -}; + } + }; /** * Destroy a record by id @@ -634,25 +632,24 @@ DataAccessObject.destroyAll = function destroyAll(where, cb) { * @param {Function} cb - callback called with (err) */ DataAccessObject.removeById = -DataAccessObject.deleteById = + DataAccessObject.deleteById = DataAccessObject.destroyById = function deleteById(id, cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - this.getDataSource().connector.destroy(this.modelName, id, function (err) { - if ('function' === typeof cb) { - cb(err); - } - }.bind(this)); + this.getDataSource().connector.destroy(this.modelName, id, function (err) { + if ('function' === typeof cb) { + cb(err); + } + }.bind(this)); }; // deleteById ~ remoting attributes setRemoting(DataAccessObject.deleteById, { - description: 'Delete a model instance by id from the data source', - accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, - http: {verb: 'del', path: '/:id'} + description: 'Delete a model instance by id from the data source', + accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, + http: {verb: 'del', path: '/:id'} }); - /** * Return count of matched records * @@ -660,27 +657,25 @@ setRemoting(DataAccessObject.deleteById, { * @param {Function} cb - callback, called with (err, count) */ DataAccessObject.count = function (where, cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - if (typeof where === 'function') { - cb = where; - where = null; - } - where = removeUndefined(where); - where = this._coerce(where); - this.getDataSource().connector.count(this.modelName, cb, where); + if (typeof where === 'function') { + cb = where; + where = null; + } + where = removeUndefined(where); + where = this._coerce(where); + this.getDataSource().connector.count(this.modelName, cb, where); }; - // count ~ remoting attributes setRemoting(DataAccessObject.count, { - description: 'Count instances of the model matched by where from the data source', - accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'}, - returns: {arg: 'count', type: 'number'}, - http: {verb: 'get', path: '/count'} + description: 'Count instances of the model matched by where from the data source', + accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'}, + returns: {arg: 'count', type: 'number'}, + http: {verb: 'get', path: '/count'} }); - /** * Save instance. When instance haven't id, create method called instead. * Triggers: validate, save, update | create @@ -688,73 +683,73 @@ setRemoting(DataAccessObject.count, { * @param callback(err, obj) */ DataAccessObject.prototype.save = function (options, callback) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - if (typeof options == 'function') { - callback = options; - options = {}; + if (typeof options == 'function') { + callback = options; + options = {}; + } + + callback = callback || function () { + }; + options = options || {}; + + if (!('validate' in options)) { + options.validate = true; + } + if (!('throws' in options)) { + options.throws = false; + } + + var inst = this; + var data = inst.toObject(true); + var Model = this.constructor; + var modelName = Model.modelName; + + if (!getIdValue(Model, this)) { + return Model.create(this, callback); + } + + // validate first + if (!options.validate) { + return save(); + } + + inst.isValid(function (valid) { + if (valid) { + save(); + } else { + var err = new ValidationError(inst); + // throws option is dangerous for async usage + if (options.throws) { + throw err; + } + callback(err, inst); } + }); - callback = callback || function () {}; - options = options || {}; - - if (!('validate' in options)) { - options.validate = true; - } - if (!('throws' in options)) { - options.throws = false; - } - - var inst = this; - var data = inst.toObject(true); - var Model = this.constructor; - var modelName = Model.modelName; - - if (!getIdValue(Model, this)) { - return Model.create(this, callback); - } - - // validate first - if (!options.validate) { - return save(); - } - - inst.isValid(function (valid) { - if (valid) { - save(); - } else { - var err = new ValidationError(inst); - // throws option is dangerous for async usage - if (options.throws) { - throw err; - } - callback(err, inst); - } - }); - - // then save - function save() { - inst.trigger('save', function (saveDone) { - inst.trigger('update', function (updateDone) { - inst._adapter().save(modelName, inst.constructor._forDB(data), function (err) { - if (err) { - return callback(err, inst); - } - inst._initProperties(data, false); - updateDone.call(inst, function () { - saveDone.call(inst, function () { - callback(err, inst); - }); - }); - }); - }, data); - }, data); - } + // then save + function save() { + inst.trigger('save', function (saveDone) { + inst.trigger('update', function (updateDone) { + inst._adapter().save(modelName, inst.constructor._forDB(data), function (err) { + if (err) { + return callback(err, inst); + } + inst._initProperties(data, false); + updateDone.call(inst, function () { + saveDone.call(inst, function () { + callback(err, inst); + }); + }); + }); + }, data); + }, data); + } }; - DataAccessObject.prototype.isNewRecord = function () { - return !getIdValue(this.constructor, this); + return !getIdValue(this.constructor, this); }; /** @@ -762,7 +757,7 @@ DataAccessObject.prototype.isNewRecord = function () { * @private */ DataAccessObject.prototype._adapter = function () { - return this.getDataSource().connector; + return this.getDataSource().connector; }; /** @@ -771,23 +766,22 @@ DataAccessObject.prototype._adapter = function () { * @triggers `destroy` hook (async) before and after destroying object */ DataAccessObject.prototype.remove = -DataAccessObject.prototype.delete = -DataAccessObject.prototype.destroy = function (cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + DataAccessObject.prototype.delete = + DataAccessObject.prototype.destroy = function (cb) { + if (stillConnecting(this.getDataSource(), this, arguments)) return; - this.trigger('destroy', function (destroyed) { + this.trigger('destroy', function (destroyed) { this._adapter().destroy(this.constructor.modelName, getIdValue(this.constructor, this), function (err) { - if (err) { - return cb(err); - } + if (err) { + return cb(err); + } - destroyed(function () { - if(cb) cb(); - }); + destroyed(function () { + if (cb) cb(); + }); }.bind(this)); - }); -}; - + }); + }; /** * Update single attribute @@ -799,9 +793,9 @@ DataAccessObject.prototype.destroy = function (cb) { * @param {Function} callback - callback called with (err, instance) */ DataAccessObject.prototype.updateAttribute = function updateAttribute(name, value, callback) { - var data = {}; - data[name] = value; - this.updateAttributes(data, callback); + var data = {}; + data[name] = value; + this.updateAttributes(data, callback); }; /** @@ -814,66 +808,66 @@ DataAccessObject.prototype.updateAttribute = function updateAttribute(name, valu * @param {Function} callback - callback called with (err, instance) */ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - var inst = this; - var model = this.constructor.modelName; + var inst = this; + var model = this.constructor.modelName; - if (typeof data === 'function') { - cb = data; - data = null; - } + if (typeof data === 'function') { + cb = data; + data = null; + } - if (!data) { - data = {}; - } + if (!data) { + data = {}; + } - // update instance's properties - for(var key in data) { - inst[key] = data[key]; - } + // update instance's properties + for (var key in data) { + inst[key] = data[key]; + } - inst.isValid(function (valid) { - if (!valid) { - if (cb) { - cb(new ValidationError(inst), inst); + inst.isValid(function (valid) { + if (!valid) { + if (cb) { + cb(new ValidationError(inst), inst); + } + } else { + inst.trigger('save', function (saveDone) { + inst.trigger('update', function (done) { + + for (var key in data) { + inst[key] = data[key]; + } + + inst._adapter().updateAttributes(model, getIdValue(inst.constructor, inst), inst.constructor._forDB(data), function (err) { + if (!err) { + // update $was attrs + for (var key in data) { + inst.__dataWas[key] = inst.__data[key]; + } + ; } - } else { - inst.trigger('save', function (saveDone) { - inst.trigger('update', function (done) { - - for(var key in data) { - inst[key] = data[key]; - } - - inst._adapter().updateAttributes(model, getIdValue(inst.constructor, inst), inst.constructor._forDB(data), function (err) { - if (!err) { - // update $was attrs - for(var key in data) { - inst.__dataWas[key] = inst.__data[key]; - }; - } - done.call(inst, function () { - saveDone.call(inst, function () { - cb(err, inst); - }); - }); - }); - }, data); - }, data); - } - }, data); + done.call(inst, function () { + saveDone.call(inst, function () { + cb(err, inst); + }); + }); + }); + }, data); + }, data); + } + }, data); }; // updateAttributes ~ remoting attributes setRemoting(DataAccessObject.prototype.updateAttributes, { - description: 'Update attributes for a model instance and persist it into the data source', - accepts: {arg: 'data', type: 'object', http: {source: 'body'}, description: 'An object of model property name/value pairs'}, - returns: {arg: 'data', type: 'object', root: true}, - http: {verb: 'put', path: '/'} + description: 'Update attributes for a model instance and persist it into the data source', + accepts: {arg: 'data', type: 'object', http: {source: 'body'}, description: 'An object of model property name/value pairs'}, + returns: {arg: 'data', type: 'object', root: true}, + http: {verb: 'put', path: '/'} }); - /** * Reload object from persistence * @@ -881,17 +875,17 @@ setRemoting(DataAccessObject.prototype.updateAttributes, { * @param {Function} callback - called with (err, instance) arguments */ DataAccessObject.prototype.reload = function reload(callback) { - if (stillConnecting(this.getDataSource(), this, arguments)) return; + if (stillConnecting(this.getDataSource(), this, arguments)) return; - this.constructor.findById(getIdValue(this.constructor, this), callback); + this.constructor.findById(getIdValue(this.constructor, this), callback); }; /* -setRemoting(DataAccessObject.prototype.reload, { - description: 'Reload a model instance from the data source', - returns: {arg: 'data', type: 'object', root: true} -}); -*/ + setRemoting(DataAccessObject.prototype.reload, { + description: 'Reload a model instance from the data source', + returns: {arg: 'data', type: 'object', root: true} + }); + */ /** * Define readonly property on object @@ -901,12 +895,12 @@ setRemoting(DataAccessObject.prototype.reload, { * @param {Mixed} value */ function defineReadonlyProp(obj, key, value) { - Object.defineProperty(obj, key, { - writable: false, - enumerable: true, - configurable: true, - value: value - }); + Object.defineProperty(obj, key, { + writable: false, + enumerable: true, + configurable: true, + value: value + }); } var defineScope = require('./scope.js').defineScope; @@ -915,7 +909,7 @@ var defineScope = require('./scope.js').defineScope; * Define scope */ DataAccessObject.scope = function (name, filter, targetClass) { - defineScope(this, targetClass || this, name, filter); + defineScope(this, targetClass || this, name, filter); }; // jutil.mixin(DataAccessObject, validations.Validatable); diff --git a/lib/datasource.js b/lib/datasource.js index 425796cb..8f11368c 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -15,8 +15,6 @@ var fs = require('fs'); var assert = require('assert'); var async = require('async'); -var existsSync = fs.existsSync || path.existsSync; - /** * Export public API */ @@ -54,115 +52,112 @@ var slice = Array.prototype.slice; * ``` */ function DataSource(name, settings, modelBuilder) { - if (!(this instanceof DataSource)) { - return new DataSource(name, settings); + if (!(this instanceof DataSource)) { + return new DataSource(name, settings); + } + + // Check if the settings object is passed as the first argument + if (typeof name === 'object' && settings === undefined) { + settings = name; + name = undefined; + } + + // Check if the first argument is a URL + if (typeof name === 'string' && name.indexOf('://') !== -1) { + name = utils.parseSettings(name); + } + + // Check if the settings is in the form of URL string + if (typeof settings === 'string' && settings.indexOf('://') !== -1) { + settings = utils.parseSettings(settings); + } + + this.modelBuilder = modelBuilder || new ModelBuilder(); + this.models = this.modelBuilder.models; + this.definitions = this.modelBuilder.definitions; + + // operation metadata + // Initialize it before calling setup as the connector might register operations + this._operations = {}; + + this.setup(name, settings); + + this._setupConnector(); + + // connector + var connector = this.connector; + + // DataAccessObject - connector defined or supply the default + var dao = (connector && connector.DataAccessObject) || this.constructor.DataAccessObject; + this.DataAccessObject = function () { + }; + + // define DataAccessObject methods + Object.keys(dao).forEach(function (name) { + var fn = dao[name]; + this.DataAccessObject[name] = fn; + + if (typeof fn === 'function') { + this.defineOperation(name, { + accepts: fn.accepts, + 'returns': fn.returns, + http: fn.http, + remoteEnabled: fn.shared ? true : false, + scope: this.DataAccessObject, + fnName: name + }); } + }.bind(this)); - // Check if the settings object is passed as the first argument - if (typeof name === 'object' && settings === undefined) { - settings = name; - name = undefined; + // define DataAccessObject.prototype methods + Object.keys(dao.prototype).forEach(function (name) { + var fn = dao.prototype[name]; + this.DataAccessObject.prototype[name] = fn; + if (typeof fn === 'function') { + this.defineOperation(name, { + prototype: true, + accepts: fn.accepts, + 'returns': fn.returns, + http: fn.http, + remoteEnabled: fn.shared ? true : false, + scope: this.DataAccessObject.prototype, + fnName: name + }); } - - // Check if the first argument is a URL - if(typeof name === 'string' && name.indexOf('://') !== -1 ) { - name = utils.parseSettings(name); - } - - // Check if the settings is in the form of URL string - if(typeof settings === 'string' && settings.indexOf('://') !== -1 ) { - settings = utils.parseSettings(settings); - } - - this.modelBuilder = modelBuilder || new ModelBuilder(); - this.models = this.modelBuilder.models; - this.definitions = this.modelBuilder.definitions; - - // operation metadata - // Initialize it before calling setup as the connector might register operations - this._operations = {}; - - this.setup(name, settings); - - this._setupConnector(); - - // connector - var connector = this.connector; - - // DataAccessObject - connector defined or supply the default - var dao = (connector && connector.DataAccessObject) || this.constructor.DataAccessObject; - this.DataAccessObject = function() {}; - - // define DataAccessObject methods - Object.keys(dao).forEach(function (name) { - var fn = dao[name]; - this.DataAccessObject[name] = fn; - - if(typeof fn === 'function') { - this.defineOperation(name, { - accepts: fn.accepts, - 'returns': fn.returns, - http: fn.http, - remoteEnabled: fn.shared ? true : false, - scope: this.DataAccessObject, - fnName: name - }); - } - }.bind(this)); - - // define DataAccessObject.prototype methods - Object.keys(dao.prototype).forEach(function (name) { - var fn = dao.prototype[name]; - this.DataAccessObject.prototype[name] = fn; - if(typeof fn === 'function') { - this.defineOperation(name, { - prototype: true, - accepts: fn.accepts, - 'returns': fn.returns, - http: fn.http, - remoteEnabled: fn.shared ? true : false, - scope: this.DataAccessObject.prototype, - fnName: name - }); - } - }.bind(this)); + }.bind(this)); } - - util.inherits(DataSource, EventEmitter); // allow child classes to supply a data access object DataSource.DataAccessObject = DataAccessObject; - - /** * Set up the connector instance for backward compatibility with JugglingDB schema/adapter * @private */ DataSource.prototype._setupConnector = function () { - this.connector = this.connector || this.adapter; // The legacy JugglingDB adapter will set up `adapter` property - this.adapter = this.connector; // Keep the adapter as an alias to connector - if (this.connector) { - if (!this.connector.dataSource) { - // 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); - }; - - this.connector.logger = function (query) { - var t1 = Date.now(); - var log = this.log; - return function (q) { - log(q || query, t1); - }; - }; + this.connector = this.connector || this.adapter; // The legacy JugglingDB adapter will set up `adapter` property + this.adapter = this.connector; // Keep the adapter as an alias to connector + if (this.connector) { + if (!this.connector.dataSource) { + // 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); + }; + + this.connector.logger = function (query) { + var t1 = Date.now(); + var log = this.log; + return function (q) { + log(q || query, t1); + }; + }; + } }; // List possible connector module names @@ -181,11 +176,11 @@ function connectorModuleNames(name) { function tryModules(names, loader) { var mod; loader = loader || require; - for(var m =0; m 1) ? str.slice(1).toLowerCase() : ''); + if (!str) { + return str; + } + return str.charAt(0).toUpperCase() + ((str.length > 1) ? str.slice(1).toLowerCase() : ''); } function fromDBName(dbName, camelCase) { - if (!dbName) { - return dbName; - } - var parts = dbName.split(/-|_/); - parts[0] = camelCase ? parts[0].toLowerCase() : capitalize(parts[0]); + if (!dbName) { + return dbName; + } + var parts = dbName.split(/-|_/); + parts[0] = camelCase ? parts[0].toLowerCase() : capitalize(parts[0]); - for (var i = 1; i < parts.length; i++) { - parts[i] = capitalize(parts[i]); - } - return parts.join(''); + for (var i = 1; i < parts.length; i++) { + parts[i] = capitalize(parts[i]); + } + return parts.join(''); } /** @@ -911,25 +903,25 @@ function fromDBName(dbName, camelCase) { * @param {Function} [cb] The callback function */ DataSource.prototype.discoverSchema = function (modelName, options, cb) { - options = options || {}; + options = options || {}; - if(!cb && 'function' === typeof options) { - cb = options; - options = {}; + if (!cb && 'function' === typeof options) { + cb = options; + options = {}; + } + options.visited = {}; + options.relations = false; + + this.discoverSchemas(modelName, options, function (err, schemas) { + if (err) { + cb && cb(err, schemas); + return; } - options.visited = {}; - options.relations = false; - - this.discoverSchemas(modelName, options, function(err, schemas) { - if(err) { - cb && cb(err, schemas); - return; - } - for(var s in schemas) { - cb && cb(null, schemas[s]); - return; - } - }); + for (var s in schemas) { + cb && cb(null, schemas[s]); + return; + } + }); } /** @@ -947,161 +939,160 @@ DataSource.prototype.discoverSchema = function (modelName, options, cb) { * @param {Function} [cb] The callback function */ DataSource.prototype.discoverSchemas = function (modelName, options, cb) { - options = options || {}; + options = options || {}; - if(!cb && 'function' === typeof options) { - cb = options; - options = {}; + if (!cb && 'function' === typeof options) { + cb = options; + options = {}; + } + + var self = this; + var schemaName = this.connector.name || this.name; + + var tasks = [ + this.discoverModelProperties.bind(this, modelName, options), + this.discoverPrimaryKeys.bind(this, modelName, options) ]; + + var followingRelations = options.associations || options.relations; + if (followingRelations) { + tasks.push(this.discoverForeignKeys.bind(this, modelName, options)); + } + + async.parallel(tasks, function (err, results) { + + if (err) { + cb && cb(err); + return; } - var self = this; - var schemaName = this.connector.name || this.name; - - var tasks = [ - this.discoverModelProperties.bind(this, modelName, options), - this.discoverPrimaryKeys.bind(this, modelName, options) ]; - - var followingRelations = options.associations || options.relations; - if (followingRelations) { - tasks.push(this.discoverForeignKeys.bind(this, modelName, options)); + var columns = results[0]; + if (!columns || columns.length === 0) { + cb && cb(); + return; } - async.parallel(tasks, function (err, results) { - - if (err) { - cb && cb(err); - return; - } - - var columns = results[0]; - if (!columns || columns.length === 0) { - cb && cb(); - return; - } - - // Handle primary keys - var primaryKeys = results[1]; - var pks = {}; - primaryKeys.forEach(function (pk) { - pks[pk.columnName] = pk.keySeq; - }); - - if (self.settings.debug) { - console.log('Primary keys: ', pks); - } - - var schema = { - name: fromDBName(modelName, false), - options: { - idInjection: false // DO NOT add id property - }, - properties: { - } - }; - - schema.options[schemaName] = { - schema: columns[0].owner, - table: modelName - }; - - columns.forEach(function (item) { - var i = item; - - var propName = fromDBName(item.columnName, true); - schema.properties[propName] = { - type: item.type, - required: (item.nullable === 'N'), - length: item.dataLength, - precision: item.dataPrecision, - scale: item.dataScale - }; - - if (pks[item.columnName]) { - schema.properties[propName].id = pks[item.columnName]; - } - schema.properties[propName][schemaName] = { - columnName: i.columnName, - dataType: i.dataType, - dataLength: i.dataLength, - dataPrecision: item.dataPrecision, - dataScale: item.dataScale, - nullable: i.nullable - }; - }); - - // Add current modelName to the visited tables - options.visited = options.visited || {}; - var schemaKey = columns[0].owner + '.' + modelName; - if (!options.visited.hasOwnProperty(schemaKey)) { - if(self.settings.debug) { - console.log('Adding schema for ' + schemaKey); - } - options.visited[schemaKey] = schema; - } - - var otherTables = {}; - if (followingRelations) { - // Handle foreign keys - var fks = {}; - var foreignKeys = results[2]; - foreignKeys.forEach(function (fk) { - var fkInfo = { - keySeq: fk.keySeq, - owner: fk.pkOwner, - tableName: fk.pkTableName, - columnName: fk.pkColumnName - }; - if (fks[fk.fkName]) { - fks[fk.fkName].push(fkInfo); - } else { - fks[fk.fkName] = [fkInfo]; - } - }); - - if (self.settings.debug) { - console.log('Foreign keys: ', fks); - } - - schema.options.relations = {}; - foreignKeys.forEach(function (fk) { - var propName = fromDBName(fk.pkTableName, true); - schema.options.relations[propName] = { - model: fromDBName(fk.pkTableName, false), - type: 'belongsTo', - foreignKey: fromDBName(fk.fkColumnName, true) - }; - - var key = fk.pkOwner + '.' + fk.pkTableName; - if (!options.visited.hasOwnProperty(key) && !otherTables.hasOwnProperty(key)) { - otherTables[key] = {owner: fk.pkOwner, tableName: fk.pkTableName}; - } - }); - } - - if (Object.keys(otherTables).length === 0) { - cb && cb(null, options.visited); - } else { - var moreTasks = []; - for (var t in otherTables) { - if(self.settings.debug) { - console.log('Discovering related schema for ' + schemaKey); - } - var newOptions = {}; - for(var key in options) { - newOptions[key] = options[key]; - } - newOptions.owner = otherTables[t].owner; - - moreTasks.push(DataSource.prototype.discoverSchemas.bind(self, otherTables[t].tableName, newOptions)); - } - async.parallel(moreTasks, function (err, results) { - var result = results && results[0]; - cb && cb(err, result); - }); - } + // Handle primary keys + var primaryKeys = results[1]; + var pks = {}; + primaryKeys.forEach(function (pk) { + pks[pk.columnName] = pk.keySeq; }); -}; + if (self.settings.debug) { + console.log('Primary keys: ', pks); + } + + var schema = { + name: fromDBName(modelName, false), + options: { + idInjection: false // DO NOT add id property + }, + properties: { + } + }; + + schema.options[schemaName] = { + schema: columns[0].owner, + table: modelName + }; + + columns.forEach(function (item) { + var i = item; + + var propName = fromDBName(item.columnName, true); + schema.properties[propName] = { + type: item.type, + required: (item.nullable === 'N'), + length: item.dataLength, + precision: item.dataPrecision, + scale: item.dataScale + }; + + if (pks[item.columnName]) { + schema.properties[propName].id = pks[item.columnName]; + } + schema.properties[propName][schemaName] = { + columnName: i.columnName, + dataType: i.dataType, + dataLength: i.dataLength, + dataPrecision: item.dataPrecision, + dataScale: item.dataScale, + nullable: i.nullable + }; + }); + + // Add current modelName to the visited tables + options.visited = options.visited || {}; + var schemaKey = columns[0].owner + '.' + modelName; + if (!options.visited.hasOwnProperty(schemaKey)) { + if (self.settings.debug) { + console.log('Adding schema for ' + schemaKey); + } + options.visited[schemaKey] = schema; + } + + var otherTables = {}; + if (followingRelations) { + // Handle foreign keys + var fks = {}; + var foreignKeys = results[2]; + foreignKeys.forEach(function (fk) { + var fkInfo = { + keySeq: fk.keySeq, + owner: fk.pkOwner, + tableName: fk.pkTableName, + columnName: fk.pkColumnName + }; + if (fks[fk.fkName]) { + fks[fk.fkName].push(fkInfo); + } else { + fks[fk.fkName] = [fkInfo]; + } + }); + + if (self.settings.debug) { + console.log('Foreign keys: ', fks); + } + + schema.options.relations = {}; + foreignKeys.forEach(function (fk) { + var propName = fromDBName(fk.pkTableName, true); + schema.options.relations[propName] = { + model: fromDBName(fk.pkTableName, false), + type: 'belongsTo', + foreignKey: fromDBName(fk.fkColumnName, true) + }; + + var key = fk.pkOwner + '.' + fk.pkTableName; + if (!options.visited.hasOwnProperty(key) && !otherTables.hasOwnProperty(key)) { + otherTables[key] = {owner: fk.pkOwner, tableName: fk.pkTableName}; + } + }); + } + + if (Object.keys(otherTables).length === 0) { + cb && cb(null, options.visited); + } else { + var moreTasks = []; + for (var t in otherTables) { + if (self.settings.debug) { + console.log('Discovering related schema for ' + schemaKey); + } + var newOptions = {}; + for (var key in options) { + newOptions[key] = options[key]; + } + newOptions.owner = otherTables[t].owner; + + moreTasks.push(DataSource.prototype.discoverSchemas.bind(self, otherTables[t].tableName, newOptions)); + } + async.parallel(moreTasks, function (err, results) { + var result = results && results[0]; + cb && cb(err, result); + }); + } + }); +}; /** * Discover schema from a given table/view synchronously @@ -1117,132 +1108,132 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) { * @param {Object} [options] The options */ DataSource.prototype.discoverSchemasSync = function (modelName, options) { - var self = this; - var schemaName = this.name || this.connector.name; + var self = this; + var schemaName = this.name || this.connector.name; - var columns = this.discoverModelPropertiesSync(modelName, options); - if (!columns || columns.length === 0) { - return []; + var columns = this.discoverModelPropertiesSync(modelName, options); + if (!columns || columns.length === 0) { + return []; + } + + // Handle primary keys + var primaryKeys = this.discoverPrimaryKeysSync(modelName, options); + var pks = {}; + primaryKeys.forEach(function (pk) { + pks[pk.columnName] = pk.keySeq; + }); + + if (self.settings.debug) { + console.log('Primary keys: ', pks); + } + + var schema = { + name: fromDBName(modelName, false), + options: { + idInjection: false // DO NOT add id property + }, + properties: { } + }; - // Handle primary keys - var primaryKeys = this.discoverPrimaryKeysSync(modelName, options); - var pks = {}; - primaryKeys.forEach(function (pk) { - pks[pk.columnName] = pk.keySeq; + schema.options[schemaName] = { + schema: columns.length > 0 && columns[0].owner, + table: modelName + }; + + columns.forEach(function (item) { + var i = item; + + var propName = fromDBName(item.columnName, true); + schema.properties[propName] = { + type: item.type, + required: (item.nullable === 'N'), + length: item.dataLength, + precision: item.dataPrecision, + scale: item.dataScale + }; + + if (pks[item.columnName]) { + schema.properties[propName].id = pks[item.columnName]; + } + schema.properties[propName][schemaName] = { + columnName: i.columnName, + dataType: i.dataType, + dataLength: i.dataLength, + dataPrecision: item.dataPrecision, + dataScale: item.dataScale, + nullable: i.nullable + }; + }); + + // Add current modelName to the visited tables + options.visited = options.visited || {}; + var schemaKey = columns[0].owner + '.' + modelName; + if (!options.visited.hasOwnProperty(schemaKey)) { + if (self.settings.debug) { + console.log('Adding schema for ' + schemaKey); + } + options.visited[schemaKey] = schema; + } + + var otherTables = {}; + var followingRelations = options.associations || options.relations; + if (followingRelations) { + // Handle foreign keys + var fks = {}; + var foreignKeys = this.discoverForeignKeysSync(modelName, options); + foreignKeys.forEach(function (fk) { + var fkInfo = { + keySeq: fk.keySeq, + owner: fk.pkOwner, + tableName: fk.pkTableName, + columnName: fk.pkColumnName + }; + if (fks[fk.fkName]) { + fks[fk.fkName].push(fkInfo); + } else { + fks[fk.fkName] = [fkInfo]; + } }); if (self.settings.debug) { - console.log('Primary keys: ', pks); + console.log('Foreign keys: ', fks); } - var schema = { - name: fromDBName(modelName, false), - options: { - idInjection: false // DO NOT add id property - }, - properties: { - } - }; + schema.options.relations = {}; + foreignKeys.forEach(function (fk) { + var propName = fromDBName(fk.pkTableName, true); + schema.options.relations[propName] = { + model: fromDBName(fk.pkTableName, false), + type: 'belongsTo', + foreignKey: fromDBName(fk.fkColumnName, true) + }; - schema.options[schemaName] = { - schema: columns.length > 0 && columns[0].owner, - table: modelName - }; - - columns.forEach(function (item) { - var i = item; - - var propName = fromDBName(item.columnName, true); - schema.properties[propName] = { - type: item.type, - required: (item.nullable === 'N'), - length: item.dataLength, - precision: item.dataPrecision, - scale: item.dataScale - }; - - if (pks[item.columnName]) { - schema.properties[propName].id = pks[item.columnName]; - } - schema.properties[propName][schemaName] = { - columnName: i.columnName, - dataType: i.dataType, - dataLength: i.dataLength, - dataPrecision: item.dataPrecision, - dataScale: item.dataScale, - nullable: i.nullable - }; + var key = fk.pkOwner + '.' + fk.pkTableName; + if (!options.visited.hasOwnProperty(key) && !otherTables.hasOwnProperty(key)) { + otherTables[key] = {owner: fk.pkOwner, tableName: fk.pkTableName}; + } }); + } - // Add current modelName to the visited tables - options.visited = options.visited || {}; - var schemaKey = columns[0].owner + '.' + modelName; - if (!options.visited.hasOwnProperty(schemaKey)) { - if (self.settings.debug) { - console.log('Adding schema for ' + schemaKey); - } - options.visited[schemaKey] = schema; + if (Object.keys(otherTables).length === 0) { + return options.visited; + } else { + var moreTasks = []; + for (var t in otherTables) { + if (self.settings.debug) { + console.log('Discovering related schema for ' + schemaKey); + } + var newOptions = {}; + for (var key in options) { + newOptions[key] = options[key]; + } + newOptions.owner = otherTables[t].owner; + self.discoverSchemasSync(otherTables[t].tableName, newOptions); } + return options.visited; - var otherTables = {}; - var followingRelations = options.associations || options.relations; - if (followingRelations) { - // Handle foreign keys - var fks = {}; - var foreignKeys = this.discoverForeignKeysSync(modelName, options); - foreignKeys.forEach(function (fk) { - var fkInfo = { - keySeq: fk.keySeq, - owner: fk.pkOwner, - tableName: fk.pkTableName, - columnName: fk.pkColumnName - }; - if (fks[fk.fkName]) { - fks[fk.fkName].push(fkInfo); - } else { - fks[fk.fkName] = [fkInfo]; - } - }); - - if (self.settings.debug) { - console.log('Foreign keys: ', fks); - } - - schema.options.relations = {}; - foreignKeys.forEach(function (fk) { - var propName = fromDBName(fk.pkTableName, true); - schema.options.relations[propName] = { - model: fromDBName(fk.pkTableName, false), - type: 'belongsTo', - foreignKey: fromDBName(fk.fkColumnName, true) - }; - - var key = fk.pkOwner + '.' + fk.pkTableName; - if (!options.visited.hasOwnProperty(key) && !otherTables.hasOwnProperty(key)) { - otherTables[key] = {owner: fk.pkOwner, tableName: fk.pkTableName}; - } - }); - } - - if (Object.keys(otherTables).length === 0) { - return options.visited; - } else { - var moreTasks = []; - for (var t in otherTables) { - if (self.settings.debug) { - console.log('Discovering related schema for ' + schemaKey); - } - var newOptions = {}; - for(var key in options) { - newOptions[key] = options[key]; - } - newOptions.owner = otherTables[t].owner; - self.discoverSchemasSync(otherTables[t].tableName, newOptions); - } - return options.visited; - - } + } }; /** @@ -1260,26 +1251,26 @@ DataSource.prototype.discoverSchemasSync = function (modelName, options) { * @param {Function} [cb] The callback function */ DataSource.prototype.discoverAndBuildModels = function (modelName, options, cb) { - var self = this; - this.discoverSchemas(modelName, options, function (err, schemas) { - if (err) { - cb && cb(err, schemas); - return; - } + var self = this; + this.discoverSchemas(modelName, options, function (err, schemas) { + if (err) { + cb && cb(err, schemas); + return; + } - var schemaList = []; - for (var s in schemas) { - var schema = schemas[s]; - schemaList.push(schema); - } + var schemaList = []; + for (var s in schemas) { + var schema = schemas[s]; + schemaList.push(schema); + } - var models = self.modelBuilder.buildModels(schemaList); - // Now attach the models to the data source - for(var m in models) { - models[m].attachTo(self); - } - cb && cb(err, models); - }); + var models = self.modelBuilder.buildModels(schemaList); + // Now attach the models to the data source + for (var m in models) { + models[m].attachTo(self); + } + cb && cb(err, models); + }); }; /** @@ -1296,16 +1287,16 @@ DataSource.prototype.discoverAndBuildModels = function (modelName, options, cb) * @param {Object} [options] The options */ DataSource.prototype.discoverAndBuildModelsSync = function (modelName, options) { - var schemas = this.discoverSchemasSync(modelName, options); + var schemas = this.discoverSchemasSync(modelName, options); - var schemaList = []; - for (var s in schemas) { - var schema = schemas[s]; - schemaList.push(schema); - } + var schemaList = []; + for (var s in schemas) { + var schema = schemas[s]; + schemaList.push(schema); + } - var models = this.modelBuilder.buildModels(schemaList); - return models; + var models = this.modelBuilder.buildModels(schemaList); + return models; }; /** @@ -1314,20 +1305,20 @@ DataSource.prototype.discoverAndBuildModelsSync = function (modelName, options) * @param {String[]} [models] A model name or an array of model names. If not present, apply to all models */ DataSource.prototype.isActual = function (models, cb) { - this.freeze(); - if (this.connector.isActual) { - this.connector.isActual(models, cb); - } else { - if ((!cb) && ('function' === typeof models)) { - cb = models; - models = undefined; - } - if (cb) { - process.nextTick(function() { - cb(null, true); - }); - } + this.freeze(); + if (this.connector.isActual) { + this.connector.isActual(models, cb); + } else { + if ((!cb) && ('function' === typeof models)) { + cb = models; + models = undefined; } + if (cb) { + process.nextTick(function () { + cb(null, true); + }); + } + } }; /** @@ -1337,19 +1328,19 @@ DataSource.prototype.isActual = function (models, cb) { * @private used by connectors */ DataSource.prototype.log = function (sql, t) { - this.emit('log', sql, t); + this.emit('log', sql, t); }; /** * Freeze dataSource. Behavior depends on connector */ DataSource.prototype.freeze = function freeze() { - if (this.connector.freezeDataSource) { - this.connector.freezeDataSource(); - } - if (this.connector.freezeSchema) { - this.connector.freezeSchema(); - } + if (this.connector.freezeDataSource) { + this.connector.freezeDataSource(); + } + if (this.connector.freezeSchema) { + this.connector.freezeSchema(); + } } /** @@ -1357,7 +1348,7 @@ DataSource.prototype.freeze = function freeze() { * @param {String} modelName The model name */ DataSource.prototype.tableName = function (modelName) { - return this.getModelDefinition(modelName).tableName(this.connector.name); + return this.getModelDefinition(modelName).tableName(this.connector.name); }; /** @@ -1367,7 +1358,7 @@ DataSource.prototype.tableName = function (modelName) { * @returns {String} columnName */ DataSource.prototype.columnName = function (modelName, propertyName) { - return this.getModelDefinition(modelName).columnName(this.connector.name, propertyName); + return this.getModelDefinition(modelName).columnName(this.connector.name, propertyName); }; /** @@ -1377,7 +1368,7 @@ DataSource.prototype.columnName = function (modelName, propertyName) { * @returns {Object} column metadata */ DataSource.prototype.columnMetadata = function (modelName, propertyName) { - return this.getModelDefinition(modelName).columnMetadata(this.connector.name, propertyName); + return this.getModelDefinition(modelName).columnMetadata(this.connector.name, propertyName); }; /** @@ -1386,7 +1377,7 @@ DataSource.prototype.columnMetadata = function (modelName, propertyName) { * @returns {String[]} column names */ DataSource.prototype.columnNames = function (modelName) { - return this.getModelDefinition(modelName).columnNames(this.connector.name); + return this.getModelDefinition(modelName).columnNames(this.connector.name); }; /** @@ -1394,8 +1385,8 @@ DataSource.prototype.columnNames = function (modelName) { * @param {String} modelName The model name * @returns {String} columnName for ID */ -DataSource.prototype.idColumnName = function(modelName) { - return this.getModelDefinition(modelName).idColumnName(this.connector.name); +DataSource.prototype.idColumnName = function (modelName) { + return this.getModelDefinition(modelName).idColumnName(this.connector.name); }; /** @@ -1403,11 +1394,11 @@ DataSource.prototype.idColumnName = function(modelName) { * @param {String} modelName The model name * @returns {String} property name for ID */ -DataSource.prototype.idName = function(modelName) { - if(!this.getModelDefinition(modelName).idName) { - console.error('No id name', this.getModelDefinition(modelName)); - } - return this.getModelDefinition(modelName).idName(); +DataSource.prototype.idName = function (modelName) { + if (!this.getModelDefinition(modelName).idName) { + console.error('No id name', this.getModelDefinition(modelName)); + } + return this.getModelDefinition(modelName).idName(); }; /** @@ -1416,10 +1407,9 @@ DataSource.prototype.idName = function(modelName) { * @returns {String[]} property names for IDs */ DataSource.prototype.idNames = function (modelName) { - return this.getModelDefinition(modelName).idNames(); + return this.getModelDefinition(modelName).idNames(); }; - /** * Define foreign key to another model * @param {String} className The model name that owns the key @@ -1427,36 +1417,36 @@ DataSource.prototype.idNames = function (modelName) { * @param {String} foreignClassName The foreign model name */ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key, foreignClassName) { - // quit if key already defined - if (this.getModelDefinition(className).rawProperties[key]) return; + // quit if key already defined + if (this.getModelDefinition(className).rawProperties[key]) return; - var defaultType = Number; - if(foreignClassName) { - var foreignModel = this.getModelDefinition(foreignClassName); - var pkName = foreignModel && foreignModel.idName(); - if(pkName) { - defaultType = foreignModel.properties[pkName].type; - } + var defaultType = Number; + if (foreignClassName) { + var foreignModel = this.getModelDefinition(foreignClassName); + var pkName = foreignModel && foreignModel.idName(); + if (pkName) { + defaultType = foreignModel.properties[pkName].type; } - if (this.connector.defineForeignKey) { - var cb = function (err, keyType) { - if (err) throw err; - // Add the foreign key property to the data source _models - this.defineProperty(className, key, {type: keyType || defaultType}); - }.bind(this); - switch (this.connector.defineForeignKey.length) { - case 4: - this.connector.defineForeignKey(className, key, foreignClassName, cb); - break; - default: - case 3: - this.connector.defineForeignKey(className, key, cb); - break; - } - } else { - // Add the foreign key property to the data source _models - this.defineProperty(className, key, {type: defaultType}); + } + if (this.connector.defineForeignKey) { + var cb = function (err, keyType) { + if (err) throw err; + // Add the foreign key property to the data source _models + this.defineProperty(className, key, {type: keyType || defaultType}); + }.bind(this); + switch (this.connector.defineForeignKey.length) { + case 4: + this.connector.defineForeignKey(className, key, foreignClassName, cb); + break; + default: + case 3: + this.connector.defineForeignKey(className, key, cb); + break; } + } else { + // Add the foreign key property to the data source _models + this.defineProperty(className, key, {type: defaultType}); + } }; @@ -1465,17 +1455,17 @@ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key * @param {Fucntion} [cb] The callback function */ DataSource.prototype.disconnect = function disconnect(cb) { - var self = this; - if (this.connected && (typeof this.connector.disconnect === 'function')) { - this.connector.disconnect(function(err, result) { - self.connected = false; - cb && cb(err, result); - }); - } else { - process.nextTick(function() { - cb && cb(); - }); - } + var self = this; + if (this.connected && (typeof this.connector.disconnect === 'function')) { + this.connector.disconnect(function (err, result) { + self.connected = false; + cb && cb(err, result); + }); + } else { + process.nextTick(function () { + cb && cb(); + }); + } }; /** @@ -1486,39 +1476,39 @@ DataSource.prototype.disconnect = function disconnect(cb) { * @private */ DataSource.prototype.copyModel = function copyModel(Master) { - var dataSource = this; - var className = Master.modelName; - var md = Master.modelBuilder.getModelDefinition(className); - var Slave = function SlaveModel() { - Master.apply(this, [].slice.call(arguments)); - }; + var dataSource = this; + var className = Master.modelName; + var md = Master.modelBuilder.getModelDefinition(className); + var Slave = function SlaveModel() { + Master.apply(this, [].slice.call(arguments)); + }; - util.inherits(Slave, Master); + util.inherits(Slave, Master); - // Delegating static properties - Slave.__proto__ = Master; + // Delegating static properties + Slave.__proto__ = Master; - hiddenProperty(Slave, 'dataSource', dataSource); - hiddenProperty(Slave, 'modelName', className); - hiddenProperty(Slave, 'relations', Master.relations); + hiddenProperty(Slave, 'dataSource', dataSource); + hiddenProperty(Slave, 'modelName', className); + hiddenProperty(Slave, 'relations', Master.relations); - if (!(className in dataSource.modelBuilder.models)) { + if (!(className in dataSource.modelBuilder.models)) { - // store class in model pool - dataSource.modelBuilder.models[className] = Slave; - dataSource.modelBuilder.definitions[className] = new ModelDefinition(dataSource.modelBuilder, md.name, md.properties, md.settings); - - if ((!dataSource.isTransaction) && dataSource.connector && dataSource.connector.define) { - dataSource.connector.define({ - model: Slave, - properties: md.properties, - settings: md.settings - }); - } + // store class in model pool + dataSource.modelBuilder.models[className] = Slave; + dataSource.modelBuilder.definitions[className] = new ModelDefinition(dataSource.modelBuilder, md.name, md.properties, md.settings); + if ((!dataSource.isTransaction) && dataSource.connector && dataSource.connector.define) { + dataSource.connector.define({ + model: Slave, + properties: md.properties, + settings: md.settings + }); } - return Slave; + } + + return Slave; }; /** @@ -1526,36 +1516,36 @@ DataSource.prototype.copyModel = function copyModel(Master) { * @returns {EventEmitter} * @private */ -DataSource.prototype.transaction = function() { - var dataSource = this; - var transaction = new EventEmitter(); +DataSource.prototype.transaction = function () { + var dataSource = this; + var transaction = new EventEmitter(); - for (var p in dataSource) { - transaction[p] = dataSource[p]; - } + for (var p in dataSource) { + transaction[p] = dataSource[p]; + } - transaction.isTransaction = true; - transaction.origin = dataSource; - transaction.name = dataSource.name; - transaction.settings = dataSource.settings; - transaction.connected = false; - transaction.connecting = false; - transaction.connector = dataSource.connector.transaction(); + transaction.isTransaction = true; + transaction.origin = dataSource; + transaction.name = dataSource.name; + transaction.settings = dataSource.settings; + transaction.connected = false; + transaction.connecting = false; + transaction.connector = dataSource.connector.transaction(); - // create blank models pool - transaction.modelBuilder = new ModelBuilder(); - transaction.models = transaction.modelBuilder.models; - transaction.definitions = transaction.modelBuilder.definitions; + // create blank models pool + transaction.modelBuilder = new ModelBuilder(); + transaction.models = transaction.modelBuilder.models; + transaction.definitions = transaction.modelBuilder.definitions; - for (var i in dataSource.modelBuilder.models) { - dataSource.copyModel.call(transaction, dataSource.modelBuilder.models[i]); - } + for (var i in dataSource.modelBuilder.models) { + dataSource.copyModel.call(transaction, dataSource.modelBuilder.models[i]); + } - transaction.exec = function(cb) { - transaction.connector.exec(cb); - }; + transaction.exec = function (cb) { + transaction.connector.exec(cb); + }; - return transaction; + return transaction; }; /** @@ -1565,7 +1555,7 @@ DataSource.prototype.transaction = function() { DataSource.prototype.enableRemote = function (operation) { var op = this.getOperation(operation); - if(op) { + if (op) { op.remoteEnabled = true; } else { throw new Error(operation + ' is not provided by the attached connector'); @@ -1579,7 +1569,7 @@ DataSource.prototype.enableRemote = function (operation) { DataSource.prototype.disableRemote = function (operation) { var op = this.getOperation(operation); - if(op) { + if (op) { op.remoteEnabled = false; } else { throw new Error(operation + ' is not provided by the attached connector'); @@ -1594,11 +1584,11 @@ DataSource.prototype.disableRemote = function (operation) { DataSource.prototype.getOperation = function (operation) { var ops = this.operations(); var opKeys = Object.keys(ops); - - for(var i = 0; i < opKeys.length; i++) { + + for (var i = 0; i < opKeys.length; i++) { var op = ops[opKeys[i]]; - - if(op.name === operation) { + + if (op.name === operation) { return op; } } @@ -1627,8 +1617,8 @@ DataSource.prototype.defineOperation = function (name, options, fn) { * Check if the backend is a relational DB * @returns {Boolean} */ -DataSource.prototype.isRelational = function() { - return this.connector && this.connector.relational; +DataSource.prototype.isRelational = function () { + return this.connector && this.connector.relational; }; /** @@ -1637,40 +1627,39 @@ DataSource.prototype.isRelational = function() { * @param args * @returns {boolean} */ -DataSource.prototype.ready = function(obj, args) { - var self = this; - if (this.connected) { - // Connected - return false; - } +DataSource.prototype.ready = function (obj, args) { + var self = this; + if (this.connected) { + // Connected + return false; + } - var method = args.callee; - // Set up a callback after the connection is established to continue the method call + var method = args.callee; + // Set up a callback after the connection is established to continue the method call - var onConnected = null, onError = null; - onConnected = function () { - // Remove the error handler - self.removeListener('error', onError); - method.apply(obj, [].slice.call(args)); - }; - onError = function (err) { - // Remove the connected listener - self.removeListener('connected', onConnected); - var params = [].slice.call(args); - var cb = params.pop(); - if(typeof cb === 'function') { - cb(err); - } - }; - this.once('connected', onConnected); - this.once('error', onError); - if (!this.connecting) { - this.connect(); + var onConnected = null, onError = null; + onConnected = function () { + // Remove the error handler + self.removeListener('error', onError); + method.apply(obj, [].slice.call(args)); + }; + onError = function (err) { + // Remove the connected listener + self.removeListener('connected', onConnected); + var params = [].slice.call(args); + var cb = params.pop(); + if (typeof cb === 'function') { + cb(err); } - return true; + }; + this.once('connected', onConnected); + this.once('error', onError); + if (!this.connecting) { + this.connect(); + } + return true; }; - /** * Define a hidden property * @param {Object} obj The property owner @@ -1678,12 +1667,12 @@ DataSource.prototype.ready = function(obj, args) { * @param {Mixed} value The default value */ function hiddenProperty(obj, key, value) { - Object.defineProperty(obj, key, { - writable: false, - enumerable: false, - configurable: false, - value: value - }); + Object.defineProperty(obj, key, { + writable: false, + enumerable: false, + configurable: false, + value: value + }); } /** @@ -1694,15 +1683,14 @@ function hiddenProperty(obj, key, value) { * @param {Mixed} value The default value */ function defineReadonlyProp(obj, key, value) { - Object.defineProperty(obj, key, { - writable: false, - enumerable: true, - configurable: true, - value: value - }); + Object.defineProperty(obj, key, { + writable: false, + enumerable: true, + configurable: true, + value: value + }); } - // Carry over a few properties/methods from the ModelBuilder as some tests use them DataSource.Text = ModelBuilder.Text; DataSource.JSON = ModelBuilder.JSON; @@ -1712,7 +1700,7 @@ DataSource.Any = ModelBuilder.Any; * @deprecated Use ModelBuilder.registerType instead * @param type */ -DataSource.registerType = function(type) { +DataSource.registerType = function (type) { ModelBuilder.registerType(type); }; diff --git a/lib/geo.js b/lib/geo.js index 580308a7..84f7656f 100644 --- a/lib/geo.js +++ b/lib/geo.js @@ -10,12 +10,12 @@ var assert = require('assert'); exports.nearFilter = function nearFilter(where) { var result = false; - - if(where && typeof where === 'object') { + + if (where && typeof where === 'object') { Object.keys(where).forEach(function (key) { var ex = where[key]; - - if(ex && ex.near) { + + if (ex && ex.near) { result = { near: ex.near, maxDistance: ex.maxDistance, @@ -24,7 +24,7 @@ exports.nearFilter = function nearFilter(where) { } }); } - + return result; } @@ -36,43 +36,43 @@ exports.filter = function (arr, filter) { var origin = filter.near; var max = filter.maxDistance > 0 ? filter.maxDistance : false; var key = filter.key; - + // create distance index var distances = {}; var result = []; - + arr.forEach(function (obj) { var loc = obj[key]; - + // filter out objects without locations - if(!loc) return; - - if(!(loc instanceof GeoPoint)) { + if (!loc) return; + + if (!(loc instanceof GeoPoint)) { loc = GeoPoint(loc); } - - if(typeof loc.lat !== 'number') return; - if(typeof loc.lng !== 'number') return; - + + if (typeof loc.lat !== 'number') return; + if (typeof loc.lng !== 'number') return; + var d = GeoPoint.distanceBetween(origin, loc); - - if(max && d > max) { + + if (max && d > max) { // dont add } else { distances[obj.id] = d; result.push(obj); } }); - + return result.sort(function (objA, objB) { var a = objB[key]; var b = objB[key]; - - if(a && b) { + + if (a && b) { var da = distances[objA.id]; var db = distances[objB.id]; - - if(db === da) return 0; + + if (db === da) return 0; return da > db ? 1 : -1; } else { return 0; @@ -87,15 +87,15 @@ exports.filter = function (arr, filter) { exports.GeoPoint = GeoPoint; function GeoPoint(data) { - if(!(this instanceof GeoPoint)) { + if (!(this instanceof GeoPoint)) { return new GeoPoint(data); } - - if(typeof data === 'string') { + + if (typeof data === 'string') { data = data.split(/,\s*/); assert(data.length === 2, 'must provide a string "lng,lat" creating a GeoPoint with a string'); } - if(Array.isArray(data)) { + if (Array.isArray(data)) { data = { lng: Number(data[0]), lat: Number(data[1]) @@ -104,7 +104,7 @@ function GeoPoint(data) { data.lng = Number(data.lng); data.lat = Number(data.lat); } - + assert(typeof data === 'object', 'must provide a lat and lng object when creating a GeoPoint'); assert(typeof data.lat === 'number' && !isNaN(data.lat), 'lat must be a number when creating a GeoPoint'); assert(typeof data.lng === 'number' && !isNaN(data.lng), 'lng must be a number when creating a GeoPoint'); @@ -112,7 +112,7 @@ function GeoPoint(data) { assert(data.lng >= -180, 'lng must be >= -180'); assert(data.lat <= 90, 'lat must be <= 90'); assert(data.lat >= -90, 'lat must be >= -90'); - + this.lat = data.lat; this.lng = data.lng; } @@ -122,19 +122,19 @@ function GeoPoint(data) { */ GeoPoint.distanceBetween = function distanceBetween(a, b, options) { - if(!(a instanceof GeoPoint)) { + if (!(a instanceof GeoPoint)) { a = GeoPoint(a); } - if(!(b instanceof GeoPoint)) { + if (!(b instanceof GeoPoint)) { b = GeoPoint(b); } - + var x1 = a.lat; var y1 = a.lng; - + var x2 = b.lat; var y2 = b.lng; - + return geoDistance(x1, y1, x2, y2, options); } @@ -162,7 +162,7 @@ GeoPoint.prototype.toString = function () { var PI = 3.1415926535897932384626433832795; // factor to convert decimal degrees to radians -var DEG2RAD = 0.01745329252; +var DEG2RAD = 0.01745329252; // factor to convert decimal degrees to radians var RAD2DEG = 57.29577951308; @@ -184,12 +184,12 @@ function geoDistance(x1, y1, x2, y2, options) { x2 = x2 * DEG2RAD; y2 = y2 * DEG2RAD; - var a = Math.pow(Math.sin(( y2-y1 ) / 2.0 ), 2); - var b = Math.pow(Math.sin(( x2-x1 ) / 2.0 ), 2); - var c = Math.sqrt( a + Math.cos( y2 ) * Math.cos( y1 ) * b ); + var a = Math.pow(Math.sin(( y2 - y1 ) / 2.0), 2); + var b = Math.pow(Math.sin(( x2 - x1 ) / 2.0), 2); + var c = Math.sqrt(a + Math.cos(y2) * Math.cos(y1) * b); var type = (options && options.type) || 'miles'; - return 2 * Math.asin( c ) * EARTH_RADIUS[type]; + return 2 * Math.asin(c) * EARTH_RADIUS[type]; } diff --git a/lib/hooks.js b/lib/hooks.js index cebc190b..815d1aa2 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -27,39 +27,41 @@ Hookable.afterDestroy = null; // TODO: Evaluate https://github.com/bnoguchi/hooks-js/ Hookable.prototype.trigger = function trigger(actionName, work, data) { - var capitalizedName = capitalize(actionName); - var beforeHook = this.constructor["before" + capitalizedName] || this.constructor["pre" + capitalizedName]; - var afterHook = this.constructor["after" + capitalizedName] || this.constructor["post" + capitalizedName]; - if (actionName === 'validate') { - beforeHook = beforeHook || this.constructor.beforeValidation; - afterHook = afterHook || this.constructor.afterValidation; - } - var inst = this; + var capitalizedName = capitalize(actionName); + var beforeHook = this.constructor["before" + capitalizedName] + || this.constructor["pre" + capitalizedName]; + var afterHook = this.constructor["after" + capitalizedName] + || this.constructor["post" + capitalizedName]; + if (actionName === 'validate') { + beforeHook = beforeHook || this.constructor.beforeValidation; + afterHook = afterHook || this.constructor.afterValidation; + } + var inst = this; - // we only call "before" hook when we have actual action (work) to perform - if (work) { - if (beforeHook) { - // before hook should be called on instance with one param: callback - beforeHook.call(inst, function () { - // actual action also have one param: callback - work.call(inst, next); - }, data); - } else { - work.call(inst, next); - } + // we only call "before" hook when we have actual action (work) to perform + if (work) { + if (beforeHook) { + // before hook should be called on instance with one param: callback + beforeHook.call(inst, function () { + // actual action also have one param: callback + work.call(inst, next); + }, data); } else { - next(); + work.call(inst, next); } + } else { + next(); + } - function next(done) { - if (afterHook) { - afterHook.call(inst, done); - } else if (done) { - done.call(this); - } + function next(done) { + if (afterHook) { + afterHook.call(inst, done); + } else if (done) { + done.call(this); } + } }; function capitalize(string) { - return string.charAt(0).toUpperCase() + string.slice(1); + return string.charAt(0).toUpperCase() + string.slice(1); } diff --git a/lib/include.js b/lib/include.js index 57f8ee9f..0bd2d557 100644 --- a/lib/include.js +++ b/lib/include.js @@ -26,135 +26,137 @@ function Inclusion() { * */ Inclusion.include = function (objects, include, cb) { - var self = this; + var self = this; - if ( - (include.constructor.name == 'Array' && include.length == 0) || - (include.constructor.name == 'Object' && Object.keys(include).length == 0) - ) { - cb(null, objects); - return; + if ( + (include.constructor.name == 'Array' && include.length == 0) || + (include.constructor.name == 'Object' && Object.keys(include).length == 0) + ) { + cb(null, objects); + return; + } + + include = processIncludeJoin(include); + + var keyVals = {}; + var objsByKeys = {}; + + var nbCallbacks = 0; + for (var i = 0; i < include.length; i++) { + var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys); + if (callback !== null) { + nbCallbacks++; + callback(function () { + nbCallbacks--; + if (nbCallbacks == 0) { + cb(null, objects); + } + }); + } else { + cb(null, objects); + } + } + + function processIncludeJoin(ij) { + if (typeof ij === 'string') { + ij = [ij]; + } + if (ij.constructor.name === 'Object') { + var newIj = []; + for (var key in ij) { + var obj = {}; + obj[key] = ij[key]; + newIj.push(obj); + } + return newIj; + } + return ij; + } + + function processIncludeItem(objs, include, keyVals, objsByKeys) { + var relations = self.relations; + + if (include.constructor.name === 'Object') { + var relationName = Object.keys(include)[0]; + var subInclude = include[relationName]; + } else { + var relationName = include; + var subInclude = []; + } + var relation = relations[relationName]; + + if (!relation) { + return function () { + cb(new Error('Relation "' + relationName + '" is not defined for ' + + self.modelName + ' model')); + } } - include = processIncludeJoin(include); + var req = {'where': {}}; - var keyVals = {}; - var objsByKeys = {}; + if (!keyVals[relation.keyFrom]) { + objsByKeys[relation.keyFrom] = {}; + objs.filter(Boolean).forEach(function (obj) { + if (!objsByKeys[relation.keyFrom][obj[relation.keyFrom]]) { + objsByKeys[relation.keyFrom][obj[relation.keyFrom]] = []; + } + objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj); + }); + keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]); + } - var nbCallbacks = 0; - for (var i = 0; i < include.length; i++) { - var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys); - if (callback !== null) { - nbCallbacks++; - callback(function() { - nbCallbacks--; - if (nbCallbacks == 0) { - cb(null, objects); + if (keyVals[relation.keyFrom].length > 0) { + // deep clone is necessary since inq seems to change the processed array + var keysToBeProcessed = {}; + var inValues = []; + for (var j = 0; j < keyVals[relation.keyFrom].length; j++) { + keysToBeProcessed[keyVals[relation.keyFrom][j]] = true; + if (keyVals[relation.keyFrom][j] !== 'null' + && keyVals[relation.keyFrom][j] !== 'undefined') { + inValues.push(keyVals[relation.keyFrom][j]); + } + } + + req['where'][relation.keyTo] = {inq: inValues}; + req['include'] = subInclude; + + return function (cb) { + relation.modelTo.find(req, function (err, objsIncluded) { + for (var i = 0; i < objsIncluded.length; i++) { + delete keysToBeProcessed[objsIncluded[i][relation.keyTo]]; + var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]]; + for (var j = 0; j < objectsFrom.length; j++) { + if (!objectsFrom[j].__cachedRelations) { + objectsFrom[j].__cachedRelations = {}; + } + if (relation.multiple) { + if (!objectsFrom[j].__cachedRelations[relationName]) { + objectsFrom[j].__cachedRelations[relationName] = []; } - }); - } else { - cb(null, objects); - } + objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]); + } else { + objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i]; + } + } + } + + // No relation have been found for these keys + for (var key in keysToBeProcessed) { + var objectsFrom = objsByKeys[relation.keyFrom][key]; + for (var j = 0; j < objectsFrom.length; j++) { + if (!objectsFrom[j].__cachedRelations) { + objectsFrom[j].__cachedRelations = {}; + } + objectsFrom[j].__cachedRelations[relationName] = + relation.multiple ? [] : null; + } + } + cb(err, objsIncluded); + }); + }; } - function processIncludeJoin(ij) { - if (typeof ij === 'string') { - ij = [ij]; - } - if (ij.constructor.name === 'Object') { - var newIj = []; - for (var key in ij) { - var obj = {}; - obj[key] = ij[key]; - newIj.push(obj); - } - return newIj; - } - return ij; - } - - function processIncludeItem(objs, include, keyVals, objsByKeys) { - var relations = self.relations; - - if (include.constructor.name === 'Object') { - var relationName = Object.keys(include)[0]; - var subInclude = include[relationName]; - } else { - var relationName = include; - var subInclude = []; - } - var relation = relations[relationName]; - - if (!relation) { - return function() { - cb(new Error('Relation "' + relationName + '" is not defined for ' + self.modelName + ' model')); - } - } - - var req = {'where': {}}; - - if (!keyVals[relation.keyFrom]) { - objsByKeys[relation.keyFrom] = {}; - objs.filter(Boolean).forEach(function(obj) { - if (!objsByKeys[relation.keyFrom][obj[relation.keyFrom]]) { - objsByKeys[relation.keyFrom][obj[relation.keyFrom]] = []; - } - objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj); - }); - keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]); - } - - if (keyVals[relation.keyFrom].length > 0) { - // deep clone is necessary since inq seems to change the processed array - var keysToBeProcessed = {}; - var inValues = []; - for (var j = 0; j < keyVals[relation.keyFrom].length; j++) { - keysToBeProcessed[keyVals[relation.keyFrom][j]] = true; - if (keyVals[relation.keyFrom][j] !== 'null' && keyVals[relation.keyFrom][j] !== 'undefined') { - inValues.push(keyVals[relation.keyFrom][j]); - } - } - - req['where'][relation.keyTo] = {inq: inValues}; - req['include'] = subInclude; - - return function(cb) { - relation.modelTo.find(req, function(err, objsIncluded) { - for (var i = 0; i < objsIncluded.length; i++) { - delete keysToBeProcessed[objsIncluded[i][relation.keyTo]]; - var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]]; - for (var j = 0; j < objectsFrom.length; j++) { - if (!objectsFrom[j].__cachedRelations) { - objectsFrom[j].__cachedRelations = {}; - } - if (relation.multiple) { - if (!objectsFrom[j].__cachedRelations[relationName]) { - objectsFrom[j].__cachedRelations[relationName] = []; - } - objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]); - } else { - objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i]; - } - } - } - - // No relation have been found for these keys - for (var key in keysToBeProcessed) { - var objectsFrom = objsByKeys[relation.keyFrom][key]; - for (var j = 0; j < objectsFrom.length; j++) { - if (!objectsFrom[j].__cachedRelations) { - objectsFrom[j].__cachedRelations = {}; - } - objectsFrom[j].__cachedRelations[relationName] = relation.multiple ? [] : null; - } - } - cb(err, objsIncluded); - }); - }; - } - - - return null; - } + return null; + } } diff --git a/lib/introspection.js b/lib/introspection.js index 5dcca91c..aa2d5e4a 100644 --- a/lib/introspection.js +++ b/lib/introspection.js @@ -1,60 +1,60 @@ module.exports = function getIntrospector(ModelBuilder) { - function introspectType(value) { + function introspectType(value) { - // Unknown type, using Any - if (value === null || value === undefined) { - return ModelBuilder.Any; - } - - // Check registered schemaTypes - for (var t in ModelBuilder.schemaTypes) { - var st = ModelBuilder.schemaTypes[t]; - if (st !== Object && st !== Array && (value instanceof st)) { - return t; - } - } - - var type = typeof value; - if (type === 'string' || type === 'number' || type === 'boolean') { - return type; - } - - if (value instanceof Date) { - return 'date'; - } - - if (Array.isArray(value)) { - for (var i = 0; i < value.length; i++) { - if (value[i] === null || value[i] === undefined) { - continue; - } - var itemType = introspectType(value[i]); - if (itemType) { - return [itemType]; - } - } - return 'array'; - } - - if (type === 'function') { - return value.constructor.name; - } - - var properties = {}; - for (var p in value) { - var itemType = introspectType(value[p]); - if (itemType) { - properties[p] = itemType; - } - } - if (Object.keys(properties).length === 0) { - return 'object'; - } - return properties; + // Unknown type, using Any + if (value === null || value === undefined) { + return ModelBuilder.Any; } - return introspectType; + // Check registered schemaTypes + for (var t in ModelBuilder.schemaTypes) { + var st = ModelBuilder.schemaTypes[t]; + if (st !== Object && st !== Array && (value instanceof st)) { + return t; + } + } + + var type = typeof value; + if (type === 'string' || type === 'number' || type === 'boolean') { + return type; + } + + if (value instanceof Date) { + return 'date'; + } + + if (Array.isArray(value)) { + for (var i = 0; i < value.length; i++) { + if (value[i] === null || value[i] === undefined) { + continue; + } + var itemType = introspectType(value[i]); + if (itemType) { + return [itemType]; + } + } + return 'array'; + } + + if (type === 'function') { + return value.constructor.name; + } + + var properties = {}; + for (var p in value) { + var itemType = introspectType(value[p]); + if (itemType) { + properties[p] = itemType; + } + } + if (Object.keys(properties).length === 0) { + return 'object'; + } + return properties; + } + + return introspectType; } diff --git a/lib/jutil.js b/lib/jutil.js index 33d81ea7..03e21500 100644 --- a/lib/jutil.js +++ b/lib/jutil.js @@ -5,24 +5,24 @@ var util = require('util'); * @param baseClass */ exports.inherits = function (newClass, baseClass, options) { - util.inherits(newClass, baseClass); + util.inherits(newClass, baseClass); - options = options || { - staticProperties: true, - override: false - }; + options = options || { + staticProperties: true, + override: false + }; - if (options.staticProperties) { - Object.keys(baseClass).forEach(function (classProp) { - if (classProp !== 'super_' && (!newClass.hasOwnProperty(classProp) || options.override)) { - var pd = Object.getOwnPropertyDescriptor(baseClass, classProp); - Object.defineProperty(newClass, classProp, pd); - } - }); - } + if (options.staticProperties) { + Object.keys(baseClass).forEach(function (classProp) { + if (classProp !== 'super_' && (!newClass.hasOwnProperty(classProp) + || options.override)) { + var pd = Object.getOwnPropertyDescriptor(baseClass, classProp); + Object.defineProperty(newClass, classProp, pd); + } + }); + } }; - /** * Mix in the a class into the new class * @param newClass The target class to receive the mixin @@ -30,76 +30,78 @@ exports.inherits = function (newClass, baseClass, options) { * @param options */ exports.mixin = function (newClass, mixinClass, options) { - if (Array.isArray(newClass._mixins)) { - if(newClass._mixins.indexOf(mixinClass) !== -1) { - return; + if (Array.isArray(newClass._mixins)) { + if (newClass._mixins.indexOf(mixinClass) !== -1) { + return; + } + newClass._mixins.push(mixinClass); + } else { + newClass._mixins = [mixinClass]; + } + + options = options || { + staticProperties: true, + instanceProperties: true, + override: false, + proxyFunctions: false + }; + + if (options.staticProperties === undefined) { + options.staticProperties = true; + } + + if (options.instanceProperties === undefined) { + options.instanceProperties = true; + } + + if (options.staticProperties) { + var staticProxies = []; + Object.keys(mixinClass).forEach(function (classProp) { + if (classProp !== 'super_' && classProp !== '_mixins' + && (!newClass.hasOwnProperty(classProp) || options.override)) { + var pd = Object.getOwnPropertyDescriptor(mixinClass, classProp); + if (options.proxyFunctions && pd.writable + && typeof pd.value === 'function') { + pd.value = exports.proxy(pd.value, staticProxies); } - newClass._mixins.push(mixinClass); - } else { - newClass._mixins = [mixinClass]; - } + Object.defineProperty(newClass, classProp, pd); + } + }); + } - options = options || { - staticProperties: true, - instanceProperties: true, - override: false, - proxyFunctions: false - }; - - if(options.staticProperties === undefined) { - options.staticProperties = true; - } - - if(options.instanceProperties === undefined) { - options.instanceProperties = true; - } - - if (options.staticProperties) { - var staticProxies = []; - Object.keys(mixinClass).forEach(function (classProp) { - if (classProp !== 'super_' && classProp !== '_mixins' && (!newClass.hasOwnProperty(classProp) || options.override)) { - var pd = Object.getOwnPropertyDescriptor(mixinClass, classProp); - if(options.proxyFunctions && pd.writable && typeof pd.value === 'function') { - pd.value = exports.proxy(pd.value, staticProxies); - } - Object.defineProperty(newClass, classProp, pd); - } - }); - } - - if (options.instanceProperties) { - if (mixinClass.prototype) { - var instanceProxies = []; - Object.keys(mixinClass.prototype).forEach(function (instanceProp) { - if (!newClass.prototype.hasOwnProperty(instanceProp) || options.override) { - var pd = Object.getOwnPropertyDescriptor(mixinClass.prototype, instanceProp); - if(options.proxyFunctions && pd.writable && typeof pd.value === 'function') { - pd.value = exports.proxy(pd.value, instanceProxies); - } - Object.defineProperty(newClass.prototype, instanceProp, pd); - } - }); + if (options.instanceProperties) { + if (mixinClass.prototype) { + var instanceProxies = []; + Object.keys(mixinClass.prototype).forEach(function (instanceProp) { + if (!newClass.prototype.hasOwnProperty(instanceProp) || options.override) { + var pd = Object.getOwnPropertyDescriptor(mixinClass.prototype, instanceProp); + if (options.proxyFunctions && pd.writable && typeof pd.value === 'function') { + pd.value = exports.proxy(pd.value, instanceProxies); + } + Object.defineProperty(newClass.prototype, instanceProp, pd); } + }); } + } - return newClass; + return newClass; }; -exports.proxy = function(fn, proxies) { +exports.proxy = function (fn, proxies) { // Make sure same methods referenced by different properties have the same proxy // For example, deleteById is an alias of removeById proxies = proxies || []; - for(var i = 0; i 0) { + // id already exists + idInjection = false; + } - // Return the unresolved model - if(settings.unresolved) { - ModelClass.settings = {unresolved: true}; - return ModelClass; - } + // Add the id property + if (idInjection) { + // Set up the id property + ModelClass.definition.defineProperty('id', { type: Number, id: 1, generated: true }); + } - // Add metadata to the ModelClass - hiddenProperty(ModelClass, 'modelBuilder', modelBuilder); - hiddenProperty(ModelClass, 'dataSource', modelBuilder); // Keep for back-compatibility - hiddenProperty(ModelClass, 'pluralModelName', pluralName); - hiddenProperty(ModelClass, 'relations', {}); - hiddenProperty(ModelClass, 'http', { path: '/' + pluralName }); - - // inherit ModelBaseClass static methods - for (var i in ModelBaseClass) { - // We need to skip properties that are already in the subclass, for example, the event emitter methods - if(i !== '_mixins' && !(i in ModelClass)) { - ModelClass[i] = ModelBaseClass[i]; - } - } - - // Load and inject the model classes - if(settings.models) { - Object.keys(settings.models).forEach(function(m) { - var model = settings.models[m]; - ModelClass[m] = typeof model === 'string' ? modelBuilder.getModel(model, true) : model; + idNames = modelDefinition.idNames(); // Reload it after rebuild + // Create a virtual property 'id' + if (idNames.length === 1) { + var idProp = idNames[0]; + if (idProp !== 'id') { + Object.defineProperty(ModelClass.prototype, 'id', { + get: function () { + var idProp = ModelClass.definition.idNames[0]; + return this.__data[idProp]; + }, + configurable: true, + enumerable: false }); } - - ModelClass.getter = {}; - ModelClass.setter = {}; - - var modelDefinition = new ModelDefinition(this, className, properties, settings); - - this.definitions[className] = modelDefinition; - - // expose properties on the ModelClass - ModelClass.definition = modelDefinition; - // keep a pointer to settings as models can use it for configuration - ModelClass.settings = modelDefinition.settings; - - var idInjection = settings.idInjection; - if(idInjection !== false) { - // Default to true if undefined - idInjection = true; - } - - var idNames = modelDefinition.idNames(); - if(idNames.length > 0) { - // id already exists - idInjection = false; - } - - // Add the id property - if (idInjection) { - // Set up the id property - ModelClass.definition.defineProperty('id', { type: Number, id: 1, generated: true }); - } - - idNames = modelDefinition.idNames(); // Reload it after rebuild - // Create a virtual property 'id' - if (idNames.length === 1) { - var idProp = idNames[0]; - if (idProp !== 'id') { - Object.defineProperty(ModelClass.prototype, 'id', { - get: function () { - var idProp = ModelClass.definition.idNames[0]; - return this.__data[idProp]; - }, - configurable: true, - enumerable: false - }); + } else { + // Now the id property is an object that consists of multiple keys + Object.defineProperty(ModelClass.prototype, 'id', { + get: function () { + var compositeId = {}; + var idNames = ModelClass.definition.idNames(); + for (var p in idNames) { + compositeId[p] = this.__data[p]; } - } else { - // Now the id property is an object that consists of multiple keys - Object.defineProperty(ModelClass.prototype, 'id', { - get: function () { - var compositeId = {}; - var idNames = ModelClass.definition.idNames(); - for (var p in idNames) { - compositeId[p] = this.__data[p]; - } - return compositeId; - }, - configurable: true, - enumerable: false - }); + return compositeId; + }, + configurable: true, + enumerable: false + }); + } + + // A function to loop through the properties + ModelClass.forEachProperty = function (cb) { + Object.keys(ModelClass.definition.properties).forEach(cb); + }; + + // A function to attach the model class to a data source + ModelClass.attachTo = function (dataSource) { + dataSource.attach(this); + }; + + // A function to extend the model + ModelClass.extend = function (className, subclassProperties, subclassSettings) { + var properties = ModelClass.definition.properties; + var settings = ModelClass.definition.settings; + + subclassProperties = subclassProperties || {}; + subclassSettings = subclassSettings || {}; + + // Check if subclass redefines the ids + var idFound = false; + for (var k in subclassProperties) { + if (subclassProperties[k].id) { + idFound = true; + break; + } } - // A function to loop through the properties - ModelClass.forEachProperty = function (cb) { - Object.keys(ModelClass.definition.properties).forEach(cb); - }; + // Merging the properties + Object.keys(properties).forEach(function (key) { + if (idFound && properties[key].id) { + // don't inherit id properties + return; + } + if (subclassProperties[key] === undefined) { + subclassProperties[key] = properties[key]; + } + }); - // A function to attach the model class to a data source - ModelClass.attachTo = function (dataSource) { - dataSource.attach(this); - }; + // Merge the settings + subclassSettings = mergeSettings(settings, subclassSettings); - // A function to extend the model - ModelClass.extend = function (className, subclassProperties, subclassSettings) { - var properties = ModelClass.definition.properties; - var settings = ModelClass.definition.settings; - - subclassProperties = subclassProperties || {}; - subclassSettings = subclassSettings || {}; - - // Check if subclass redefines the ids - var idFound = false; - for(var k in subclassProperties) { - if(subclassProperties[k].id) { - idFound = true; - break; - } - } - - // Merging the properties - Object.keys(properties).forEach(function (key) { - if(idFound && properties[key].id) { - // don't inherit id properties - return; - } - if(subclassProperties[key] === undefined) { - subclassProperties[key] = properties[key]; - } - }); - - // Merge the settings - subclassSettings = mergeSettings(settings, subclassSettings); - - /* - Object.keys(settings).forEach(function (key) { - if(subclassSettings[key] === undefined) { - subclassSettings[key] = settings[key]; - } - }); - */ - - // Define the subclass - var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); - - // Calling the setup function - if(typeof subClass.setup === 'function') { - subClass.setup.call(subClass); - } - - return subClass; - }; - - /** - * Register a property for the model class - * @param propertyName + /* + Object.keys(settings).forEach(function (key) { + if(subclassSettings[key] === undefined) { + subclassSettings[key] = settings[key]; + } + }); */ - ModelClass.registerProperty = function (propertyName) { - var properties = modelDefinition.build(); - var prop = properties[propertyName]; - var DataType = prop.type; - if(!DataType) { - throw new Error('Invalid type for property ' + propertyName); + + // Define the subclass + var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); + + // Calling the setup function + if (typeof subClass.setup === 'function') { + subClass.setup.call(subClass); + } + + return subClass; + }; + + /** + * Register a property for the model class + * @param propertyName + */ + ModelClass.registerProperty = function (propertyName) { + var properties = modelDefinition.build(); + var prop = properties[propertyName]; + var DataType = prop.type; + if (!DataType) { + throw new Error('Invalid type for property ' + propertyName); + } + if (Array.isArray(DataType) || DataType === Array) { + DataType = List; + } else if (DataType.name === 'Date') { + var OrigDate = Date; + DataType = function Date(arg) { + return new OrigDate(arg); + }; + } else if (typeof DataType === 'string') { + DataType = modelBuilder.resolveType(DataType); + } + + if (prop.required) { + var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; + ModelClass.validatesPresenceOf(propertyName, requiredOptions); + } + + Object.defineProperty(ModelClass.prototype, propertyName, { + get: function () { + if (ModelClass.getter[propertyName]) { + return ModelClass.getter[propertyName].call(this); // Try getter first + } else { + return this.__data && this.__data[propertyName]; // Try __data } - if (Array.isArray(DataType) || DataType === Array) { - DataType = List; - } else if (DataType.name === 'Date') { - var OrigDate = Date; - DataType = function Date(arg) { - return new OrigDate(arg); - }; - } else if(typeof DataType === 'string') { - DataType = modelBuilder.resolveType(DataType); + }, + set: function (value) { + if (ModelClass.setter[propertyName]) { + ModelClass.setter[propertyName].call(this, value); // Try setter first + } else { + if (!this.__data) { + this.__data = {}; + } + if (value === null || value === undefined) { + this.__data[propertyName] = value; + } else { + if (DataType === List) { + this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); + } else { + // Assume the type constructor handles Constructor() call + // If not, we should call new DataType(value).valueOf(); + this.__data[propertyName] = DataType(value); + } + } } - - if(prop.required) { - var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; - ModelClass.validatesPresenceOf(propertyName, requiredOptions); + }, + configurable: true, + enumerable: true + }); + + // $was --> __dataWas. + Object.defineProperty(ModelClass.prototype, propertyName + '$was', { + get: function () { + return this.__dataWas && this.__dataWas[propertyName]; + }, + configurable: true, + enumerable: false + }); + + // FIXME: [rfeng] Do we need to keep the raw data? + // Use $ as the prefix to avoid conflicts with properties such as _id + Object.defineProperty(ModelClass.prototype, '$' + propertyName, { + get: function () { + return this.__data && this.__data[propertyName]; + }, + set: function (value) { + if (!this.__data) { + this.__data = {}; } + this.__data[propertyName] = value; + }, + configurable: true, + enumerable: false + }); + }; - Object.defineProperty(ModelClass.prototype, propertyName, { - get: function () { - if (ModelClass.getter[propertyName]) { - return ModelClass.getter[propertyName].call(this); // Try getter first - } else { - return this.__data && this.__data[propertyName]; // Try __data - } - }, - set: function (value) { - if (ModelClass.setter[propertyName]) { - ModelClass.setter[propertyName].call(this, value); // Try setter first - } else { - if (!this.__data) { - this.__data = {}; - } - if (value === null || value === undefined) { - this.__data[propertyName] = value; - } else { - if(DataType === List) { - this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); - } else { - // Assume the type constructor handles Constructor() call - // If not, we should call new DataType(value).valueOf(); - this.__data[propertyName] = DataType(value); - } - } - } - }, - configurable: true, - enumerable: true - }); + ModelClass.forEachProperty(ModelClass.registerProperty); - // $was --> __dataWas. - Object.defineProperty(ModelClass.prototype, propertyName + '$was', { - get: function () { - return this.__dataWas && this.__dataWas[propertyName]; - }, - configurable: true, - enumerable: false - }); + ModelClass.emit('defined', ModelClass); - // FIXME: [rfeng] Do we need to keep the raw data? - // Use $ as the prefix to avoid conflicts with properties such as _id - Object.defineProperty(ModelClass.prototype, '$' + propertyName, { - get: function () { - return this.__data && this.__data[propertyName]; - }, - set: function (value) { - if (!this.__data) { - this.__data = {}; - } - this.__data[propertyName] = value; - }, - configurable: true, - enumerable: false - }); - }; - - ModelClass.forEachProperty(ModelClass.registerProperty); - - ModelClass.emit('defined', ModelClass); - - return ModelClass; + return ModelClass; }; @@ -428,8 +428,8 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett * @param {Object} propertyDefinition - property settings */ ModelBuilder.prototype.defineProperty = function (model, propertyName, propertyDefinition) { - this.definitions[model].defineProperty(propertyName, propertyDefinition); - this.models[model].registerProperty(propertyName); + this.definitions[model].defineProperty(propertyName, propertyDefinition); + this.models[model].registerProperty(propertyName); }; /** @@ -456,69 +456,67 @@ ModelBuilder.prototype.defineProperty = function (model, propertyName, propertyD * }); */ ModelBuilder.prototype.extendModel = function (model, props) { - var t = this; - Object.keys(props).forEach(function (propName) { - var definition = props[propName]; - t.defineProperty(model, propName, definition); - }); + var t = this; + Object.keys(props).forEach(function (propName) { + var definition = props[propName]; + t.defineProperty(model, propName, definition); + }); }; - ModelBuilder.prototype.copyModel = function copyModel(Master) { - var modelBuilder = this; - var className = Master.modelName; - var md = Master.modelBuilder.definitions[className]; - var Slave = function SlaveModel() { - Master.apply(this, [].slice.call(arguments)); + var modelBuilder = this; + var className = Master.modelName; + var md = Master.modelBuilder.definitions[className]; + var Slave = function SlaveModel() { + Master.apply(this, [].slice.call(arguments)); + }; + + util.inherits(Slave, Master); + + Slave.__proto__ = Master; + + hiddenProperty(Slave, 'modelBuilder', modelBuilder); + hiddenProperty(Slave, 'modelName', className); + hiddenProperty(Slave, 'relations', Master.relations); + + if (!(className in modelBuilder.models)) { + + // store class in model pool + modelBuilder.models[className] = Slave; + modelBuilder.definitions[className] = { + properties: md.properties, + settings: md.settings }; + } - util.inherits(Slave, Master); - - Slave.__proto__ = Master; - - hiddenProperty(Slave, 'modelBuilder', modelBuilder); - hiddenProperty(Slave, 'modelName', className); - hiddenProperty(Slave, 'relations', Master.relations); - - if (!(className in modelBuilder.models)) { - - // store class in model pool - modelBuilder.models[className] = Slave; - modelBuilder.definitions[className] = { - properties: md.properties, - settings: md.settings - }; - } - - return Slave; + return Slave; }; - /*! * Define hidden property */ function hiddenProperty(where, property, value) { - Object.defineProperty(where, property, { - writable: true, - enumerable: false, - configurable: true, - value: value - }); + Object.defineProperty(where, property, { + writable: true, + enumerable: false, + configurable: true, + value: value + }); } /** * Get the schema name */ ModelBuilder.prototype.getSchemaName = function (name) { - if (name) { - return name; - } - if (typeof this._nameCount !== 'number') { - this._nameCount = 0; - } else { - this._nameCount++; - } - return 'AnonymousModel_' + this._nameCount; + if (name) { + return name; + } + if (typeof this._nameCount !== 'number') { + this._nameCount = 0; + } else { + this._nameCount++; + } + return 'AnonymousModel_' + this._nameCount; }; /** @@ -526,41 +524,41 @@ ModelBuilder.prototype.getSchemaName = function (name) { * @param {String} type The type string, such as 'number', 'Number', 'boolean', or 'String'. It's case insensitive * @returns {Function} if the type is resolved */ -ModelBuilder.prototype.resolveType = function(type) { - if (!type) { - return type; - } - if (Array.isArray(type) && type.length > 0) { - // For array types, the first item should be the type string - var itemType = this.resolveType(type[0]); - if (typeof itemType === 'function') { - return [itemType]; - } - else { - return itemType; // Not resolved, return the type string - } - } - if (typeof type === 'string') { - var schemaType = ModelBuilder.schemaTypes[type.toLowerCase()] || this.models[type]; - if (schemaType) { - return schemaType; - } else { - // The type cannot be resolved, let's create a place holder - type = this.define(type, {}, {unresolved: true}); - return type; - } - } else if (type.constructor.name === 'Object') { - // We also support the syntax {type: 'string', ...} - if (type.type) { - return this.resolveType(type.type); - } else { - return this.define(this.getSchemaName(null), - type, {anonymous: true, idInjection: false}); - } - } else if('function' === typeof type ) { - return type; - } +ModelBuilder.prototype.resolveType = function (type) { + if (!type) { return type; + } + if (Array.isArray(type) && type.length > 0) { + // For array types, the first item should be the type string + var itemType = this.resolveType(type[0]); + if (typeof itemType === 'function') { + return [itemType]; + } + else { + return itemType; // Not resolved, return the type string + } + } + if (typeof type === 'string') { + var schemaType = ModelBuilder.schemaTypes[type.toLowerCase()] || this.models[type]; + if (schemaType) { + return schemaType; + } else { + // The type cannot be resolved, let's create a place holder + type = this.define(type, {}, {unresolved: true}); + return type; + } + } else if (type.constructor.name === 'Object') { + // We also support the syntax {type: 'string', ...} + if (type.type) { + return this.resolveType(type.type); + } else { + return this.define(this.getSchemaName(null), + type, {anonymous: true, idInjection: false}); + } + } else if ('function' === typeof type) { + return type; + } + return type; }; /** @@ -576,46 +574,46 @@ ModelBuilder.prototype.resolveType = function(type) { * @returns {Object} A map of model constructors keyed by model name */ ModelBuilder.prototype.buildModels = function (schemas) { - var models = {}; + var models = {}; - // Normalize the schemas to be an array of the schema objects {name: , properties: {}, options: {}} - if (!Array.isArray(schemas)) { - if (schemas.properties && schemas.name) { - // Only one item - schemas = [schemas]; - } else { - // Anonymous schema - schemas = [ - { - name: this.getSchemaName(), - properties: schemas, - options: {anonymous: true} - } - ]; + // Normalize the schemas to be an array of the schema objects {name: , properties: {}, options: {}} + if (!Array.isArray(schemas)) { + if (schemas.properties && schemas.name) { + // Only one item + schemas = [schemas]; + } else { + // Anonymous schema + schemas = [ + { + name: this.getSchemaName(), + properties: schemas, + options: {anonymous: true} } + ]; } + } - var relations = []; - for (var s in schemas) { - var name = this.getSchemaName(schemas[s].name); - schemas[s].name = name; - var model = this.define(schemas[s].name, schemas[s].properties, schemas[s].options); - models[name] = model; - relations = relations.concat(model.definition.relations); - } + var relations = []; + for (var s in schemas) { + var name = this.getSchemaName(schemas[s].name); + schemas[s].name = name; + var model = this.define(schemas[s].name, schemas[s].properties, schemas[s].options); + models[name] = model; + relations = relations.concat(model.definition.relations); + } - // Connect the models based on the relations - for (var i = 0; i < relations.length; i++) { - var relation = relations[i]; - var sourceModel = models[relation.source]; - var targetModel = models[relation.target]; - if (sourceModel && targetModel) { - if(typeof sourceModel[relation.type] === 'function') { - sourceModel[relation.type](targetModel, {as: relation.as}); - } - } + // Connect the models based on the relations + for (var i = 0; i < relations.length; i++) { + var relation = relations[i]; + var sourceModel = models[relation.source]; + var targetModel = models[relation.target]; + if (sourceModel && targetModel) { + if (typeof sourceModel[relation.type] === 'function') { + sourceModel[relation.type](targetModel, {as: relation.as}); + } } - return models; + } + return models; }; /** @@ -625,13 +623,13 @@ ModelBuilder.prototype.buildModels = function (schemas) { * @param [Object} options The options * @returns {} */ -ModelBuilder.prototype.buildModelFromInstance = function(name, json, options) { +ModelBuilder.prototype.buildModelFromInstance = function (name, json, options) { - // Introspect the JSON document to generate a schema - var schema = introspect(json); + // Introspect the JSON document to generate a schema + var schema = introspect(json); - // Create a model for the generated schema - return this.define(name, schema, options); + // Create a model for the generated schema + return this.define(name, schema, options); }; diff --git a/lib/model-definition.js b/lib/model-definition.js index c13d1cf1..47ce8eca 100644 --- a/lib/model-definition.js +++ b/lib/model-definition.js @@ -21,27 +21,27 @@ module.exports = ModelDefinition; * */ function ModelDefinition(modelBuilder, name, properties, settings) { - if (!(this instanceof ModelDefinition)) { - // Allow to call ModelDefinition without new - return new ModelDefinition(modelBuilder, name, properties, settings); - } - this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance; - assert(name, 'name is missing'); + if (!(this instanceof ModelDefinition)) { + // Allow to call ModelDefinition without new + return new ModelDefinition(modelBuilder, name, properties, settings); + } + this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance; + assert(name, 'name is missing'); - if (arguments.length === 2 && typeof name === 'object') { - var schema = name; - this.name = schema.name; - this.rawProperties = schema.properties || {}; // Keep the raw property definitions - this.settings = schema.settings || {}; - } else { - assert(typeof name === 'string', 'name must be a string'); - this.name = name; - this.rawProperties = properties || {}; // Keep the raw property definitions - this.settings = settings || {}; - } - this.relations = []; - this.properties = null; - this.build(); + if (arguments.length === 2 && typeof name === 'object') { + var schema = name; + this.name = schema.name; + this.rawProperties = schema.properties || {}; // Keep the raw property definitions + this.settings = schema.settings || {}; + } else { + assert(typeof name === 'string', 'name must be a string'); + this.name = name; + this.rawProperties = properties || {}; // Keep the raw property definitions + this.settings = settings || {}; + } + this.relations = []; + this.properties = null; + this.build(); } util.inherits(ModelDefinition, EventEmitter); @@ -49,18 +49,17 @@ util.inherits(ModelDefinition, EventEmitter); // Set up types require('./types')(ModelDefinition); - /** * Return table name for specified `modelName` * @param {String} connectorType The connector type, such as 'oracle' or 'mongodb' */ ModelDefinition.prototype.tableName = function (connectorType) { - var settings = this.settings; - if(settings[connectorType]) { - return settings[connectorType].table || settings[connectorType].tableName || this.name; - } else { - return this.name; - } + var settings = this.settings; + if (settings[connectorType]) { + return settings[connectorType].table || settings[connectorType].tableName || this.name; + } else { + return this.name; + } }; /** @@ -70,16 +69,16 @@ ModelDefinition.prototype.tableName = function (connectorType) { * @returns {String} columnName */ ModelDefinition.prototype.columnName = function (connectorType, propertyName) { - if(!propertyName) { - return propertyName; - } - this.build(); - var property = this.properties[propertyName]; - if(property && property[connectorType]) { - return property[connectorType].column || property[connectorType].columnName || propertyName; - } else { - return propertyName; - } + if (!propertyName) { + return propertyName; + } + this.build(); + var property = this.properties[propertyName]; + if (property && property[connectorType]) { + return property[connectorType].column || property[connectorType].columnName || propertyName; + } else { + return propertyName; + } }; /** @@ -89,16 +88,16 @@ ModelDefinition.prototype.columnName = function (connectorType, propertyName) { * @returns {Object} column metadata */ ModelDefinition.prototype.columnMetadata = function (connectorType, propertyName) { - if(!propertyName) { - return propertyName; - } - this.build(); - var property = this.properties[propertyName]; - if(property && property[connectorType]) { - return property[connectorType]; - } else { - return null; - } + if (!propertyName) { + return propertyName; + } + this.build(); + var property = this.properties[propertyName]; + if (property && property[connectorType]) { + return property[connectorType]; + } else { + return null; + } }; /** @@ -107,17 +106,17 @@ ModelDefinition.prototype.columnMetadata = function (connectorType, propertyName * @returns {String[]} column names */ ModelDefinition.prototype.columnNames = function (connectorType) { - this.build(); - var props = this.properties; - var cols = []; - for(var p in props) { - if(props[p][connectorType]) { - cols.push(property[connectorType].column || props[p][connectorType].columnName || p); - } else { - cols.push(p); - } + this.build(); + var props = this.properties; + var cols = []; + for (var p in props) { + if (props[p][connectorType]) { + cols.push(property[connectorType].column || props[p][connectorType].columnName || p); + } else { + cols.push(p); } - return cols; + } + return cols; }; /** @@ -125,27 +124,27 @@ ModelDefinition.prototype.columnNames = function (connectorType) { * @returns {Object[]} property name/index for IDs */ ModelDefinition.prototype.ids = function () { - if(this._ids) { - return this._ids; + if (this._ids) { + return this._ids; + } + var ids = []; + this.build(); + var props = this.properties; + for (var key in props) { + var id = props[key].id; + if (!id) { + continue; } - var ids = []; - this.build(); - var props = this.properties; - for (var key in props) { - var id = props[key].id; - if(!id) { - continue; - } - if(typeof id !== 'number') { - id = 1; - } - ids.push({name: key, id: id}); + if (typeof id !== 'number') { + id = 1; } - ids.sort(function (a, b) { - return a.key - b.key; - }); - this._ids = ids; - return ids; + ids.push({name: key, id: id}); + } + ids.sort(function (a, b) { + return a.key - b.key; + }); + this._ids = ids; + return ids; }; /** @@ -153,20 +152,21 @@ ModelDefinition.prototype.ids = function () { * @param {String} modelName The model name * @returns {String} columnName for ID */ -ModelDefinition.prototype.idColumnName = function(connectorType) { - return this.columnName(connectorType, this.idName()); +ModelDefinition.prototype.idColumnName = function (connectorType) { + return this.columnName(connectorType, this.idName()); }; /** * Find the ID property name * @returns {String} property name for ID */ -ModelDefinition.prototype.idName = function() { - var id = this.ids()[0]; - if(this.properties.id && this.properties.id.id) { - return 'id'; - } else {} - return id && id.name; +ModelDefinition.prototype.idName = function () { + var id = this.ids()[0]; + if (this.properties.id && this.properties.id.id) { + return 'id'; + } else { + } + return id && id.name; }; /** @@ -174,11 +174,11 @@ ModelDefinition.prototype.idName = function() { * @returns {String[]} property names for IDs */ ModelDefinition.prototype.idNames = function () { - var ids = this.ids(); - var names = ids.map(function (id) { - return id.name; - }); - return names; + var ids = this.ids(); + var names = ids.map(function (id) { + return id.name; + }); + return names; }; /** @@ -186,19 +186,19 @@ ModelDefinition.prototype.idNames = function () { * @returns {{}} */ ModelDefinition.prototype.indexes = function () { - this.build(); - var indexes = {}; - if (this.settings.indexes) { - for (var i in this.settings.indexes) { - indexes[i] = this.settings.indexes[i]; - } + this.build(); + var indexes = {}; + if (this.settings.indexes) { + for (var i in this.settings.indexes) { + indexes[i] = this.settings.indexes[i]; } - for (var p in this.properties) { - if (this.properties[p].index) { - indexes[p + '_index'] = this.properties[p].index; - } + } + for (var p in this.properties) { + if (this.properties[p].index) { + indexes[p + '_index'] = this.properties[p].index; } - return indexes; + } + return indexes; }; /** @@ -206,41 +206,41 @@ ModelDefinition.prototype.indexes = function () { * @param {Boolean} force Forcing rebuild */ ModelDefinition.prototype.build = function (forceRebuild) { - if(forceRebuild) { - this.properties = null; - this.relations = []; - this._ids = null; - } - if (this.properties) { - return this.properties; - } - this.properties = {}; - for (var p in this.rawProperties) { - var prop = this.rawProperties[p]; - var type = this.modelBuilder.resolveType(prop); - if (typeof type === 'string') { - this.relations.push({ - source: this.name, - target: type, - type: Array.isArray(prop) ? 'hasMany' : 'belongsTo', - as: p - }); - } else { - var typeDef = { - type: type - }; - if (typeof prop === 'object' && prop !== null) { - for (var a in prop) { - // Skip the type property but don't delete it Model.extend() shares same instances of the properties from the base class - if (a !== 'type') { - typeDef[a] = prop[a]; - } - } - } - this.properties[p] = typeDef; - } - } + if (forceRebuild) { + this.properties = null; + this.relations = []; + this._ids = null; + } + if (this.properties) { return this.properties; + } + this.properties = {}; + for (var p in this.rawProperties) { + var prop = this.rawProperties[p]; + var type = this.modelBuilder.resolveType(prop); + if (typeof type === 'string') { + this.relations.push({ + source: this.name, + target: type, + type: Array.isArray(prop) ? 'hasMany' : 'belongsTo', + as: p + }); + } else { + var typeDef = { + type: type + }; + if (typeof prop === 'object' && prop !== null) { + for (var a in prop) { + // Skip the type property but don't delete it Model.extend() shares same instances of the properties from the base class + if (a !== 'type') { + typeDef[a] = prop[a]; + } + } + } + this.properties[p] = typeDef; + } + } + return this.properties; }; /** @@ -249,56 +249,55 @@ ModelDefinition.prototype.build = function (forceRebuild) { * @param {Object} propertyDefinition The property definition */ ModelDefinition.prototype.defineProperty = function (propertyName, propertyDefinition) { - this.rawProperties[propertyName] = propertyDefinition; - this.build(true); + this.rawProperties[propertyName] = propertyDefinition; + this.build(true); }; - function isModelClass(cls) { - if(!cls) { - return false; - } - return cls.prototype instanceof ModelBaseClass; + if (!cls) { + return false; + } + return cls.prototype instanceof ModelBaseClass; } -ModelDefinition.prototype.toJSON = function(forceRebuild) { - if(forceRebuild) { - this.json = null; - } - if(this.json) { - return json; - } - var json = { - name: this.name, - properties: {}, - settings: this.settings - }; - this.build(forceRebuild); - - var mapper = function(val) { - if(val === undefined || val === null) { - return val; - } - if('function' === typeof val.toJSON) { - // The value has its own toJSON() object - return val.toJSON(); - } - if('function' === typeof val) { - if(isModelClass(val)) { - if(val.settings && val.settings.anonymous) { - return val.definition && val.definition.toJSON().properties; - } else { - return val.modelName; - } - } - return val.name; - } else { - return val; - } - }; - for(var p in this.properties) { - json.properties[p] = traverse(this.properties[p]).map(mapper); - } - this.json = json; +ModelDefinition.prototype.toJSON = function (forceRebuild) { + if (forceRebuild) { + this.json = null; + } + if (this.json) { return json; + } + var json = { + name: this.name, + properties: {}, + settings: this.settings + }; + this.build(forceRebuild); + + var mapper = function (val) { + if (val === undefined || val === null) { + return val; + } + if ('function' === typeof val.toJSON) { + // The value has its own toJSON() object + return val.toJSON(); + } + if ('function' === typeof val) { + if (isModelClass(val)) { + if (val.settings && val.settings.anonymous) { + return val.definition && val.definition.toJSON().properties; + } else { + return val.modelName; + } + } + return val.name; + } else { + return val; + } + }; + for (var p in this.properties) { + json.properties[p] = traverse(this.properties[p]).map(mapper); + } + this.json = json; + return json; }; diff --git a/lib/model.js b/lib/model.js index ec8ec90e..58211b14 100644 --- a/lib/model.js +++ b/lib/model.js @@ -6,7 +6,7 @@ module.exports = ModelBaseClass; /** * Module dependencies */ - + var util = require('util'); var traverse = require('traverse'); var jutil = require('./jutil'); @@ -28,19 +28,19 @@ var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text']; * @param {Object} data - initial object data */ function ModelBaseClass(data) { - this._initProperties(data, true); + this._initProperties(data, true); } // FIXME: [rfeng] We need to make sure the input data should not be mutated. Disabled cloning for now to get tests passing function clone(data) { - /* - if(!(data instanceof ModelBaseClass)) { - if(data && (Array.isArray(data) || 'object' === typeof data)) { - return traverse(data).clone(); - } - } - */ - return data; + /* + if(!(data instanceof ModelBaseClass)) { + if(data && (Array.isArray(data) || 'object' === typeof data)) { + return traverse(data).clone(); + } + } + */ + return data; } /** * Initialize properties @@ -49,117 +49,117 @@ function clone(data) { * @private */ ModelBaseClass.prototype._initProperties = function (data, applySetters) { - var self = this; - var ctor = this.constructor; - - var properties = ctor.definition.build(); - data = data || {}; + var self = this; + var ctor = this.constructor; - Object.defineProperty(this, '__cachedRelations', { - writable: true, - enumerable: false, - configurable: true, - value: {} - }); + var properties = ctor.definition.build(); + data = data || {}; - Object.defineProperty(this, '__data', { - writable: true, - enumerable: false, - configurable: true, - value: {} - }); + Object.defineProperty(this, '__cachedRelations', { + writable: true, + enumerable: false, + configurable: true, + value: {} + }); - Object.defineProperty(this, '__dataWas', { - writable: true, - enumerable: false, - configurable: true, - value: {} - }); + Object.defineProperty(this, '__data', { + writable: true, + enumerable: false, + configurable: true, + value: {} + }); - if (data['__cachedRelations']) { - this.__cachedRelations = data['__cachedRelations']; + Object.defineProperty(this, '__dataWas', { + writable: true, + enumerable: false, + configurable: true, + value: {} + }); + + if (data['__cachedRelations']) { + this.__cachedRelations = data['__cachedRelations']; + } + + // Check if the strict option is set to false for the model + var strict = ctor.definition.settings.strict; + + for (var i in data) { + if (i in properties) { + this.__data[i] = this.__dataWas[i] = clone(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] = clone(data[i]); + } else if (strict === 'throw') { + throw new Error('Unknown property: ' + i); + } + } + } + + if (applySetters === true) { + for (var propertyName in data) { + if ((propertyName in properties) || (propertyName in ctor.relations)) { + self[propertyName] = self.__data[propertyName] || data[propertyName]; + } + } + } + + // Set the unknown properties as properties to the object + if (strict === false) { + for (var propertyName in data) { + if (!(propertyName in properties)) { + self[propertyName] = self.__data[propertyName] || data[propertyName]; + } + } + } + + ctor.forEachProperty(function (propertyName) { + + if ('undefined' === typeof self.__data[propertyName]) { + self.__data[propertyName] = self.__dataWas[propertyName] = getDefault(propertyName); + } else { + self.__dataWas[propertyName] = self.__data[propertyName]; } - // Check if the strict option is set to false for the model - var strict = ctor.definition.settings.strict; + }); - for (var i in data) { - if (i in properties) { - this.__data[i] = this.__dataWas[i] = clone(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] = clone(data[i]); - } else if(strict === 'throw') { - throw new Error('Unknown property: ' + i); - } + ctor.forEachProperty(function (propertyName) { + + var type = properties[propertyName].type; + + if (BASE_TYPES.indexOf(type.name) === -1) { + if (typeof self.__data[propertyName] !== 'object' && self.__data[propertyName]) { + try { + self.__data[propertyName] = JSON.parse(self.__data[propertyName] + ''); + } catch (e) { + self.__data[propertyName] = String(self.__data[propertyName]); } + } + if (type.name === 'Array' || Array.isArray(type)) { + if (!(self.__data[propertyName] instanceof List)) { + self.__data[propertyName] = new List(self.__data[propertyName], type, self); + } + } } - if (applySetters === true) { - for(var propertyName in data) { - if((propertyName in properties) || (propertyName in ctor.relations)) { - self[propertyName] = self.__data[propertyName] || data[propertyName]; - } - } + }); + + function getDefault(propertyName) { + var def = properties[propertyName]['default']; + if (def !== undefined) { + if (typeof def === 'function') { + return def(); + } else { + return def; + } + } else { + return undefined; } + } - // Set the unknown properties as properties to the object - if(strict === false) { - for(var propertyName in data) { - if(!(propertyName in properties)) { - self[propertyName] = self.__data[propertyName] || data[propertyName]; - } - } - } - - ctor.forEachProperty(function (propertyName) { - - if ('undefined' === typeof self.__data[propertyName]) { - self.__data[propertyName] = self.__dataWas[propertyName] = getDefault(propertyName); - } else { - self.__dataWas[propertyName] = self.__data[propertyName]; - } - - }); - - ctor.forEachProperty(function (propertyName) { - - var type = properties[propertyName].type; - - if (BASE_TYPES.indexOf(type.name) === -1) { - if (typeof self.__data[propertyName] !== 'object' && self.__data[propertyName]) { - try { - self.__data[propertyName] = JSON.parse(self.__data[propertyName] + ''); - } catch (e) { - self.__data[propertyName] = String(self.__data[propertyName]); - } - } - if (type.name === 'Array' || Array.isArray(type)) { - if(!(self.__data[propertyName] instanceof List)) { - self.__data[propertyName] = new List(self.__data[propertyName], type, self); - } - } - } - - }); - - function getDefault(propertyName) { - var def = properties[propertyName]['default']; - if (def !== undefined) { - if (typeof def === 'function') { - return def(); - } else { - return def; - } - } else { - return undefined; - } - } - - this.trigger('initialize'); + this.trigger('initialize'); } /** @@ -167,24 +167,24 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) { * @param {Object} params - various property configuration */ ModelBaseClass.defineProperty = function (prop, params) { - this.dataSource.defineProperty(this.modelName, prop, params); + this.dataSource.defineProperty(this.modelName, prop, params); }; ModelBaseClass.getPropertyType = function (propName) { - var prop = this.definition.properties[propName]; - if(!prop) { - // The property is not part of the definition - return null; - } - if (!prop.type) { - throw new Error('Type not defined for property ' + this.modelName + '.' + propName); - // return null; - } - return prop.type.name; + var prop = this.definition.properties[propName]; + if (!prop) { + // The property is not part of the definition + return null; + } + if (!prop.type) { + throw new Error('Type not defined for property ' + this.modelName + '.' + propName); + // return null; + } + return prop.type.name; }; ModelBaseClass.prototype.getPropertyType = function (propName) { - return this.constructor.getPropertyType(propName); + return this.constructor.getPropertyType(propName); }; /** @@ -193,7 +193,7 @@ ModelBaseClass.prototype.getPropertyType = function (propName) { * @override default toString method */ ModelBaseClass.toString = function () { - return '[Model ' + this.modelName + ']'; + return '[Model ' + this.modelName + ']'; }; /** @@ -205,37 +205,37 @@ ModelBaseClass.toString = function () { * @returns {Object} - canonical object representation (no getters and setters) */ ModelBaseClass.prototype.toObject = function (onlySchema) { - var data = {}; - var self = this; + var data = {}; + var self = this; - var schemaLess = this.constructor.definition.settings.strict === false || !onlySchema; - this.constructor.forEachProperty(function (propertyName) { - if (self[propertyName] instanceof List) { - data[propertyName] = self[propertyName].toObject(!schemaLess); - } else if (self.__data.hasOwnProperty(propertyName)) { - if(self[propertyName] !== undefined && self[propertyName]!== null && self[propertyName].toObject) { - data[propertyName] = self[propertyName].toObject(!schemaLess); - } else { - data[propertyName] = self[propertyName]; - } - } else { - data[propertyName] = null; - } - }); - - if (schemaLess) { - for(var propertyName in self.__data) { - if (!data.hasOwnProperty(propertyName)) { - var val = self.hasOwnProperty(propertyName) ? self[propertyName] : self.__data[propertyName]; - if(val !== undefined && val!== null && val.toObject) { - data[propertyName] = val.toObject(!schemaLess); - } else { - data[propertyName] = val; - } - } - } + var schemaLess = this.constructor.definition.settings.strict === false || !onlySchema; + this.constructor.forEachProperty(function (propertyName) { + if (self[propertyName] instanceof List) { + data[propertyName] = self[propertyName].toObject(!schemaLess); + } else if (self.__data.hasOwnProperty(propertyName)) { + if (self[propertyName] !== undefined && self[propertyName] !== null && self[propertyName].toObject) { + data[propertyName] = self[propertyName].toObject(!schemaLess); + } else { + data[propertyName] = self[propertyName]; + } + } else { + data[propertyName] = null; } - return data; + }); + + if (schemaLess) { + for (var propertyName in self.__data) { + if (!data.hasOwnProperty(propertyName)) { + var val = self.hasOwnProperty(propertyName) ? self[propertyName] : self.__data[propertyName]; + if (val !== undefined && val !== null && val.toObject) { + data[propertyName] = val.toObject(!schemaLess); + } else { + data[propertyName] = val; + } + } + } + } + return data; }; // ModelBaseClass.prototype.hasOwnProperty = function (prop) { @@ -244,13 +244,13 @@ ModelBaseClass.prototype.toObject = function (onlySchema) { // }; ModelBaseClass.prototype.toJSON = function () { - return this.toObject(); + return this.toObject(); }; ModelBaseClass.prototype.fromObject = function (obj) { - for(var key in obj) { - this[key] = obj[key]; - } + for (var key in obj) { + this[key] = obj[key]; + } }; /** @@ -260,7 +260,7 @@ ModelBaseClass.prototype.fromObject = function (obj) { * @return Boolean */ ModelBaseClass.prototype.propertyChanged = function propertyChanged(propertyName) { - return this.__data[propertyName] !== this.__dataWas[propertyName]; + return this.__data[propertyName] !== this.__dataWas[propertyName]; }; /** @@ -270,23 +270,23 @@ ModelBaseClass.prototype.propertyChanged = function propertyChanged(propertyName * initial state */ ModelBaseClass.prototype.reset = function () { - var obj = this; - for(var k in obj) { - if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) { - delete obj[k]; - } - if (obj.propertyChanged(k)) { - obj[k] = obj[k + '$was']; - } + var obj = this; + for (var k in obj) { + if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) { + delete obj[k]; } + if (obj.propertyChanged(k)) { + obj[k] = obj[k + '$was']; + } + } }; ModelBaseClass.prototype.inspect = function () { - return util.inspect(this.__data, false, 4, true); + return util.inspect(this.__data, false, 4, true); }; -ModelBaseClass.mixin = function(anotherClass, options) { - return jutil.mixin(this, anotherClass, options); +ModelBaseClass.mixin = function (anotherClass, options) { + return jutil.mixin(this, anotherClass, options); }; ModelBaseClass.prototype.getDataSource = function () { diff --git a/lib/relations.js b/lib/relations.js index 65e98824..d3588168 100644 --- a/lib/relations.js +++ b/lib/relations.js @@ -11,11 +11,11 @@ function Relation() { } Relation.relationNameFor = function relationNameFor(foreignKey) { - for (var rel in this.relations) { - if (this.relations[rel].type === 'belongsTo' && this.relations[rel].keyFrom === foreignKey) { - return rel; - } + for (var rel in this.relations) { + if (this.relations[rel].type === 'belongsTo' && this.relations[rel].keyFrom === foreignKey) { + return rel; } + } }; /** @@ -26,131 +26,132 @@ Relation.relationNameFor = function relationNameFor(foreignKey) { * @example `User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});` */ Relation.hasMany = function hasMany(anotherClass, params) { - var thisClassName = this.modelName; - params = params || {}; - if (typeof anotherClass === 'string') { - params.as = anotherClass; - if (params.model) { - anotherClass = params.model; - } else { - var anotherClassName = i8n.singularize(anotherClass).toLowerCase(); - for(var name in this.dataSource.modelBuilder.models) { - if (name.toLowerCase() === anotherClassName) { - anotherClass = this.dataSource.modelBuilder.models[name]; - } - } + var thisClassName = this.modelName; + params = params || {}; + if (typeof anotherClass === 'string') { + params.as = anotherClass; + if (params.model) { + anotherClass = params.model; + } else { + var anotherClassName = i8n.singularize(anotherClass).toLowerCase(); + for (var name in this.dataSource.modelBuilder.models) { + if (name.toLowerCase() === anotherClassName) { + anotherClass = this.dataSource.modelBuilder.models[name]; } + } } - var methodName = params.as || i8n.camelize(anotherClass.pluralModelName, true); - var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true); + } + var methodName = params.as || i8n.camelize(anotherClass.pluralModelName, true); + var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true); - var idName = this.dataSource.idName(this.modelName) || 'id'; + var idName = this.dataSource.idName(this.modelName) || 'id'; - this.relations[methodName] = { - type: 'hasMany', - keyFrom: idName, - keyTo: fk, - modelTo: anotherClass, - multiple: true - }; - // each instance of this class should have method named - // pluralize(anotherClass.modelName) - // which is actually just anotherClass.find({where: {thisModelNameId: this[idName]}}, cb); - var scopeMethods = { - findById: find, - destroy: destroy - }; - if (params.through) { - var fk2 = i8n.camelize(anotherClass.modelName + '_id', true); - scopeMethods.create = function create(data, done) { - if (typeof data !== 'object') { - done = data; - data = {}; - } - if ('function' !== typeof done) { - done = function() {}; - } - var self = this; - anotherClass.create(data, function(err, ac) { - if (err) return done(err, ac); - var d = {}; - d[params.through.relationNameFor(fk)] = self; - d[params.through.relationNameFor(fk2)] = ac; - params.through.create(d, function(e) { - if (e) { - ac.destroy(function() { - done(e); - }); - } else { - done(err, ac); - } - }); + this.relations[methodName] = { + type: 'hasMany', + keyFrom: idName, + keyTo: fk, + modelTo: anotherClass, + multiple: true + }; + // each instance of this class should have method named + // pluralize(anotherClass.modelName) + // which is actually just anotherClass.find({where: {thisModelNameId: this[idName]}}, cb); + var scopeMethods = { + findById: find, + destroy: destroy + }; + if (params.through) { + var fk2 = i8n.camelize(anotherClass.modelName + '_id', true); + scopeMethods.create = function create(data, done) { + if (typeof data !== 'object') { + done = data; + data = {}; + } + if ('function' !== typeof done) { + done = function () { + }; + } + var self = this; + anotherClass.create(data, function (err, ac) { + if (err) return done(err, ac); + var d = {}; + d[params.through.relationNameFor(fk)] = self; + d[params.through.relationNameFor(fk2)] = ac; + params.through.create(d, function (e) { + if (e) { + ac.destroy(function () { + done(e); }); - }; - scopeMethods.add = function(acInst, done) { - var data = {}; - var query = {}; - query[fk] = this[idName]; - data[params.through.relationNameFor(fk)] = this; - query[fk2] = acInst[idName] || acInst; - data[params.through.relationNameFor(fk2)] = acInst; - params.through.findOrCreate({where: query}, data, done); - }; - scopeMethods.remove = function(acInst, done) { - var q = {}; - q[fk2] = acInst[idName] || acInst; - params.through.findOne({where: q}, function(err, d) { - if (err) { - return done(err); - } - if (!d) { - return done(); - } - d.destroy(done); - }); - }; - delete scopeMethods.destroy; - } - defineScope(this.prototype, params.through || anotherClass, methodName, function () { - var filter = {}; - filter.where = {}; - filter.where[fk] = this[idName]; - if (params.through) { - filter.collect = i8n.camelize(anotherClass.modelName, true); - filter.include = filter.collect; - } - return filter; - }, scopeMethods); - - if (!params.through) { - // obviously, anotherClass should have attribute called `fk` - anotherClass.dataSource.defineForeignKey(anotherClass.modelName, fk, this.modelName); - } - - function find(id, cb) { - anotherClass.findById(id, function (err, inst) { - if (err) return cb(err); - if (!inst) return cb(new Error('Not found')); - if (inst[fk] && inst[fk].toString() == this[idName].toString()) { - cb(null, inst); - } else { - cb(new Error('Permission denied')); - } - }.bind(this)); - } - - function destroy(id, cb) { - var self = this; - anotherClass.findById(id, function (err, inst) { - if (err) return cb(err); - if (!inst) return cb(new Error('Not found')); - if (inst[fk] && inst[fk].toString() == self[idName].toString()) { - inst.destroy(cb); - } else { - cb(new Error('Permission denied')); - } + } else { + done(err, ac); + } }); + }); + }; + scopeMethods.add = function (acInst, done) { + var data = {}; + var query = {}; + query[fk] = this[idName]; + data[params.through.relationNameFor(fk)] = this; + query[fk2] = acInst[idName] || acInst; + data[params.through.relationNameFor(fk2)] = acInst; + params.through.findOrCreate({where: query}, data, done); + }; + scopeMethods.remove = function (acInst, done) { + var q = {}; + q[fk2] = acInst[idName] || acInst; + params.through.findOne({where: q}, function (err, d) { + if (err) { + return done(err); + } + if (!d) { + return done(); + } + d.destroy(done); + }); + }; + delete scopeMethods.destroy; + } + defineScope(this.prototype, params.through || anotherClass, methodName, function () { + var filter = {}; + filter.where = {}; + filter.where[fk] = this[idName]; + if (params.through) { + filter.collect = i8n.camelize(anotherClass.modelName, true); + filter.include = filter.collect; } + return filter; + }, scopeMethods); + + if (!params.through) { + // obviously, anotherClass should have attribute called `fk` + anotherClass.dataSource.defineForeignKey(anotherClass.modelName, fk, this.modelName); + } + + function find(id, cb) { + anotherClass.findById(id, function (err, inst) { + if (err) return cb(err); + if (!inst) return cb(new Error('Not found')); + if (inst[fk] && inst[fk].toString() == this[idName].toString()) { + cb(null, inst); + } else { + cb(new Error('Permission denied')); + } + }.bind(this)); + } + + function destroy(id, cb) { + var self = this; + anotherClass.findById(id, function (err, inst) { + if (err) return cb(err); + if (!inst) return cb(new Error('Not found')); + if (inst[fk] && inst[fk].toString() == self[idName].toString()) { + inst.destroy(cb); + } else { + cb(new Error('Permission denied')); + } + }); + } }; @@ -178,87 +179,87 @@ Relation.hasMany = function hasMany(anotherClass, params) { * This optional parameter default value is false, so the related object will be loaded from cache if available. */ Relation.belongsTo = function (anotherClass, params) { - params = params || {}; - if ('string' === typeof anotherClass) { - params.as = anotherClass; - if (params.model) { - anotherClass = params.model; - } else { - var anotherClassName = anotherClass.toLowerCase(); - for(var name in this.dataSource.modelBuilder.models) { - if (name.toLowerCase() === anotherClassName) { - anotherClass = this.dataSource.modelBuilder.models[name]; - } - } + params = params || {}; + if ('string' === typeof anotherClass) { + params.as = anotherClass; + if (params.model) { + anotherClass = params.model; + } else { + var anotherClassName = anotherClass.toLowerCase(); + for (var name in this.dataSource.modelBuilder.models) { + if (name.toLowerCase() === anotherClassName) { + anotherClass = this.dataSource.modelBuilder.models[name]; } + } } + } - var idName = this.dataSource.idName(anotherClass.modelName) || 'id'; - var methodName = params.as || i8n.camelize(anotherClass.modelName, true); - var fk = params.foreignKey || methodName + 'Id'; + var idName = this.dataSource.idName(anotherClass.modelName) || 'id'; + var methodName = params.as || i8n.camelize(anotherClass.modelName, true); + var fk = params.foreignKey || methodName + 'Id'; - this.relations[methodName] = { - type: 'belongsTo', - keyFrom: fk, - keyTo: idName, - modelTo: anotherClass, - multiple: false - }; + this.relations[methodName] = { + type: 'belongsTo', + keyFrom: fk, + keyTo: idName, + modelTo: anotherClass, + multiple: false + }; - this.dataSource.defineForeignKey(this.modelName, fk, anotherClass.modelName); - this.prototype['__finders__'] = this.prototype['__finders__'] || {}; + this.dataSource.defineForeignKey(this.modelName, fk, anotherClass.modelName); + this.prototype['__finders__'] = this.prototype['__finders__'] || {}; - this.prototype['__finders__'][methodName] = function (id, cb) { - if (id === null) { - cb(null, null); - return; - } - anotherClass.findById(id, function (err,inst) { - if (err) return cb(err); - if (!inst) return cb(null, null); - if (inst[idName] === this[fk]) { - cb(null, inst); - } else { - cb(new Error('Permission denied')); - } - }.bind(this)); - }; + this.prototype['__finders__'][methodName] = function (id, cb) { + if (id === null) { + cb(null, null); + return; + } + anotherClass.findById(id, function (err, inst) { + if (err) return cb(err); + if (!inst) return cb(null, null); + if (inst[idName] === this[fk]) { + cb(null, inst); + } else { + cb(new Error('Permission denied')); + } + }.bind(this)); + }; - this.prototype[methodName] = function (refresh, p) { - if (arguments.length === 1) { - p = refresh; - refresh = false; - } else if (arguments.length > 2) { - throw new Error('Method can\'t be called with more than two arguments'); - } - var self = this; - var cachedValue; - if (!refresh && this.__cachedRelations && (typeof this.__cachedRelations[methodName] !== 'undefined')) { - cachedValue = this.__cachedRelations[methodName]; - } - if (p instanceof ModelBaseClass) { // acts as setter - this[fk] = p[idName]; - this.__cachedRelations[methodName] = p; - } else if (typeof p === 'function') { // acts as async getter - if (typeof cachedValue === 'undefined') { - this.__finders__[methodName].apply(self, [this[fk], function(err, inst) { - if (!err) { - self.__cachedRelations[methodName] = inst; - } - p(err, inst); - }]); - return this[fk]; - } else { - p(null, cachedValue); - return cachedValue; - } - } else if (typeof p === 'undefined') { // acts as sync getter - return this[fk]; - } else { // setter - this[fk] = p; - delete this.__cachedRelations[methodName]; - } - }; + this.prototype[methodName] = function (refresh, p) { + if (arguments.length === 1) { + p = refresh; + refresh = false; + } else if (arguments.length > 2) { + throw new Error('Method can\'t be called with more than two arguments'); + } + var self = this; + var cachedValue; + if (!refresh && this.__cachedRelations && (typeof this.__cachedRelations[methodName] !== 'undefined')) { + cachedValue = this.__cachedRelations[methodName]; + } + if (p instanceof ModelBaseClass) { // acts as setter + this[fk] = p[idName]; + this.__cachedRelations[methodName] = p; + } else if (typeof p === 'function') { // acts as async getter + if (typeof cachedValue === 'undefined') { + this.__finders__[methodName].apply(self, [this[fk], function (err, inst) { + if (!err) { + self.__cachedRelations[methodName] = inst; + } + p(err, inst); + }]); + return this[fk]; + } else { + p(null, cachedValue); + return cachedValue; + } + } else if (typeof p === 'undefined') { // acts as sync getter + return this[fk]; + } else { // setter + this[fk] = p; + delete this.__cachedRelations[methodName]; + } + }; }; @@ -268,40 +269,40 @@ Relation.belongsTo = function (anotherClass, params) { * Post.hasAndBelongsToMany('tags'); creates connection model 'PostTag' */ Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params) { - params = params || {}; - var models = this.dataSource.modelBuilder.models; + params = params || {}; + var models = this.dataSource.modelBuilder.models; - if ('string' === typeof anotherClass) { - params.as = anotherClass; - if (params.model) { - anotherClass = params.model; - } else { - anotherClass = lookupModel(i8n.singularize(anotherClass)) || - anotherClass; - } - if (typeof anotherClass === 'string') { - throw new Error('Could not find "' + anotherClass + '" relation for ' + this.modelName); - } + if ('string' === typeof anotherClass) { + params.as = anotherClass; + if (params.model) { + anotherClass = params.model; + } else { + anotherClass = lookupModel(i8n.singularize(anotherClass)) || + anotherClass; } - - if (!params.through) { - var name1 = this.modelName + anotherClass.modelName; - var name2 = anotherClass.modelName + this.modelName; - params.through = lookupModel(name1) || lookupModel(name2) || - this.dataSource.define(name1); + if (typeof anotherClass === 'string') { + throw new Error('Could not find "' + anotherClass + '" relation for ' + this.modelName); } - params.through.belongsTo(this); - params.through.belongsTo(anotherClass); + } - this.hasMany(anotherClass, {as: params.as, through: params.through}); + if (!params.through) { + var name1 = this.modelName + anotherClass.modelName; + var name2 = anotherClass.modelName + this.modelName; + params.through = lookupModel(name1) || lookupModel(name2) || + this.dataSource.define(name1); + } + params.through.belongsTo(this); + params.through.belongsTo(anotherClass); - function lookupModel(modelName) { - var lookupClassName = modelName.toLowerCase(); - for (var name in models) { - if (name.toLowerCase() === lookupClassName) { - return models[name]; - } - } + this.hasMany(anotherClass, {as: params.as, through: params.through}); + + function lookupModel(modelName) { + var lookupClassName = modelName.toLowerCase(); + for (var name in models) { + if (name.toLowerCase() === lookupClassName) { + return models[name]; + } } + } }; diff --git a/lib/scope.js b/lib/scope.js index c1e6a51d..d37d97c5 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -5,192 +5,192 @@ exports.defineScope = defineScope; function defineScope(cls, targetClass, name, params, methods) { - // collect meta info about scope - if (!cls._scopeMeta) { - cls._scopeMeta = {}; - } + // collect meta info about scope + if (!cls._scopeMeta) { + cls._scopeMeta = {}; + } - // only makes sence to add scope in meta if base and target classes - // are same - if (cls === targetClass) { - cls._scopeMeta[name] = params; - } else { - if (!targetClass._scopeMeta) { - targetClass._scopeMeta = {}; + // only makes sence to add scope in meta if base and target classes + // are same + if (cls === targetClass) { + cls._scopeMeta[name] = params; + } else { + if (!targetClass._scopeMeta) { + targetClass._scopeMeta = {}; + } + } + + // Define a property for the scope + Object.defineProperty(cls, name, { + enumerable: false, + configurable: true, + /** + * This defines a property for the scope. For example, user.accounts or + * User.vips. Please note the cls can be the model class or prototype of + * the model class. + * + * The property value is function. It can be used to query the scope, + * such as user.accounts(condOrRefresh, cb) or User.vips(cb). The value + * can also have child properties for create/build/delete. For example, + * user.accounts.create(act, cb). + * + */ + get: function () { + var f = function caller(condOrRefresh, cb) { + var actualCond = {}; + var actualRefresh = false; + var saveOnCache = true; + if (arguments.length === 1) { + cb = condOrRefresh; + } else if (arguments.length === 2) { + if (typeof condOrRefresh === 'boolean') { + actualRefresh = condOrRefresh; + } else { + actualCond = condOrRefresh; + actualRefresh = true; + saveOnCache = false; + } + } else { + throw new Error('Method can be only called with one or two arguments'); } - } - // Define a property for the scope - Object.defineProperty(cls, name, { - enumerable: false, - configurable: true, - /** - * This defines a property for the scope. For example, user.accounts or - * User.vips. Please note the cls can be the model class or prototype of - * the model class. - * - * The property value is function. It can be used to query the scope, - * such as user.accounts(condOrRefresh, cb) or User.vips(cb). The value - * can also have child properties for create/build/delete. For example, - * user.accounts.create(act, cb). - * - */ - get: function () { - var f = function caller(condOrRefresh, cb) { - var actualCond = {}; - var actualRefresh = false; - var saveOnCache = true; - if (arguments.length === 1) { - cb = condOrRefresh; - } else if (arguments.length === 2) { - if (typeof condOrRefresh === 'boolean') { - actualRefresh = condOrRefresh; - } else { - actualCond = condOrRefresh; - actualRefresh = true; - saveOnCache = false; - } - } else { - throw new Error('Method can be only called with one or two arguments'); - } - - if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) { - var self = this; - var params = mergeParams(actualCond, caller._scope); - return targetClass.find(params, function(err, data) { - if (!err && saveOnCache) { - if (!self.__cachedRelations) { - self.__cachedRelations = {}; - } - self.__cachedRelations[name] = data; - } - cb(err, data); - }); - } else { - cb(null, this.__cachedRelations[name]); - } - }; - f._scope = typeof params === 'function' ? params.call(this) : params; - - f.build = build; - f.create = create; - f.destroyAll = destroyAll; - for (var i in methods) { - f[i] = methods[i].bind(this); + if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) { + var self = this; + var params = mergeParams(actualCond, caller._scope); + return targetClass.find(params, function (err, data) { + if (!err && saveOnCache) { + if (!self.__cachedRelations) { + self.__cachedRelations = {}; + } + self.__cachedRelations[name] = data; } + cb(err, data); + }); + } else { + cb(null, this.__cachedRelations[name]); + } + }; + f._scope = typeof params === 'function' ? params.call(this) : params; - // define sub-scopes - Object.keys(targetClass._scopeMeta).forEach(function (name) { - Object.defineProperty(f, name, { - enumerable: false, - get: function () { - mergeParams(f._scope, targetClass._scopeMeta[name]); - return f; - } - }); - }.bind(this)); + f.build = build; + f.create = create; + f.destroyAll = destroyAll; + for (var i in methods) { + f[i] = methods[i].bind(this); + } + + // define sub-scopes + Object.keys(targetClass._scopeMeta).forEach(function (name) { + Object.defineProperty(f, name, { + enumerable: false, + get: function () { + mergeParams(f._scope, targetClass._scopeMeta[name]); return f; - } - }); - - // Wrap the property into a function for remoting - var fn = function() { - // primaryObject.scopeName, such as user.accounts - var f = this[name]; - // set receiver to be the scope property whose value is a function - f.apply(this[name], arguments); - }; - - fn.shared = true; - fn.http = {verb: 'get', path: '/' + name}; - fn.accepts = {arg: 'where', type: 'object'}; - fn.description = 'Fetches ' + name; - fn.returns = {arg: name, type: 'array', root: true}; - - cls['__get__' + name] = fn; - - var fn_create = function() { - var f = this[name].create; - f.apply(this[name], arguments); - }; - - fn_create.shared = true; - fn_create.http = {verb: 'post', path: '/' + name}; - fn_create.accepts = {arg: 'data', type: 'object', http: {source: 'body'}}; - fn_create.description = 'Creates ' + name; - fn_create.returns = {arg: 'data', type: 'object', root: true}; - - cls['__create__' + name] = fn_create; - - var fn_delete = function() { - var f = this[name].destroyAll; - f.apply(this[name], arguments); - }; - fn_delete.shared = true; - fn_delete.http = {verb: 'delete', path: '/' + name}; - fn_delete.description = 'Deletes ' + name; - fn_delete.returns = {arg: 'data', type: 'object', root: true}; - - cls['__delete__' + name] = fn_delete; - - // and it should have create/build methods with binded thisModelNameId param - function build(data) { - return new targetClass(mergeParams(this._scope, {where:data || {}}).where); - } - - function create(data, cb) { - if (typeof data === 'function') { - cb = data; - data = {}; - } - this.build(data).save(cb); - } - - /* - Callback - - The callback will be called after all elements are destroyed - - For every destroy call which results in an error - - If fetching the Elements on which destroyAll is called results in an error - */ - function destroyAll(cb) { - targetClass.find(this._scope, function (err, data) { - if (err) { - cb(err); - } else { - (function loopOfDestruction (data) { - if(data.length > 0) { - data.shift().destroy(function(err) { - if(err && cb) cb(err); - loopOfDestruction(data); - }); - } else { - if(cb) cb(); - } - }(data)); - } + } }); + }.bind(this)); + return f; + } + }); + + // Wrap the property into a function for remoting + var fn = function () { + // primaryObject.scopeName, such as user.accounts + var f = this[name]; + // set receiver to be the scope property whose value is a function + f.apply(this[name], arguments); + }; + + fn.shared = true; + fn.http = {verb: 'get', path: '/' + name}; + fn.accepts = {arg: 'where', type: 'object'}; + fn.description = 'Fetches ' + name; + fn.returns = {arg: name, type: 'array', root: true}; + + cls['__get__' + name] = fn; + + var fn_create = function () { + var f = this[name].create; + f.apply(this[name], arguments); + }; + + fn_create.shared = true; + fn_create.http = {verb: 'post', path: '/' + name}; + fn_create.accepts = {arg: 'data', type: 'object', http: {source: 'body'}}; + fn_create.description = 'Creates ' + name; + fn_create.returns = {arg: 'data', type: 'object', root: true}; + + cls['__create__' + name] = fn_create; + + var fn_delete = function () { + var f = this[name].destroyAll; + f.apply(this[name], arguments); + }; + fn_delete.shared = true; + fn_delete.http = {verb: 'delete', path: '/' + name}; + fn_delete.description = 'Deletes ' + name; + fn_delete.returns = {arg: 'data', type: 'object', root: true}; + + cls['__delete__' + name] = fn_delete; + + // and it should have create/build methods with binded thisModelNameId param + function build(data) { + return new targetClass(mergeParams(this._scope, {where: data || {}}).where); + } + + function create(data, cb) { + if (typeof data === 'function') { + cb = data; + data = {}; + } + this.build(data).save(cb); + } + + /* + Callback + - The callback will be called after all elements are destroyed + - For every destroy call which results in an error + - If fetching the Elements on which destroyAll is called results in an error + */ + function destroyAll(cb) { + targetClass.find(this._scope, function (err, data) { + if (err) { + cb(err); + } else { + (function loopOfDestruction(data) { + if (data.length > 0) { + data.shift().destroy(function (err) { + if (err && cb) cb(err); + loopOfDestruction(data); + }); + } else { + if (cb) cb(); + } + }(data)); + } + }); + } + + function mergeParams(base, update) { + base = base || {}; + if (update.where) { + base.where = merge(base.where, update.where); + } + if (update.include) { + base.include = update.include; + } + if (update.collect) { + base.collect = update.collect; } - function mergeParams(base, update) { - base = base || {}; - if (update.where) { - base.where = merge(base.where, update.where); - } - if (update.include) { - base.include = update.include; - } - if (update.collect) { - base.collect = update.collect; - } - - // overwrite order - if (update.order) { - base.order = update.order; - } - - return base; - + // overwrite order + if (update.order) { + base.order = update.order; } + + return base; + + } } /** @@ -200,12 +200,12 @@ function defineScope(cls, targetClass, name, params, methods) { * @returns {Object} `base` */ function merge(base, update) { - base = base || {}; - if (update) { - Object.keys(update).forEach(function (key) { - base[key] = update[key]; - }); - } - return base; + base = base || {}; + if (update) { + Object.keys(update).forEach(function (key) { + base[key] = update[key]; + }); + } + return base; } diff --git a/lib/sql.js b/lib/sql.js index 281212c5..b3b5c664 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -8,7 +8,7 @@ module.exports = BaseSQL; * @constructor */ function BaseSQL() { - Connector.apply(this, [].slice.call(arguments)); + Connector.apply(this, [].slice.call(arguments)); } util.inherits(BaseSQL, Connector); @@ -20,33 +20,32 @@ util.inherits(BaseSQL, Connector); BaseSQL.prototype.relational = true; BaseSQL.prototype.query = function () { - throw new Error('query method should be declared in connector'); + throw new Error('query method should be declared in connector'); }; BaseSQL.prototype.command = function (sql, params, callback) { - return this.query(sql, params, callback); + return this.query(sql, params, callback); }; BaseSQL.prototype.queryOne = function (sql, callback) { - return this.query(sql, function (err, data) { - if (err) return callback(err); - callback(err, data[0]); - }); + return this.query(sql, function (err, data) { + if (err) return callback(err); + callback(err, data[0]); + }); }; - /** * Get the table name for a given model * @param {String} model The model name * @returns {String} The table name */ BaseSQL.prototype.table = function (model) { - var name = this.getDataSource(model).tableName(model); - var dbName = this.dbName; - if(typeof dbName === 'function') { - name = dbName(name); - } - return name; + var name = this.getDataSource(model).tableName(model); + var dbName = this.dbName; + if (typeof dbName === 'function') { + name = dbName(name); + } + return name; }; /** @@ -56,12 +55,12 @@ BaseSQL.prototype.table = function (model) { * @returns {String} The column name */ BaseSQL.prototype.column = function (model, property) { - var name = this.getDataSource(model).columnName(model, property); - var dbName = this.dbName; - if(typeof dbName === 'function') { - name = dbName(name); - } - return name; + var name = this.getDataSource(model).columnName(model, property); + var dbName = this.dbName; + if (typeof dbName === 'function') { + name = dbName(name); + } + return name; }; /** @@ -71,7 +70,7 @@ BaseSQL.prototype.column = function (model, property) { * @returns {Object} The column metadata */ BaseSQL.prototype.columnMetadata = function (model, property) { - return this.getDataSource(model).columnMetadata(model, property); + return this.getDataSource(model).columnMetadata(model, property); }; /** @@ -81,13 +80,13 @@ BaseSQL.prototype.columnMetadata = function (model, property) { * @returns {String} The property name for a given column */ BaseSQL.prototype.propertyName = function (model, column) { - var props = this._models[model].properties; - for(var p in props) { - if(this.column(model, p) === column) { - return p; - } + var props = this._models[model].properties; + for (var p in props) { + if (this.column(model, p) === column) { + return p; } - return null; + } + return null; }; /** @@ -96,12 +95,13 @@ BaseSQL.prototype.propertyName = function (model, column) { * @returns {String} The column name */ BaseSQL.prototype.idColumn = function (model) { - var name = this.getDataSource(model).idColumnName(model);; - var dbName = this.dbName; - if(typeof dbName === 'function') { - name = dbName(name); - } - return name; + var name = this.getDataSource(model).idColumnName(model); + ; + var dbName = this.dbName; + if (typeof dbName === 'function') { + name = dbName(name); + } + return name; }; /** @@ -110,7 +110,7 @@ BaseSQL.prototype.idColumn = function (model) { * @returns {String} the escaped id column name */ BaseSQL.prototype.idColumnEscaped = function (model) { - return this.escapeName(this.getDataSource(model).idColumnName(model)); + return this.escapeName(this.getDataSource(model).idColumnName(model)); }; /** @@ -118,7 +118,7 @@ BaseSQL.prototype.idColumnEscaped = function (model) { * @param {String} name The name */ BaseSQL.prototype.escapeName = function (name) { - throw new Error('escapeName method should be declared in connector'); + throw new Error('escapeName method should be declared in connector'); }; /** @@ -127,7 +127,7 @@ BaseSQL.prototype.escapeName = function (name) { * @returns {String} the escaped table name */ BaseSQL.prototype.tableEscaped = function (model) { - return this.escapeName(this.table(model)); + return this.escapeName(this.table(model)); }; /** @@ -137,7 +137,7 @@ BaseSQL.prototype.tableEscaped = function (model) { * @returns {String} The escaped column name */ BaseSQL.prototype.columnEscaped = function (model, property) { - return this.escapeName(this.column(model, property)); + return this.escapeName(this.column(model, property)); }; /** @@ -147,15 +147,14 @@ BaseSQL.prototype.columnEscaped = function (model, property) { * @param {Function} callback The callback function */ BaseSQL.prototype.save = function (model, data, callback) { - var sql = 'UPDATE ' + this.tableEscaped(model) + ' SET ' - + this.toFields(model, data) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + Number(data.id); + var sql = 'UPDATE ' + this.tableEscaped(model) + ' SET ' + + this.toFields(model, data) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + Number(data.id); - this.query(sql, function (err) { - callback(err); - }); + this.query(sql, function (err) { + callback(err); + }); }; - /** * Check if a model instance exists for the given id value * @param {String} model The model name @@ -163,13 +162,13 @@ BaseSQL.prototype.save = function (model, data, callback) { * @param {Function} callback The callback function */ BaseSQL.prototype.exists = function (model, id, callback) { - var sql = 'SELECT 1 FROM ' + - this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + Number(id) + ' LIMIT 1'; + var sql = 'SELECT 1 FROM ' + + this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + Number(id) + ' LIMIT 1'; - this.query(sql, function (err, data) { - if (err) return callback(err); - callback(null, data.length === 1); - }); + this.query(sql, function (err, data) { + if (err) return callback(err); + callback(null, data.length === 1); + }); }; /** @@ -179,17 +178,17 @@ BaseSQL.prototype.exists = function (model, id, callback) { * @param {Function} callback The callback function */ BaseSQL.prototype.find = function find(model, id, callback) { - var sql = 'SELECT * FROM ' + - this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id + ' LIMIT 1'; + var sql = 'SELECT * FROM ' + + this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id + ' LIMIT 1'; - this.query(sql, function (err, data) { - if (data && data.length === 1) { - data[0].id = id; - } else { - data = [null]; - } - callback(err, this.fromDatabase(model, data[0])); - }.bind(this)); + this.query(sql, function (err, data) { + if (data && data.length === 1) { + data[0].id = id; + } else { + data = [null]; + } + callback(err, this.fromDatabase(model, data[0])); + }.bind(this)); }; /** @@ -199,12 +198,12 @@ BaseSQL.prototype.find = function find(model, id, callback) { * @param {Function} callback The callback function */ BaseSQL.prototype.delete = BaseSQL.prototype.destroy = function destroy(model, id, callback) { - var sql = 'DELETE FROM ' + - this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id; + var sql = 'DELETE FROM ' + + this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id; - this.command(sql, function (err) { - callback(err); - }); + this.command(sql, function (err) { + callback(err); + }); }; /** @@ -214,12 +213,12 @@ BaseSQL.prototype.delete = BaseSQL.prototype.destroy = function destroy(model, i * @param {Function} callback The callback function */ BaseSQL.prototype.deleteAll = BaseSQL.prototype.destroyAll = function destroyAll(model, callback) { - this.command('DELETE FROM ' + this.tableEscaped(model), function (err) { - if (err) { - return callback(err, []); - } - callback(err); - }.bind(this)); + this.command('DELETE FROM ' + this.tableEscaped(model), function (err) { + if (err) { + return callback(err, []); + } + callback(err); + }.bind(this)); }; /** @@ -230,27 +229,27 @@ BaseSQL.prototype.deleteAll = BaseSQL.prototype.destroyAll = function destroyAll * @param {Object} where The where clause */ BaseSQL.prototype.count = function count(model, callback, where) { - var self = this; - var props = this._models[model].properties; + var self = this; + var props = this._models[model].properties; - this.queryOne('SELECT count(*) as cnt FROM ' + - this.tableEscaped(model) + ' ' + buildWhere(where), function (err, res) { - if (err) return callback(err); - callback(err, res && res.cnt); + this.queryOne('SELECT count(*) as cnt FROM ' + + this.tableEscaped(model) + ' ' + buildWhere(where), function (err, res) { + if (err) return callback(err); + callback(err, res && res.cnt); + }); + + function buildWhere(conds) { + var cs = []; + Object.keys(conds || {}).forEach(function (key) { + var keyEscaped = self.columnEscaped(model, key); + if (conds[key] === null) { + cs.push(keyEscaped + ' IS NULL'); + } else { + cs.push(keyEscaped + ' = ' + self.toDatabase(props[key], conds[key])); + } }); - - function buildWhere(conds) { - var cs = []; - Object.keys(conds || {}).forEach(function (key) { - var keyEscaped = self.columnEscaped(model, key); - if (conds[key] === null) { - cs.push(keyEscaped + ' IS NULL'); - } else { - cs.push(keyEscaped + ' = ' + self.toDatabase(props[key], conds[key])); - } - }); - return cs.length ? ' WHERE ' + cs.join(' AND ') : ''; - } + return cs.length ? ' WHERE ' + cs.join(' AND ') : ''; + } }; /** @@ -261,15 +260,15 @@ BaseSQL.prototype.count = function count(model, callback, where) { * @param {Function} cb The callback function */ BaseSQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { - data.id = id; - this.save(model, data, cb); + data.id = id; + this.save(model, data, cb); }; /** * Disconnect from the connector */ BaseSQL.prototype.disconnect = function disconnect() { - this.client.end(); + this.client.end(); }; /** @@ -278,38 +277,38 @@ BaseSQL.prototype.disconnect = function disconnect() { * @param {Function} [cb] The callback function */ BaseSQL.prototype.automigrate = function (models, cb) { - var self = this; - var wait = 0; - if ((!cb) && ('function' === typeof models)) { - cb = models; - models = undefined; - } - // First argument is a model name - if ('string' === typeof models) { - models = [models]; - } + var self = this; + var wait = 0; + if ((!cb) && ('function' === typeof models)) { + cb = models; + models = undefined; + } + // First argument is a model name + if ('string' === typeof models) { + models = [models]; + } - models = models || Object.keys(this._models); - models.forEach(function (model) { - if (model in self._models) { - wait++; - self.dropTable(model, function () { - // console.log('drop', model); - self.createTable(model, function (err) { - // console.log('create', model); - if (err) console.log(err); - done(); - }); - }); - } - }); - if (wait === 0) cb(); - - function done() { - if (--wait === 0 && cb) { - cb(); - } + models = models || Object.keys(this._models); + models.forEach(function (model) { + if (model in self._models) { + wait++; + self.dropTable(model, function () { + // console.log('drop', model); + self.createTable(model, function (err) { + // console.log('create', model); + if (err) console.log(err); + done(); + }); + }); } + }); + if (wait === 0) cb(); + + function done() { + if (--wait === 0 && cb) { + cb(); + } + } }; /** @@ -318,7 +317,7 @@ BaseSQL.prototype.automigrate = function (models, cb) { * @param {Function} [cb] The callback function */ BaseSQL.prototype.dropTable = function (model, cb) { - this.command('DROP TABLE IF EXISTS ' + this.tableEscaped(model), cb); + this.command('DROP TABLE IF EXISTS ' + this.tableEscaped(model), cb); }; /** @@ -328,7 +327,7 @@ BaseSQL.prototype.dropTable = function (model, cb) { */ BaseSQL.prototype.createTable = function (model, cb) { - this.command('CREATE TABLE ' + this.tableEscaped(model) + - ' (\n ' + this.propertiesSQL(model) + '\n)', cb); + this.command('CREATE TABLE ' + this.tableEscaped(model) + + ' (\n ' + this.propertiesSQL(model) + '\n)', cb); }; diff --git a/lib/types.js b/lib/types.js index 8967d4c0..e5dbcc1a 100644 --- a/lib/types.js +++ b/lib/types.js @@ -1,61 +1,61 @@ module.exports = function (Types) { - var List = require('./list.js'); - var GeoPoint = require('./geo').GeoPoint; + var List = require('./list.js'); + var GeoPoint = require('./geo').GeoPoint; - /** - * Schema types - */ - Types.Text = function Text(value) { - if (!(this instanceof Text)) { - return value; - } - this.value = value; - }; // Text type + /** + * Schema types + */ + Types.Text = function Text(value) { + if (!(this instanceof Text)) { + return value; + } + this.value = value; + }; // Text type - Types.Text.prototype.toObject = Types.Text.prototype.toJSON = function () { - return this.value; - }; + Types.Text.prototype.toObject = Types.Text.prototype.toJSON = function () { + return this.value; + }; - Types.JSON = function JSON(value) { - if (!(this instanceof JSON)) { - return value; - } - this.value = value; - }; // JSON Object - Types.JSON.prototype.toObject = Types.JSON.prototype.toJSON = function () { - return this.value; - }; + Types.JSON = function JSON(value) { + if (!(this instanceof JSON)) { + return value; + } + this.value = value; + }; // JSON Object + Types.JSON.prototype.toObject = Types.JSON.prototype.toJSON = function () { + return this.value; + }; - Types.Any = function Any(value) { - if (!(this instanceof Any)) { - return value; - } - this.value = value; - }; // Any Type - Types.Any.prototype.toObject = Types.Any.prototype.toJSON = function () { - return this.value; - }; + Types.Any = function Any(value) { + if (!(this instanceof Any)) { + return 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.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(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 + 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/lib/utils.js b/lib/utils.js index 806f9c2d..08804dda 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -8,54 +8,56 @@ exports.mergeSettings = mergeSettings; var traverse = require('traverse'); function safeRequire(module) { - try { - return require(module); - } catch (e) { - console.log('Run "npm install loopback-datasource-juggler ' + module + '" command to use loopback-datasource-juggler using ' + module + ' database engine'); - process.exit(1); - } + try { + return require(module); + } catch (e) { + console.log('Run "npm install loopback-datasource-juggler ' + module + + '" command to use loopback-datasource-juggler using ' + module + + ' database engine'); + process.exit(1); + } } function fieldsToArray(fields, properties) { - if(!fields) return; - - // include all properties by default - var result = properties; - - if(typeof fields === 'string') { - return [fields]; + if (!fields) return; + + // include all properties by default + var result = properties; + + if (typeof fields === 'string') { + return [fields]; + } + + if (Array.isArray(fields) && fields.length > 0) { + // No empty array, including all the fields + return fields; + } + + if ('object' === typeof fields) { + // { field1: boolean, field2: boolean ... } + var included = []; + var excluded = []; + var keys = Object.keys(fields); + if (!keys.length) return; + + keys.forEach(function (k) { + if (fields[k]) { + included.push(k); + } else if ((k in fields) && !fields[k]) { + excluded.push(k); + } + }); + if (included.length > 0) { + result = included; + } else if (excluded.length > 0) { + excluded.forEach(function (e) { + var index = result.indexOf(e); + result.splice(index, 1); + }); } - - if (Array.isArray(fields) && fields.length > 0) { - // No empty array, including all the fields - return fields; - } - - if ('object' === typeof fields) { - // { field1: boolean, field2: boolean ... } - var included = []; - var excluded = []; - var keys = Object.keys(fields); - if(!keys.length) return; - - keys.forEach(function (k) { - if (fields[k]) { - included.push(k); - } else if ((k in fields) && !fields[k]) { - excluded.push(k); - } - }); - if (included.length > 0) { - result = included; - } else if (excluded.length > 0) { - excluded.forEach(function (e) { - var index = result.indexOf(e); - result.splice(index, 1); - }); - } - } - - return result; + } + + return result; } function selectFields(fields) { @@ -63,10 +65,10 @@ function selectFields(fields) { return function (obj) { var result = {}; var key; - + for (var i = 0; i < fields.length; i++) { key = fields[i]; - + result[key] = obj[key]; } return result; @@ -79,24 +81,25 @@ function selectFields(fields) { * @returns {exports.map|*} */ function removeUndefined(query) { - if (typeof query !== 'object' || query === null) { - return query; + if (typeof query !== 'object' || query === null) { + return query; + } + // WARNING: [rfeng] Use map() will cause mongodb to produce invalid BSON + // as traverse doesn't transform the ObjectId correctly + return traverse(query).forEach(function (x) { + if (x === undefined) { + this.remove(); } - // WARNING: [rfeng] Use map() will cause mongodb to produce invalid BSON - // as traverse doesn't transform the ObjectId correctly - return traverse(query).forEach(function (x) { - if (x === undefined) { - this.remove(); - } - if (!Array.isArray(x) && (typeof x === 'object' && x !== null && x.constructor !== Object)) { - // This object is not a plain object - this.update(x, true); // Stop navigating into this object - return x; - } + if (!Array.isArray(x) && (typeof x === 'object' && x !== null + && x.constructor !== Object)) { + // This object is not a plain object + this.update(x, true); // Stop navigating into this object + return x; + } - return x; - }); + return x; + }); } var url = require('url'); @@ -108,25 +111,25 @@ var qs = require('qs'); * @returns {Object} The settings object */ function parseSettings(urlStr) { - if(!urlStr) { - return {}; + if (!urlStr) { + return {}; + } + var uri = url.parse(urlStr, false); + var settings = {}; + settings.connector = uri.protocol && uri.protocol.split(':')[0]; // Remove the trailing : + settings.host = settings.hostname = uri.hostname; + settings.port = uri.port && Number(uri.port); // port is a string + settings.user = settings.username = uri.auth && uri.auth.split(':')[0]; // : + settings.password = uri.auth && uri.auth.split(':')[1]; + settings.database = uri.pathname && uri.pathname.split('/')[1]; // remove the leading / + settings.url = urlStr; + if (uri.query) { + var params = qs.parse(uri.query); + for (var p in params) { + settings[p] = params[p]; } - var uri = url.parse(urlStr, false); - var settings = {}; - settings.connector = uri.protocol && uri.protocol.split(':')[0]; // Remove the trailing : - settings.host = settings.hostname = uri.hostname; - settings.port = uri.port && Number(uri.port); // port is a string - settings.user = settings.username = uri.auth && uri.auth.split(':')[0]; // : - settings.password = uri.auth && uri.auth.split(':')[1]; - settings.database = uri.pathname && uri.pathname.split('/')[1]; // remove the leading / - settings.url = urlStr; - if(uri.query) { - var params = qs.parse(uri.query); - for(var p in params) { - settings[p] = params[p]; - } - } - return settings; + } + return settings; } /** diff --git a/lib/validations.js b/lib/validations.js index 397841ec..c7a43157 100644 --- a/lib/validations.js +++ b/lib/validations.js @@ -26,7 +26,7 @@ function Validatable() { /** * Validate presence. This validation fails when validated field is blank. - * + * * Default error message "can't be blank" * * @example presence of title @@ -96,7 +96,7 @@ Validatable.validatesNumericalityOf = getConfigurator('numericality'); /** * Validate inclusion in set * - * @example + * @example * ``` * User.validatesInclusionOf('gender', {in: ['male', 'female']}); * User.validatesInclusionOf('role', { @@ -203,121 +203,121 @@ Validatable.validatesUniquenessOf = getConfigurator('uniqueness', {async: true}) * Presence validator */ function validatePresence(attr, conf, err) { - if (blank(this[attr])) { - err(); - } + if (blank(this[attr])) { + err(); + } } /** * Length validator */ function validateLength(attr, conf, err) { - if (nullCheck.call(this, attr, conf, err)) return; + if (nullCheck.call(this, attr, conf, err)) return; - var len = this[attr].length; - if (conf.min && len < conf.min) { - err('min'); - } - if (conf.max && len > conf.max) { - err('max'); - } - if (conf.is && len !== conf.is) { - err('is'); - } + var len = this[attr].length; + if (conf.min && len < conf.min) { + err('min'); + } + if (conf.max && len > conf.max) { + err('max'); + } + if (conf.is && len !== conf.is) { + err('is'); + } } /** * Numericality validator */ function validateNumericality(attr, conf, err) { - if (nullCheck.call(this, attr, conf, err)) return; + if (nullCheck.call(this, attr, conf, err)) return; - if (typeof this[attr] !== 'number') { - return err('number'); - } - if (conf.int && this[attr] !== Math.round(this[attr])) { - return err('int'); - } + if (typeof this[attr] !== 'number') { + return err('number'); + } + if (conf.int && this[attr] !== Math.round(this[attr])) { + return err('int'); + } } /** * Inclusion validator */ function validateInclusion(attr, conf, err) { - if (nullCheck.call(this, attr, conf, err)) return; + if (nullCheck.call(this, attr, conf, err)) return; - if (!~conf.in.indexOf(this[attr])) { - err() - } + if (!~conf.in.indexOf(this[attr])) { + err() + } } /** * Exclusion validator */ function validateExclusion(attr, conf, err) { - if (nullCheck.call(this, attr, conf, err)) return; + if (nullCheck.call(this, attr, conf, err)) return; - if (~conf.in.indexOf(this[attr])) { - err() - } + if (~conf.in.indexOf(this[attr])) { + err() + } } /** * Format validator */ function validateFormat(attr, conf, err) { - if (nullCheck.call(this, attr, conf, err)) return; + if (nullCheck.call(this, attr, conf, err)) return; - if (typeof this[attr] === 'string') { - if (!this[attr].match(conf['with'])) { - err(); - } - } else { - err(); + if (typeof this[attr] === 'string') { + if (!this[attr].match(conf['with'])) { + err(); } + } else { + err(); + } } /** * Custom validator */ function validateCustom(attr, conf, err, done) { - conf.customValidator.call(this, err, done); + conf.customValidator.call(this, err, done); } /** * Uniqueness validator */ function validateUniqueness(attr, conf, err, done) { - var cond = {where: {}}; - cond.where[attr] = this[attr]; - this.constructor.find(cond, function (error, found) { - if (error) { - return err(); - } - if (found.length > 1) { - err(); - } else if (found.length === 1 && (!this.id || !found[0].id || found[0].id.toString() != this.id.toString())) { - err(); - } - done(); - }.bind(this)); + var cond = {where: {}}; + cond.where[attr] = this[attr]; + this.constructor.find(cond, function (error, found) { + if (error) { + return err(); + } + if (found.length > 1) { + err(); + } else if (found.length === 1 && (!this.id || !found[0].id || found[0].id.toString() != this.id.toString())) { + err(); + } + done(); + }.bind(this)); } var validators = { - presence: validatePresence, - length: validateLength, - numericality: validateNumericality, - inclusion: validateInclusion, - exclusion: validateExclusion, - format: validateFormat, - custom: validateCustom, - uniqueness: validateUniqueness + presence: validatePresence, + length: validateLength, + numericality: validateNumericality, + inclusion: validateInclusion, + exclusion: validateExclusion, + format: validateFormat, + custom: validateCustom, + uniqueness: validateUniqueness }; function getConfigurator(name, opts) { - return function () { - configure(this, name, arguments, opts); - }; + return function () { + configure(this, name, arguments, opts); + }; } /** @@ -341,193 +341,193 @@ function getConfigurator(name, opts) { * ``` */ Validatable.prototype.isValid = function (callback, data) { - var valid = true, inst = this, wait = 0, async = false; + var valid = true, inst = this, wait = 0, async = false; - // exit with success when no errors - if (!this.constructor._validations) { - cleanErrors(this); - if (callback) { - this.trigger('validate', function (validationsDone) { - validationsDone.call(inst, function() { - callback(valid); - }); - }); - } - return valid; + // exit with success when no errors + if (!this.constructor._validations) { + cleanErrors(this); + if (callback) { + this.trigger('validate', function (validationsDone) { + validationsDone.call(inst, function () { + callback(valid); + }); + }); } + return valid; + } + + Object.defineProperty(this, 'errors', { + enumerable: false, + configurable: true, + value: new Errors + }); + + this.trigger('validate', function (validationsDone) { + var inst = this, + asyncFail = false; + + this.constructor._validations.forEach(function (v) { + if (v[2] && v[2].async) { + async = true; + wait += 1; + process.nextTick(function () { + validationFailed(inst, v, done); + }); + } else { + if (validationFailed(inst, v)) { + valid = false; + } + } - Object.defineProperty(this, 'errors', { - enumerable: false, - configurable: true, - value: new Errors }); - this.trigger('validate', function (validationsDone) { - var inst = this, - asyncFail = false; - - this.constructor._validations.forEach(function (v) { - if (v[2] && v[2].async) { - async = true; - wait += 1; - process.nextTick(function () { - validationFailed(inst, v, done); - }); - } else { - if (validationFailed(inst, v)) { - valid = false; - } - } - - }); - - if (!async) { - validationsDone.call(inst, function() { - if (valid) cleanErrors(inst); - if (callback) { - callback(valid); - } - }); + if (!async) { + validationsDone.call(inst, function () { + if (valid) cleanErrors(inst); + if (callback) { + callback(valid); } - - function done(fail) { - asyncFail = asyncFail || fail; - if (--wait === 0) { - validationsDone.call(inst, function () { - if (valid && !asyncFail) cleanErrors(inst); - if (callback) { - callback(valid && !asyncFail); - } - }); - } - } - - }, data); - - if (async) { - // in case of async validation we should return undefined here, - // because not all validations are finished yet - return; - } else { - return valid; + }); } + function done(fail) { + asyncFail = asyncFail || fail; + if (--wait === 0) { + validationsDone.call(inst, function () { + if (valid && !asyncFail) cleanErrors(inst); + if (callback) { + callback(valid && !asyncFail); + } + }); + } + } + + }, data); + + if (async) { + // in case of async validation we should return undefined here, + // because not all validations are finished yet + return; + } else { + return valid; + } + }; function cleanErrors(inst) { - Object.defineProperty(inst, 'errors', { - enumerable: false, - configurable: true, - value: false - }); + Object.defineProperty(inst, 'errors', { + enumerable: false, + configurable: true, + value: false + }); } function validationFailed(inst, v, cb) { - var attr = v[0]; - var conf = v[1]; - var opts = v[2] || {}; + var attr = v[0]; + var conf = v[1]; + var opts = v[2] || {}; - if (typeof attr !== 'string') return false; + if (typeof attr !== 'string') return false; - // here we should check skip validation conditions (if, unless) - // that can be specified in conf - if (skipValidation(inst, conf, 'if')) return false; - if (skipValidation(inst, conf, 'unless')) return false; + // here we should check skip validation conditions (if, unless) + // that can be specified in conf + if (skipValidation(inst, conf, 'if')) return false; + if (skipValidation(inst, conf, 'unless')) return false; - var fail = false; - var validator = validators[conf.validation]; - var validatorArguments = []; - validatorArguments.push(attr); - validatorArguments.push(conf); - validatorArguments.push(function onerror(kind) { - var message, code = conf.validation; - if (conf.message) { - message = conf.message; - } - if (!message && defaultMessages[conf.validation]) { - message = defaultMessages[conf.validation]; - } - if (!message) { - message = 'is invalid'; - } - if (kind) { - code += '.' + kind; - if (message[kind]) { - // get deeper - message = message[kind]; - } else if (defaultMessages.common[kind]) { - message = defaultMessages.common[kind]; - } else { - message = 'is invalid'; - } - } - inst.errors.add(attr, message, code); - fail = true; - }); - if (cb) { - validatorArguments.push(function () { - cb(fail); - }); + var fail = false; + var validator = validators[conf.validation]; + var validatorArguments = []; + validatorArguments.push(attr); + validatorArguments.push(conf); + validatorArguments.push(function onerror(kind) { + var message, code = conf.validation; + if (conf.message) { + message = conf.message; } - validator.apply(inst, validatorArguments); - return fail; + if (!message && defaultMessages[conf.validation]) { + message = defaultMessages[conf.validation]; + } + if (!message) { + message = 'is invalid'; + } + if (kind) { + code += '.' + kind; + if (message[kind]) { + // get deeper + message = message[kind]; + } else if (defaultMessages.common[kind]) { + message = defaultMessages.common[kind]; + } else { + message = 'is invalid'; + } + } + inst.errors.add(attr, message, code); + fail = true; + }); + if (cb) { + validatorArguments.push(function () { + cb(fail); + }); + } + validator.apply(inst, validatorArguments); + return fail; } function skipValidation(inst, conf, kind) { - var doValidate = true; - if (typeof conf[kind] === 'function') { - doValidate = conf[kind].call(inst); - if (kind === 'unless') doValidate = !doValidate; - } else if (typeof conf[kind] === 'string') { - if (typeof inst[conf[kind]] === 'function') { - doValidate = inst[conf[kind]].call(inst); - if (kind === 'unless') doValidate = !doValidate; - } else if (inst.__data.hasOwnProperty(conf[kind])) { - doValidate = inst[conf[kind]]; - if (kind === 'unless') doValidate = !doValidate; - } else { - doValidate = kind === 'if'; - } + var doValidate = true; + if (typeof conf[kind] === 'function') { + doValidate = conf[kind].call(inst); + if (kind === 'unless') doValidate = !doValidate; + } else if (typeof conf[kind] === 'string') { + if (typeof inst[conf[kind]] === 'function') { + doValidate = inst[conf[kind]].call(inst); + if (kind === 'unless') doValidate = !doValidate; + } else if (inst.__data.hasOwnProperty(conf[kind])) { + doValidate = inst[conf[kind]]; + if (kind === 'unless') doValidate = !doValidate; + } else { + doValidate = kind === 'if'; } - return !doValidate; + } + return !doValidate; } var defaultMessages = { - presence: 'can\'t be blank', - length: { - min: 'too short', - max: 'too long', - is: 'length is wrong' - }, - common: { - blank: 'is blank', - 'null': 'is null' - }, - numericality: { - 'int': 'is not an integer', - 'number': 'is not a number' - }, - inclusion: 'is not included in the list', - exclusion: 'is reserved', - uniqueness: 'is not unique' + presence: 'can\'t be blank', + length: { + min: 'too short', + max: 'too long', + is: 'length is wrong' + }, + common: { + blank: 'is blank', + 'null': 'is null' + }, + numericality: { + 'int': 'is not an integer', + 'number': 'is not a number' + }, + inclusion: 'is not included in the list', + exclusion: 'is reserved', + uniqueness: 'is not unique' }; function nullCheck(attr, conf, err) { - var isNull = this[attr] === null || !(attr in this); - if (isNull) { - if (!conf.allowNull) { - err('null'); - } - return true; - } else { - if (blank(this[attr])) { - if (!conf.allowBlank) { - err('blank'); - } - return true; - } + var isNull = this[attr] === null || !(attr in this); + if (isNull) { + if (!conf.allowNull) { + err('null'); } - return false; + return true; + } else { + if (blank(this[attr])) { + if (!conf.allowBlank) { + err('blank'); + } + return true; + } + } + return false; } /** @@ -538,78 +538,78 @@ function nullCheck(attr, conf, err) { * @returns {Boolean} whether `v` blank or not */ function blank(v) { - if (typeof v === 'undefined') return true; - if (v instanceof Array && v.length === 0) return true; - if (v === null) return true; - if (typeof v == 'string' && v === '') return true; - return false; + if (typeof v === 'undefined') return true; + if (v instanceof Array && v.length === 0) return true; + if (v === null) return true; + if (typeof v == 'string' && v === '') return true; + return false; } function configure(cls, validation, args, opts) { - if (!cls._validations) { - Object.defineProperty(cls, '_validations', { - writable: true, - configurable: true, - enumerable: false, - value: [] - }); - } - args = [].slice.call(args); - var conf; - if (typeof args[args.length - 1] === 'object') { - conf = args.pop(); - } else { - conf = {}; - } - if (validation === 'custom' && typeof args[args.length - 1] === 'function') { - conf.customValidator = args.pop(); - } - conf.validation = validation; - args.forEach(function (attr) { - cls._validations.push([attr, conf, opts]); + if (!cls._validations) { + Object.defineProperty(cls, '_validations', { + writable: true, + configurable: true, + enumerable: false, + value: [] }); + } + args = [].slice.call(args); + var conf; + if (typeof args[args.length - 1] === 'object') { + conf = args.pop(); + } else { + conf = {}; + } + if (validation === 'custom' && typeof args[args.length - 1] === 'function') { + conf.customValidator = args.pop(); + } + conf.validation = validation; + args.forEach(function (attr) { + cls._validations.push([attr, conf, opts]); + }); } function Errors() { - Object.defineProperty(this, 'codes', { - enumerable: false, - configurable: true, - value: {} - }); + Object.defineProperty(this, 'codes', { + enumerable: false, + configurable: true, + value: {} + }); } Errors.prototype.add = function (field, message, code) { - code = code || 'invalid'; - if (!this[field]) { - this[field] = []; - this.codes[field] = []; - } - this[field].push(message); - this.codes[field].push(code); + code = code || 'invalid'; + if (!this[field]) { + this[field] = []; + this.codes[field] = []; + } + this[field].push(message); + this.codes[field].push(code); }; function ErrorCodes(messages) { - var c = this; - Object.keys(messages).forEach(function(field) { - c[field] = messages[field].codes; - }); + var c = this; + Object.keys(messages).forEach(function (field) { + c[field] = messages[field].codes; + }); } function ValidationError(obj) { - if (!(this instanceof ValidationError)) return new ValidationError(obj); + if (!(this instanceof ValidationError)) return new ValidationError(obj); - this.name = 'ValidationError'; - this.message = 'The Model instance is not valid. ' + - 'See `details` property of the error object for more info.'; - this.statusCode = 422; + this.name = 'ValidationError'; + this.message = 'The Model instance is not valid. ' + + 'See `details` property of the error object for more info.'; + this.statusCode = 422; - this.details = { - context: obj && obj.constructor && obj.constructor.modelName, - codes: obj.errors && obj.errors.codes, - messages: obj.errors - }; + this.details = { + context: obj && obj.constructor && obj.constructor.modelName, + codes: obj.errors && obj.errors.codes, + messages: obj.errors + }; - Error.captureStackTrace(this, this.constructor); + Error.captureStackTrace(this, this.constructor); } util.inherits(ValidationError, Error); diff --git a/test/basic-querying.test.js b/test/basic-querying.test.js index 11267b0a..9b90d66d 100644 --- a/test/basic-querying.test.js +++ b/test/basic-querying.test.js @@ -2,347 +2,345 @@ var should = require('./init.js'); var db, User; -describe('basic-querying', function() { +describe('basic-querying', function () { - before(function(done) { - db = getSchema(); - - User = db.define('User', { - name: {type: String, index: true, sort: true}, - email: {type: String, index: true}, - role: {type: String, index: true}, - order: {type: Number, index: true, sort: true} - }); - - db.automigrate(done); + before(function (done) { + db = getSchema(); + User = db.define('User', { + name: {type: String, index: true, sort: true}, + email: {type: String, index: true}, + role: {type: String, index: true}, + order: {type: Number, index: true, sort: true} }); + db.automigrate(done); - describe('findById', function() { + }); - before(function(done) { - User.destroyAll(done); - }); - - it('should query by id: not found', function(done) { - User.findById(1, function(err, u) { - should.not.exist(u); - should.not.exist(err); - done(); - }); - }); - - it('should query by id: found', function(done) { - User.create(function(err, u) { - should.not.exist(err); - should.exist(u.id); - User.findById(u.id, function(err, u) { - should.exist(u); - should.not.exist(err); - u.should.be.an.instanceOf(User); - done(); - }); - }); - }); + describe('findById', function () { + before(function (done) { + User.destroyAll(done); }); - describe('find', function() { + it('should query by id: not found', function (done) { + User.findById(1, function (err, u) { + should.not.exist(u); + should.not.exist(err); + done(); + }); + }); - before(seed); + it('should query by id: found', function (done) { + User.create(function (err, u) { + should.not.exist(err); + should.exist(u.id); + User.findById(u.id, function (err, u) { + should.exist(u); + should.not.exist(err); + u.should.be.an.instanceOf(User); + done(); + }); + }); + }); - it('should query collection', function(done) { - User.find(function(err, users) { - should.exists(users); - should.not.exists(err); - users.should.have.lengthOf(6); - done(); - }); - }); - - it('should query limited collection', function(done) { - User.find({limit: 3}, function(err, users) { - should.exists(users); - should.not.exists(err); - users.should.have.lengthOf(3); - done(); - }); - }); - - it('should query offset collection with limit', function(done) { - User.find({skip: 1, limit: 4}, function(err, users) { - should.exists(users); - should.not.exists(err); - users.should.have.lengthOf(4); - done(); - }); - }); + }); - it('should query filtered collection', function(done) { - User.find({where: {role: 'lead'}}, function(err, users) { - should.exists(users); - should.not.exists(err); - users.should.have.lengthOf(2); - done(); - }); - }); + describe('find', function () { - it('should query collection sorted by numeric field', function(done) { - User.find({order: 'order'}, function(err, users) { - should.exists(users); - should.not.exists(err); - users.forEach(function(u, i) { - u.order.should.eql(i + 1); - }); - done(); - }); - }); + before(seed); - it('should query collection desc sorted by numeric field', function(done) { - User.find({order: 'order DESC'}, function(err, users) { - should.exists(users); - should.not.exists(err); - users.forEach(function(u, i) { - u.order.should.eql(users.length - i); - }); - done(); - }); - }); + it('should query collection', function (done) { + User.find(function (err, users) { + should.exists(users); + should.not.exists(err); + users.should.have.lengthOf(6); + done(); + }); + }); - it('should query collection sorted by string field', function(done) { - User.find({order: 'name'}, function(err, users) { - should.exists(users); - should.not.exists(err); - users.shift().name.should.equal('George Harrison'); - users.shift().name.should.equal('John Lennon'); - users.pop().name.should.equal('Stuart Sutcliffe'); - done(); - }); - }); + it('should query limited collection', function (done) { + User.find({limit: 3}, function (err, users) { + should.exists(users); + should.not.exists(err); + users.should.have.lengthOf(3); + done(); + }); + }); - it('should query collection desc sorted by string field', function(done) { - User.find({order: 'name DESC'}, function(err, users) { - should.exists(users); - should.not.exists(err); - users.pop().name.should.equal('George Harrison'); - users.pop().name.should.equal('John Lennon'); - users.shift().name.should.equal('Stuart Sutcliffe'); - done(); - }); + it('should query offset collection with limit', function (done) { + User.find({skip: 1, limit: 4}, function (err, users) { + should.exists(users); + should.not.exists(err); + users.should.have.lengthOf(4); + done(); + }); + }); + + it('should query filtered collection', function (done) { + User.find({where: {role: 'lead'}}, function (err, users) { + should.exists(users); + should.not.exists(err); + users.should.have.lengthOf(2); + done(); + }); + }); + + it('should query collection sorted by numeric field', function (done) { + User.find({order: 'order'}, function (err, users) { + should.exists(users); + should.not.exists(err); + users.forEach(function (u, i) { + u.order.should.eql(i + 1); }); - - it('should only include fields as specified', function(done) { - var remaining = 0; - - function sample(fields) { - - return { - expect: function (arr) { - remaining++; - User.find({fields: fields}, function(err, users) { - - remaining--; - if(err) return done(err); - - should.exists(users); - - if(remaining === 0) { - done(); - } - - users.forEach(function (user) { - var obj = user.toObject(); - - Object.keys(obj) - .forEach(function (key) { - // if the obj has an unexpected value - if(obj[key] !== undefined && arr.indexOf(key) === -1) { - console.log('Given fields:', fields); - console.log('Got:', key, obj[key]); - console.log('Expected:', arr); - throw new Error('should not include data for key: '+ key); - } - }); - }); - }); - } + done(); + }); + }); + + it('should query collection desc sorted by numeric field', function (done) { + User.find({order: 'order DESC'}, function (err, users) { + should.exists(users); + should.not.exists(err); + users.forEach(function (u, i) { + u.order.should.eql(users.length - i); + }); + done(); + }); + }); + + it('should query collection sorted by string field', function (done) { + User.find({order: 'name'}, function (err, users) { + should.exists(users); + should.not.exists(err); + users.shift().name.should.equal('George Harrison'); + users.shift().name.should.equal('John Lennon'); + users.pop().name.should.equal('Stuart Sutcliffe'); + done(); + }); + }); + + it('should query collection desc sorted by string field', function (done) { + User.find({order: 'name DESC'}, function (err, users) { + should.exists(users); + should.not.exists(err); + users.pop().name.should.equal('George Harrison'); + users.pop().name.should.equal('John Lennon'); + users.shift().name.should.equal('Stuart Sutcliffe'); + done(); + }); + }); + + it('should only include fields as specified', function (done) { + var remaining = 0; + + function sample(fields) { + + return { + expect: function (arr) { + remaining++; + User.find({fields: fields}, function (err, users) { + + remaining--; + if (err) return done(err); + + should.exists(users); + + if (remaining === 0) { + done(); } - } - - sample({name: true}).expect(['name']); - sample({name: false}).expect(['id', 'email', 'role', 'order']); - sample({name: false, id: true}).expect(['id']); - sample({id: true}).expect(['id']); - sample('id').expect(['id']); - sample(['id']).expect(['id']); - sample(['email']).expect(['email']); - }); + users.forEach(function (user) { + var obj = user.toObject(); + + Object.keys(obj) + .forEach(function (key) { + // if the obj has an unexpected value + if (obj[key] !== undefined && arr.indexOf(key) === -1) { + console.log('Given fields:', fields); + console.log('Got:', key, obj[key]); + console.log('Expected:', arr); + throw new Error('should not include data for key: ' + key); + } + }); + }); + }); + } + } + } + + sample({name: true}).expect(['name']); + sample({name: false}).expect(['id', 'email', 'role', 'order']); + sample({name: false, id: true}).expect(['id']); + sample({id: true}).expect(['id']); + sample('id').expect(['id']); + sample(['id']).expect(['id']); + sample(['email']).expect(['email']); }); - describe('count', function() { + }); - before(seed); + describe('count', function () { - it('should query total count', function(done) { - User.count(function(err, n) { - should.not.exist(err); - should.exist(n); - n.should.equal(6); - done(); - }); - }); + before(seed); - it('should query filtered count', function(done) { - User.count({role: 'lead'}, function(err, n) { - should.not.exist(err); - should.exist(n); - n.should.equal(2); - done(); - }); - }); + it('should query total count', function (done) { + User.count(function (err, n) { + should.not.exist(err); + should.exist(n); + n.should.equal(6); + done(); + }); }); - describe('findOne', function() { + it('should query filtered count', function (done) { + User.count({role: 'lead'}, function (err, n) { + should.not.exist(err); + should.exist(n); + n.should.equal(2); + done(); + }); + }); + }); - before(seed); + describe('findOne', function () { - it('should find first record (default sort by id)', function(done) { - User.all({order: 'id'}, function(err, users) { - User.findOne(function(e, u) { - should.not.exist(e); - should.exist(u); - u.id.toString().should.equal(users[0].id.toString()); - done(); - }); - }); + before(seed); + + it('should find first record (default sort by id)', function (done) { + User.all({order: 'id'}, function (err, users) { + User.findOne(function (e, u) { + should.not.exist(e); + should.exist(u); + u.id.toString().should.equal(users[0].id.toString()); + done(); }); - - it('should find first record', function(done) { - User.findOne({order: 'order'}, function(e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(1); - u.name.should.equal('Paul McCartney'); - done(); - }); - }); - - it('should find last record', function(done) { - User.findOne({order: 'order DESC'}, function(e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(6); - u.name.should.equal('Ringo Starr'); - done(); - }); - }); - - it('should find last record in filtered set', function(done) { - User.findOne({ - where: {role: 'lead'}, - order: 'order DESC' - }, function(e, u) { - should.not.exist(e); - should.exist(u); - u.order.should.equal(2); - u.name.should.equal('John Lennon'); - done(); - }); - }); - - it('should work even when find by id', function(done) { - User.findOne(function(e, u) { - User.findOne({where: {id: u.id}}, function(err, user) { - should.not.exist(err); - should.exist(user); - done(); - }); - }); - }); - + }); }); - describe('exists', function() { - - before(seed); - - it('should check whether record exist', function(done) { - User.findOne(function(e, u) { - User.exists(u.id, function(err, exists) { - should.not.exist(err); - should.exist(exists); - exists.should.be.ok; - done(); - }); - }); - }); - - it('should check whether record not exist', function(done) { - User.destroyAll(function() { - User.exists(42, function(err, exists) { - should.not.exist(err); - exists.should.not.be.ok; - done(); - }); - }); - }); - + it('should find first record', function (done) { + User.findOne({order: 'order'}, function (e, u) { + should.not.exist(e); + should.exist(u); + u.order.should.equal(1); + u.name.should.equal('Paul McCartney'); + done(); + }); }); - describe('destroyAll with where option', function() { - - before(seed); - - it('should only delete instances that satisfy the where condition', function(done) { - User.destroyAll({name: 'John Lennon'}, function() { - User.find({where: {name: 'John Lennon'}}, function(err, data) { - should.not.exist(err); - data.length.should.equal(0); - User.find({where: {name: 'Paul McCartney'}}, function(err, data) { - should.not.exist(err); - data.length.should.equal(1); - done(); - }); - }); - }); - }); - + it('should find last record', function (done) { + User.findOne({order: 'order DESC'}, function (e, u) { + should.not.exist(e); + should.exist(u); + u.order.should.equal(6); + u.name.should.equal('Ringo Starr'); + done(); + }); }); + it('should find last record in filtered set', function (done) { + User.findOne({ + where: {role: 'lead'}, + order: 'order DESC' + }, function (e, u) { + should.not.exist(e); + should.exist(u); + u.order.should.equal(2); + u.name.should.equal('John Lennon'); + done(); + }); + }); + it('should work even when find by id', function (done) { + User.findOne(function (e, u) { + User.findOne({where: {id: u.id}}, function (err, user) { + should.not.exist(err); + should.exist(user); + done(); + }); + }); + }); + + }); + + describe('exists', function () { + + before(seed); + + it('should check whether record exist', function (done) { + User.findOne(function (e, u) { + User.exists(u.id, function (err, exists) { + should.not.exist(err); + should.exist(exists); + exists.should.be.ok; + done(); + }); + }); + }); + + it('should check whether record not exist', function (done) { + User.destroyAll(function () { + User.exists(42, function (err, exists) { + should.not.exist(err); + exists.should.not.be.ok; + done(); + }); + }); + }); + + }); + + describe('destroyAll with where option', function () { + + before(seed); + + it('should only delete instances that satisfy the where condition', function (done) { + User.destroyAll({name: 'John Lennon'}, function () { + User.find({where: {name: 'John Lennon'}}, function (err, data) { + should.not.exist(err); + data.length.should.equal(0); + User.find({where: {name: 'Paul McCartney'}}, function (err, data) { + should.not.exist(err); + data.length.should.equal(1); + done(); + }); + }); + }); + }); + + }); }); function seed(done) { - var count = 0; - var beatles = [ - { - name: 'John Lennon', - email: 'john@b3atl3s.co.uk', - role: 'lead', - order: 2 - }, { - name: 'Paul McCartney', - email: 'paul@b3atl3s.co.uk', - role: 'lead', - order: 1 - }, - {name: 'George Harrison', order: 5}, - {name: 'Ringo Starr', order: 6}, - {name: 'Pete Best', order: 4}, - {name: 'Stuart Sutcliffe', order: 3} - ]; - User.destroyAll(function() { - beatles.forEach(function(beatle) { - User.create(beatle, ok); - }); + var count = 0; + var beatles = [ + { + name: 'John Lennon', + email: 'john@b3atl3s.co.uk', + role: 'lead', + order: 2 + }, + { + name: 'Paul McCartney', + email: 'paul@b3atl3s.co.uk', + role: 'lead', + order: 1 + }, + {name: 'George Harrison', order: 5}, + {name: 'Ringo Starr', order: 6}, + {name: 'Pete Best', order: 4}, + {name: 'Stuart Sutcliffe', order: 3} + ]; + User.destroyAll(function () { + beatles.forEach(function (beatle) { + User.create(beatle, ok); }); + }); - function ok() { - if (++count === beatles.length) { - done(); - } + function ok() { + if (++count === beatles.length) { + done(); } + } } diff --git a/test/common_test.js b/test/common_test.js index 35d1ec72..ace03719 100644 --- a/test/common_test.js +++ b/test/common_test.js @@ -1,4 +1,3 @@ - var Schema = require('../index').Schema; var Text = Schema.Text; @@ -8,1122 +7,1130 @@ var batch; var schemaName; function it(name, cases) { - batch[schemaName][name] = cases; + batch[schemaName][name] = cases; } function skip(name) { - delete batch[schemaName][name]; + delete batch[schemaName][name]; } module.exports = function testSchema(exportCasesHere, dataSource) { - batch = exportCasesHere; - schemaName = dataSource.name; - if (dataSource.name.match(/^\/.*\/test\/\.\.$/)) { - schemaName = schemaName.split('/').slice(-3).shift(); - } - var start; + batch = exportCasesHere; + schemaName = dataSource.name; + if (dataSource.name.match(/^\/.*\/test\/\.\.$/)) { + schemaName = schemaName.split('/').slice(-3).shift(); + } + var start; - batch['should connect to database'] = function (test) { - start = Date.now(); - if (dataSource.connected) return test.done(); - dataSource.on('connected', test.done); - }; + batch['should connect to database'] = function (test) { + start = Date.now(); + if (dataSource.connected) return test.done(); + dataSource.on('connected', test.done); + }; - dataSource.log = function (a) { - console.log(a); - nbSchemaRequests++; - }; + dataSource.log = function (a) { + console.log(a); + nbSchemaRequests++; + }; - batch[schemaName] = {}; + batch[schemaName] = {}; - testOrm(dataSource); + testOrm(dataSource); - batch['all tests done'] = function (test) { - test.done(); - process.nextTick(allTestsDone); - }; + batch['all tests done'] = function (test) { + test.done(); + process.nextTick(allTestsDone); + }; - function allTestsDone() { - dataSource.disconnect(); - console.log('Test done in %dms\n', Date.now() - start); - } + function allTestsDone() { + dataSource.disconnect(); + console.log('Test done in %dms\n', Date.now() - start); + } }; Object.defineProperty(module.exports, 'it', { - writable: true, - enumerable: false, - configurable: true, - value: it + writable: true, + enumerable: false, + configurable: true, + value: it }); Object.defineProperty(module.exports, 'skip', { - writable: true, - enumerable: false, - configurable: true, - value: skip + writable: true, + enumerable: false, + configurable: true, + value: skip }); function clearAndCreate(model, data, callback) { - var createdItems = []; - model.destroyAll(function () { - nextItem(null, null); - }); + var createdItems = []; + model.destroyAll(function () { + nextItem(null, null); + }); - var itemIndex = 0; - function nextItem(err, lastItem) { - if (lastItem !== null) { - createdItems.push(lastItem); - } - if (itemIndex >= data.length) { - callback(createdItems); - return; - } - model.create(data[itemIndex], nextItem); - itemIndex++; + var itemIndex = 0; + + function nextItem(err, lastItem) { + if (lastItem !== null) { + createdItems.push(lastItem); } + if (itemIndex >= data.length) { + callback(createdItems); + return; + } + model.create(data[itemIndex], nextItem); + itemIndex++; + } } function testOrm(dataSource) { - var requestsAreCounted = dataSource.name !== 'mongodb'; + var requestsAreCounted = dataSource.name !== 'mongodb'; - var Post, User, Passport, Log, Dog; + var Post, User, Passport, Log, Dog; - it('should define class', function (test) { - - User = dataSource.define('User', { - name: { type: String, index: true }, - email: { type: String, index: true }, - bio: Text, - approved: Boolean, - joinedAt: Date, - age: Number, - passwd: { type: String, index: true } - }); - - Dog = dataSource.define('Dog', { - name : { type: String, limit: 64, allowNull: false } - }); - - Log = dataSource.define('Log', { - ownerId : { type: Number, allowNull: true }, - name : { type: String, limit: 64, allowNull: false } - }); - - Log.belongsTo(Dog, {as: 'owner', foreignKey: 'ownerId'}); - - dataSource.extendModel('User', { - settings: { type: Schema.JSON }, - extra: Object - }); - - var newuser = new User({settings: {hey: 'you'}}); - test.ok(newuser.settings); - - Post = dataSource.define('Post', { - title: { type: String, length: 255, index: true }, - subject: { type: String }, - content: { type: Text }, - date: { type: Date, default: function () { return new Date }, index: true }, - published: { type: Boolean, default: false, index: true }, - likes: [], - related: [RelatedPost] - }, {table: 'posts'}); - - function RelatedPost() { } - RelatedPost.prototype.someMethod = function () { - return this.parent; - }; - - Post.validateAsync('title', function (err, done) { - process.nextTick(done); - }); - - User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); - // creates instance methods: - // user.posts(conds) - // user.posts.build(data) // like new Post({userId: user.id}); - // user.posts.create(data) // build and save - // user.posts.find - - // User.hasOne('latestPost', {model: Post, foreignKey: 'postId'}); - - // User.hasOne(Post, {as: 'latestPost', foreignKey: 'latestPostId'}); - // creates instance methods: - // user.latestPost() - // user.latestPost.build(data) - // user.latestPost.create(data) - - Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); - // creates instance methods: - // post.author(callback) -- getter when called with function - // post.author() -- sync getter when called without params - // post.author(user) -- setter when called with object - - Passport = dataSource.define('Passport', { - number: String - }); - - Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'}); - User.hasMany(Passport, {as: 'passports', foreignKey: 'ownerId'}); - - var user = new User; - - test.ok(User instanceof Function); - - // class methods - test.ok(User.find instanceof Function); - test.ok(User.create instanceof Function); - - // instance methods - test.ok(user.save instanceof Function); - - dataSource.automigrate(function (err) { - if (err) { - console.log('Error while migrating'); - console.log(err); - } else { - test.done(); - } - }); + it('should define class', function (test) { + User = dataSource.define('User', { + name: { type: String, index: true }, + email: { type: String, index: true }, + bio: Text, + approved: Boolean, + joinedAt: Date, + age: Number, + passwd: { type: String, index: true } }); - it('should initialize object properly', function (test) { - var hw = 'Hello word', - now = Date.now(), - post = new Post({title: hw}), - anotherPost = Post({title: 'Resig style constructor'}); + Dog = dataSource.define('Dog', { + name: { type: String, limit: 64, allowNull: false } + }); - test.equal(post.title, hw); - test.ok(!post.propertyChanged('title'), 'property changed: title'); - post.title = 'Goodbye, Lenin'; - test.equal(post.title_was, hw); - test.ok(post.propertyChanged('title')); - test.strictEqual(post.published, false); - test.ok(post.date >= now); - test.ok(post.isNewRecord()); - test.ok(anotherPost instanceof Post); - test.ok(anotherPost.title, 'Resig style constructor'); + Log = dataSource.define('Log', { + ownerId: { type: Number, allowNull: true }, + name: { type: String, limit: 64, allowNull: false } + }); + + Log.belongsTo(Dog, {as: 'owner', foreignKey: 'ownerId'}); + + dataSource.extendModel('User', { + settings: { type: Schema.JSON }, + extra: Object + }); + + var newuser = new User({settings: {hey: 'you'}}); + test.ok(newuser.settings); + + Post = dataSource.define('Post', { + title: { type: String, length: 255, index: true }, + subject: { type: String }, + content: { type: Text }, + date: { type: Date, default: function () { + return new Date + }, index: true }, + published: { type: Boolean, default: false, index: true }, + likes: [], + related: [RelatedPost] + }, {table: 'posts'}); + + function RelatedPost() { + } + + RelatedPost.prototype.someMethod = function () { + return this.parent; + }; + + Post.validateAsync('title', function (err, done) { + process.nextTick(done); + }); + + User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); + // creates instance methods: + // user.posts(conds) + // user.posts.build(data) // like new Post({userId: user.id}); + // user.posts.create(data) // build and save + // user.posts.find + + // User.hasOne('latestPost', {model: Post, foreignKey: 'postId'}); + + // User.hasOne(Post, {as: 'latestPost', foreignKey: 'latestPostId'}); + // creates instance methods: + // user.latestPost() + // user.latestPost.build(data) + // user.latestPost.create(data) + + Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); + // creates instance methods: + // post.author(callback) -- getter when called with function + // post.author() -- sync getter when called without params + // post.author(user) -- setter when called with object + + Passport = dataSource.define('Passport', { + number: String + }); + + Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'}); + User.hasMany(Passport, {as: 'passports', foreignKey: 'ownerId'}); + + var user = new User; + + test.ok(User instanceof Function); + + // class methods + test.ok(User.find instanceof Function); + test.ok(User.create instanceof Function); + + // instance methods + test.ok(user.save instanceof Function); + + dataSource.automigrate(function (err) { + if (err) { + console.log('Error while migrating'); + console.log(err); + } else { test.done(); + } }); - it('should save object', function (test) { - var title = 'Initial title', title2 = 'Hello world', - date = new Date; + }); - Post.create({ - title: title, - date: date - }, function (err, obj) { - test.ok(obj.id, 'Object id should present'); - test.equals(obj.title, title); - // test.equals(obj.date, date); - obj.title = title2; - test.ok(obj.propertyChanged('title'), 'Title changed'); - obj.save(function (err, obj) { - test.equal(obj.title, title2); - test.ok(!obj.propertyChanged('title')); + it('should initialize object properly', function (test) { + var hw = 'Hello word', + now = Date.now(), + post = new Post({title: hw}), + anotherPost = Post({title: 'Resig style constructor'}); - var p = new Post({title: 1}); - p.title = 2; - p.save(function (err, obj) { - test.ok(!p.propertyChanged('title')); - p.title = 3; - test.ok(p.propertyChanged('title')); - test.equal(p.title_was, 2); - p.save(function () { - test.equal(p.title_was, 3); - test.ok(!p.propertyChanged('title')); - test.done(); - }); - }); - }); - }); - }); + test.equal(post.title, hw); + test.ok(!post.propertyChanged('title'), 'property changed: title'); + post.title = 'Goodbye, Lenin'; + test.equal(post.title_was, hw); + test.ok(post.propertyChanged('title')); + test.strictEqual(post.published, false); + test.ok(post.date >= now); + test.ok(post.isNewRecord()); + test.ok(anotherPost instanceof Post); + test.ok(anotherPost.title, 'Resig style constructor'); + test.done(); + }); - it('should create object with initial data', function (test) { - var title = 'Initial title', - date = new Date; + it('should save object', function (test) { + var title = 'Initial title', title2 = 'Hello world', + date = new Date; - Post.create({ - title: title, - date: date - }, function (err, obj) { - test.ok(obj.id); - test.equals(obj.title, title); - test.equals(obj.date, date); - Post.findById(obj.id, function () { - test.equal(obj.title, title); - test.equal(obj.date.toString(), date.toString()); - test.done(); - }); - }); - }); + Post.create({ + title: title, + date: date + }, function (err, obj) { + test.ok(obj.id, 'Object id should present'); + test.equals(obj.title, title); + // test.equals(obj.date, date); + obj.title = title2; + test.ok(obj.propertyChanged('title'), 'Title changed'); + obj.save(function (err, obj) { + test.equal(obj.title, title2); + test.ok(!obj.propertyChanged('title')); - it('should save only dataSource-defined field in database', function (test) { - Post.create({title: '1602', nonSchemaField: 'some value'}, function (err, post) { - test.ok(!post.nonSchemaField); - post.a = 1; - post.save(function () { - test.ok(post.a); - post.reload(function (err, psto) { - test.ok(!psto.a); - test.done(); - }); - }); - }); - }); - - /* - it('should not create new instances for the same object', function (test) { - var title = 'Initial title'; - Post.create({ title: title }, function (err, post) { - test.ok(post.id, 'Object should have id'); - test.equals(post.title, title); - Post.findById(post.id, function (err, foundPost) { - if (err) throw err; - test.equal(post.title, title); - test.strictEqual(post, foundPost); - test.done(); - }); - }); - }); - */ - - it('should not re-instantiate object on saving', function (test) { - var title = 'Initial title'; - var post = new Post({title: title}); - post.save(function (err, savedPost) { - test.strictEqual(post, savedPost); + var p = new Post({title: 1}); + p.title = 2; + p.save(function (err, obj) { + test.ok(!p.propertyChanged('title')); + p.title = 3; + test.ok(p.propertyChanged('title')); + test.equal(p.title_was, 2); + p.save(function () { + test.equal(p.title_was, 3); + test.ok(!p.propertyChanged('title')); test.done(); + }); }); + }); }); + }); - it('should destroy object', function (test) { - Post.create(function (err, post) { - Post.exists(post.id, function (err, exists) { - test.ok(exists, 'Object exists'); - post.destroy(function () { - Post.exists(post.id, function (err, exists) { - if (err) console.log(err); - test.ok(!exists, 'Hey! ORM told me that object exists, but it looks like it doesn\'t. Something went wrong...'); - Post.findById(post.id, function (err, obj) { - test.equal(obj, null, 'Param obj should be null'); - test.done(); - }); - }); - }); - }); - }); - }); + it('should create object with initial data', function (test) { + var title = 'Initial title', + date = new Date; - it('should handle virtual attributes', function (test) { - var salt = 's0m3s3cr3t5a1t'; - - User.setter.passwd = function (password) { - this._passwd = calcHash(password, salt); - }; - - function calcHash(pass, salt) { - var crypto = require('crypto'); - var hash = crypto.createHash('sha256'); - hash.update(pass); - hash.update(salt); - return hash.digest('base64'); - } - - var u = new User; - u.passwd = 's3cr3t'; - test.equal(u.passwd, calcHash('s3cr3t', salt)); + Post.create({ + title: title, + date: date + }, function (err, obj) { + test.ok(obj.id); + test.equals(obj.title, title); + test.equals(obj.date, date); + Post.findById(obj.id, function () { + test.equal(obj.title, title); + test.equal(obj.date.toString(), date.toString()); test.done(); + }); }); + }); - // it('should serialize JSON type', function (test) { - // User.create({settings: {hello: 'world'}}, function (err, user) { - // test.ok(user.id); - // test.equal(user.settings.hello, 'world'); - // User.find(user.id, function (err, u) { - // console.log(u.settings); - // test.equal(u.settings.hello, 'world'); - // test.done(); - // }); - // }); - // }); + it('should save only dataSource-defined field in database', function (test) { + Post.create({title: '1602', nonSchemaField: 'some value'}, function (err, post) { + test.ok(!post.nonSchemaField); + post.a = 1; + post.save(function () { + test.ok(post.a); + post.reload(function (err, psto) { + test.ok(!psto.a); + test.done(); + }); + }); + }); + }); - it('should update single attribute', function (test) { - Post.create({title: 'title', content: 'content', published: true}, function (err, post) { - post.content = 'New content'; - post.updateAttribute('title', 'New title', function () { - test.equal(post.title, 'New title'); - test.ok(!post.propertyChanged('title')); - test.equal(post.content, 'New content', 'dirty state saved'); - test.ok(post.propertyChanged('content')); - post.reload(function (err, post) { - test.equal(post.title, 'New title'); - test.ok(!post.propertyChanged('title'), 'title not changed'); - test.equal(post.content, 'content', 'real value turned back'); - test.ok(!post.propertyChanged('content'), 'content unchanged'); - test.done(); - }); + /* + it('should not create new instances for the same object', function (test) { + var title = 'Initial title'; + Post.create({ title: title }, function (err, post) { + test.ok(post.id, 'Object should have id'); + test.equals(post.title, title); + Post.findById(post.id, function (err, foundPost) { + if (err) throw err; + test.equal(post.title, title); + test.strictEqual(post, foundPost); + test.done(); + }); + }); + }); + */ + + it('should not re-instantiate object on saving', function (test) { + var title = 'Initial title'; + var post = new Post({title: title}); + post.save(function (err, savedPost) { + test.strictEqual(post, savedPost); + test.done(); + }); + }); + + it('should destroy object', function (test) { + Post.create(function (err, post) { + Post.exists(post.id, function (err, exists) { + test.ok(exists, 'Object exists'); + post.destroy(function () { + Post.exists(post.id, function (err, exists) { + if (err) console.log(err); + test.ok(!exists, 'Hey! ORM told me that object exists, but it looks like it doesn\'t. Something went wrong...'); + Post.findById(post.id, function (err, obj) { + test.equal(obj, null, 'Param obj should be null'); + test.done(); }); + }); }); + }); }); + }); - var countOfposts, countOfpostsFiltered; - it('should fetch collection', function (test) { - Post.all(function (err, posts) { - countOfposts = posts.length; - test.ok(countOfposts > 0); - test.ok(posts[0] instanceof Post); - countOfpostsFiltered = posts.filter(function (p) { - return p.title === 'title'; - }).length; - test.done(); + it('should handle virtual attributes', function (test) { + var salt = 's0m3s3cr3t5a1t'; + + User.setter.passwd = function (password) { + this._passwd = calcHash(password, salt); + }; + + function calcHash(pass, salt) { + var crypto = require('crypto'); + var hash = crypto.createHash('sha256'); + hash.update(pass); + hash.update(salt); + return hash.digest('base64'); + } + + var u = new User; + u.passwd = 's3cr3t'; + test.equal(u.passwd, calcHash('s3cr3t', salt)); + test.done(); + }); + + // it('should serialize JSON type', function (test) { + // User.create({settings: {hello: 'world'}}, function (err, user) { + // test.ok(user.id); + // test.equal(user.settings.hello, 'world'); + // User.find(user.id, function (err, u) { + // console.log(u.settings); + // test.equal(u.settings.hello, 'world'); + // test.done(); + // }); + // }); + // }); + + it('should update single attribute', function (test) { + Post.create({title: 'title', content: 'content', published: true}, function (err, post) { + post.content = 'New content'; + post.updateAttribute('title', 'New title', function () { + test.equal(post.title, 'New title'); + test.ok(!post.propertyChanged('title')); + test.equal(post.content, 'New content', 'dirty state saved'); + test.ok(post.propertyChanged('content')); + post.reload(function (err, post) { + test.equal(post.title, 'New title'); + test.ok(!post.propertyChanged('title'), 'title not changed'); + test.equal(post.content, 'content', 'real value turned back'); + test.ok(!post.propertyChanged('content'), 'content unchanged'); + test.done(); }); + }); }); + }); - it('should find records filtered with multiple attributes', function (test) { - var d = new Date; - Post.create({title: 'title', content: 'content', published: true, date: d}, function (err, post) { - Post.all({where: {title: 'title', date: d, published: true}}, function (err, res) { - test.equals(res.length, 1, 'Filtering Posts returns one post'); - test.done(); - }); - }); + var countOfposts, countOfpostsFiltered; + it('should fetch collection', function (test) { + Post.all(function (err, posts) { + countOfposts = posts.length; + test.ok(countOfposts > 0); + test.ok(posts[0] instanceof Post); + countOfpostsFiltered = posts.filter(function (p) { + return p.title === 'title'; + }).length; + test.done(); }); + }); - if ( - !dataSource.name.match(/redis/) && - dataSource.name !== 'memory' && - dataSource.name !== 'neo4j' && - dataSource.name !== 'cradle' - ) + it('should find records filtered with multiple attributes', function (test) { + var d = new Date; + Post.create({title: 'title', content: 'content', published: true, date: d}, function (err, post) { + Post.all({where: {title: 'title', date: d, published: true}}, function (err, res) { + test.equals(res.length, 1, 'Filtering Posts returns one post'); + test.done(); + }); + }); + }); + + if ( + !dataSource.name.match(/redis/) && + dataSource.name !== 'memory' && + dataSource.name !== 'neo4j' && + dataSource.name !== 'cradle' + ) it('relations key is working', function (test) { - test.ok(User.relations, 'Relations key should be defined'); - test.ok(User.relations.posts, 'posts relation should exist on User'); - test.equal(User.relations.posts.type, 'hasMany', 'Type of hasMany relation is hasMany'); - test.equal(User.relations.posts.multiple, true, 'hasMany relations are multiple'); - test.equal(User.relations.posts.keyFrom, 'id', 'keyFrom is primary key of model table'); - test.equal(User.relations.posts.keyTo, 'userId', 'keyTo is foreign key of related model table'); + test.ok(User.relations, 'Relations key should be defined'); + test.ok(User.relations.posts, 'posts relation should exist on User'); + test.equal(User.relations.posts.type, 'hasMany', 'Type of hasMany relation is hasMany'); + test.equal(User.relations.posts.multiple, true, 'hasMany relations are multiple'); + test.equal(User.relations.posts.keyFrom, 'id', 'keyFrom is primary key of model table'); + test.equal(User.relations.posts.keyTo, 'userId', 'keyTo is foreign key of related model table'); - test.ok(Post.relations, 'Relations key should be defined'); - test.ok(Post.relations.author, 'author relation should exist on Post'); - test.equal(Post.relations.author.type, 'belongsTo', 'Type of belongsTo relation is belongsTo'); - test.equal(Post.relations.author.multiple, false, 'belongsTo relations are not multiple'); - test.equal(Post.relations.author.keyFrom, 'userId', 'keyFrom is foreign key of model table'); - test.equal(Post.relations.author.keyTo, 'id', 'keyTo is primary key of related model table'); - test.done(); + test.ok(Post.relations, 'Relations key should be defined'); + test.ok(Post.relations.author, 'author relation should exist on Post'); + test.equal(Post.relations.author.type, 'belongsTo', 'Type of belongsTo relation is belongsTo'); + test.equal(Post.relations.author.multiple, false, 'belongsTo relations are not multiple'); + test.equal(Post.relations.author.keyFrom, 'userId', 'keyFrom is foreign key of model table'); + test.equal(Post.relations.author.keyTo, 'id', 'keyTo is primary key of related model table'); + test.done(); }); - - it('should handle hasMany relationship', function (test) { - User.create(function (err, u) { - if (err) return console.log(err); - test.ok(u.posts, 'Method defined: posts'); - test.ok(u.posts.build, 'Method defined: posts.build'); - test.ok(u.posts.create, 'Method defined: posts.create'); - u.posts.create(function (err, post) { - if (err) return console.log(err); - u.posts(function (err, posts) { - test.equal(posts.pop().id.toString(), post.id.toString()); - test.done(); - }); - }); + it('should handle hasMany relationship', function (test) { + User.create(function (err, u) { + if (err) return console.log(err); + test.ok(u.posts, 'Method defined: posts'); + test.ok(u.posts.build, 'Method defined: posts.build'); + test.ok(u.posts.create, 'Method defined: posts.create'); + u.posts.create(function (err, post) { + if (err) return console.log(err); + u.posts(function (err, posts) { + test.equal(posts.pop().id.toString(), post.id.toString()); + test.done(); }); + }); }); + }); - it('should navigate variations of belongsTo regardless of column name', function(test){ + it('should navigate variations of belongsTo regardless of column name', function (test) { - Dog.create({name: 'theDog'}, function(err, obj){ - test.ok(obj instanceof Dog); - Log.create({name: 'theLog', ownerId: obj.id}, function(err, obj){ - test.ok(obj instanceof Log); - obj.owner(function(err, obj){ - test.ok(!err, 'Should not have an error.'); // Before cba174b this would be 'Error: Permission denied' - if(err){ - console.log('Found: ' + err); - } - test.ok(obj, 'Should not find null or undefined.'); // Before cba174b this could be null or undefined. - test.ok(obj instanceof Dog, 'Should find a Dog.'); - if(obj){ // Since test won't stop on fail, have to check before accessing obj.name. - test.ok(obj.name, 'Should have a name.'); - } - if(obj && obj.name){ - test.equal(obj.name, 'theDog', 'The owner of theLog is theDog.'); - } - test.done(); - }); - }); + Dog.create({name: 'theDog'}, function (err, obj) { + test.ok(obj instanceof Dog); + Log.create({name: 'theLog', ownerId: obj.id}, function (err, obj) { + test.ok(obj instanceof Log); + obj.owner(function (err, obj) { + test.ok(!err, 'Should not have an error.'); // Before cba174b this would be 'Error: Permission denied' + if (err) { + console.log('Found: ' + err); + } + test.ok(obj, 'Should not find null or undefined.'); // Before cba174b this could be null or undefined. + test.ok(obj instanceof Dog, 'Should find a Dog.'); + if (obj) { // Since test won't stop on fail, have to check before accessing obj.name. + test.ok(obj.name, 'Should have a name.'); + } + if (obj && obj.name) { + test.equal(obj.name, 'theDog', 'The owner of theLog is theDog.'); + } + test.done(); }); + }); }); + }); - it('hasMany should support additional conditions', function (test) { + it('hasMany should support additional conditions', function (test) { - User.create(function (e, u) { - u.posts.create({}, function (e, p) { - u.posts({where: {id: p.id}}, function (e, posts) { - test.equal(posts.length, 1, 'There should be only 1 post.'); - test.done(); - }); - }); + User.create(function (e, u) { + u.posts.create({}, function (e, p) { + u.posts({where: {id: p.id}}, function (e, posts) { + test.equal(posts.length, 1, 'There should be only 1 post.'); + test.done(); }); - + }); }); - it('hasMany should be cached', function (test) { - //User.create(function (e, u) { - // u.posts.create({}, function (e, p) { - // find all posts for a user. - // Finding one post with an existing author associated - Post.all(function (err, posts) { - // We try to get the first post with a userId != NULL - for (var i = 0; i < posts.length; i++) { - var post = posts[i]; - if (post.userId) { - // We could get the user with belongs to relationship but it is better if there is no interactions. - User.findById(post.userId, function(err, user) { - User.create(function(err, voidUser) { - Post.create({userId: user.id}, function() { + }); + + it('hasMany should be cached', function (test) { + //User.create(function (e, u) { + // u.posts.create({}, function (e, p) { + // find all posts for a user. + // Finding one post with an existing author associated + Post.all(function (err, posts) { + // We try to get the first post with a userId != NULL + for (var i = 0; i < posts.length; i++) { + var post = posts[i]; + if (post.userId) { + // We could get the user with belongs to relationship but it is better if there is no interactions. + User.findById(post.userId, function (err, user) { + User.create(function (err, voidUser) { + Post.create({userId: user.id}, function () { + + // There can't be any concurrency because we are counting requests + // We are first testing cases when user has posts + user.posts(function (err, data) { + var nbInitialRequests = nbSchemaRequests; + user.posts(function (err, data2) { + test.equal(data.length, 2, 'There should be 2 posts.'); + test.equal(data.length, data2.length, 'Posts should be the same, since we are loading on the same object.'); + requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); + + if (dataSource.name === 'mongodb') { // for the moment mongodb doesn\'t support additional conditions on hasMany relations (see above) + test.done(); + } else { + user.posts({where: {id: data[0].id}}, function (err, data) { + test.equal(data.length, 1, 'There should be only one post.'); + requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we added conditions.'); + + user.posts(function (err, data) { + test.equal(data.length, 2, 'Previous get shouldn\'t have changed cached value though, since there was additional conditions.'); + requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should not be any request because value is cached.'); + + // We are now testing cases when user doesn't have any post + voidUser.posts(function (err, data) { + var nbInitialRequests = nbSchemaRequests; + voidUser.posts(function (err, data2) { + test.equal(data.length, 0, 'There shouldn\'t be any posts (1/2).'); + test.equal(data2.length, 0, 'There shouldn\'t be any posts (2/2).'); + requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); + + voidUser.posts(true, function (err, data3) { + test.equal(data3.length, 0, 'There shouldn\'t be any posts.'); + requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we forced refresh.'); - // There can't be any concurrency because we are counting requests - // We are first testing cases when user has posts - user.posts(function(err, data) { - var nbInitialRequests = nbSchemaRequests; - user.posts(function(err, data2) { - test.equal(data.length, 2, 'There should be 2 posts.'); - test.equal(data.length, data2.length, 'Posts should be the same, since we are loading on the same object.'); - requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); - - if (dataSource.name === 'mongodb') { // for the moment mongodb doesn\'t support additional conditions on hasMany relations (see above) - test.done(); - } else { - user.posts({where: {id: data[0].id}}, function(err, data) { - test.equal(data.length, 1, 'There should be only one post.'); - requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we added conditions.'); - - user.posts(function(err, data) { - test.equal(data.length, 2, 'Previous get shouldn\'t have changed cached value though, since there was additional conditions.'); - requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should not be any request because value is cached.'); - - // We are now testing cases when user doesn't have any post - voidUser.posts(function(err, data) { - var nbInitialRequests = nbSchemaRequests; - voidUser.posts(function(err, data2) { - test.equal(data.length, 0, 'There shouldn\'t be any posts (1/2).'); - test.equal(data2.length, 0, 'There shouldn\'t be any posts (2/2).'); - requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); - - voidUser.posts(true, function(err, data3) { - test.equal(data3.length, 0, 'There shouldn\'t be any posts.'); - requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we forced refresh.'); - - test.done(); - }); - }); - }); - - }); - }); - } - - }); - }); - - }); - }); - }); - break; - } - } - }); - - }); - - // it('should handle hasOne relationship', function (test) { - // User.create(function (err, u) { - // if (err) return console.log(err); - // }); - // }); - - it('should support scopes', function (test) { - var wait = 2; - - test.ok(Post.scope, 'Scope supported'); - Post.scope('published', {where: {published: true}}); - test.ok(typeof Post.published === 'function'); - test.ok(Post.published._scope.where.published === true); - var post = Post.published.build(); - test.ok(post.published, 'Can build'); - test.ok(post.isNewRecord()); - Post.published.create(function (err, psto) { - if (err) return console.log(err); - test.ok(psto.published); - test.ok(!psto.isNewRecord()); - done(); - }); - - User.create(function (err, u) { - if (err) return console.log(err); - test.ok(typeof u.posts.published == 'function'); - test.ok(u.posts.published._scope.where.published); - console.log(u.posts.published._scope); - test.equal(u.posts.published._scope.where.userId, u.id); - done(); - }); - - function done() { - if (--wait === 0) test.done(); - }; - }); - - it('should return type of property', function (test) { - test.equal(Post.getPropertyType('title'), 'String'); - test.equal(Post.getPropertyType('content'), 'Text'); - var p = new Post; - test.equal(p.getPropertyType('title'), 'String'); - test.equal(p.getPropertyType('content'), 'Text'); - test.done(); - }); - - it('should handle ORDER clause', function (test) { - var titles = [ { title: 'Title A', subject: "B" }, - { title: 'Title Z', subject: "A" }, - { title: 'Title M', subject: "C" }, - { title: 'Title A', subject: "A" }, - { title: 'Title B', subject: "A" }, - { title: 'Title C', subject: "D" }]; - var isRedis = Post.dataSource.name === 'redis'; - var dates = isRedis ? [ 5, 9, 0, 17, 10, 9 ] : [ - new Date(1000 * 5 ), - new Date(1000 * 9), - new Date(1000 * 0), - new Date(1000 * 17), - new Date(1000 * 10), - new Date(1000 * 9) - ]; - titles.forEach(function (t, i) { - Post.create({title: t.title, subject: t.subject, date: dates[i]}, done); - }); - - var i = 0, tests = 0; - function done(err, obj) { - if (++i === titles.length) { - doFilterAndSortTest(); - doFilterAndSortReverseTest(); - doStringTest(); - doNumberTest(); - - if (dataSource.name == 'mongoose') { - doMultipleSortTest(); - doMultipleReverseSortTest(); - } - } - } - - function compare(a, b) { - if (a.title < b.title) return -1; - if (a.title > b.title) return 1; - return 0; - } - - // Post.dataSource.log = console.log; - - function doStringTest() { - tests += 1; - Post.all({order: 'title'}, function (err, posts) { - if (err) console.log(err); - test.equal(posts.length, 6); - titles.sort(compare).forEach(function (t, i) { - if (posts[i]) test.equal(posts[i].title, t.title); - }); - finished(); - }); - } - - function doNumberTest() { - tests += 1; - Post.all({order: 'date'}, function (err, posts) { - if (err) console.log(err); - test.equal(posts.length, 6); - dates.sort(numerically).forEach(function (d, i) { - if (posts[i]) - test.equal(posts[i].date.toString(), d.toString(), 'doNumberTest'); - }); - finished(); - }); - } - - function doFilterAndSortTest() { - tests += 1; - Post.all({where: {date: new Date(1000 * 9)}, order: 'title', limit: 3}, function (err, posts) { - if (err) console.log(err); - console.log(posts.length); - test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); - [ 'Title C', 'Title Z' ].forEach(function (t, i) { - if (posts[i]) { - test.equal(posts[i].title, t, 'doFilterAndSortTest'); - } - }); - finished(); - }); - } - - function doFilterAndSortReverseTest() { - tests += 1; - Post.all({where: {date: new Date(1000 * 9)}, order: 'title DESC', limit: 3}, function (err, posts) { - if (err) console.log(err); - test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); - [ 'Title Z', 'Title C' ].forEach(function (t, i) { - if (posts[i]) { - test.equal(posts[i].title, t, 'doFilterAndSortReverseTest'); - } - }); - finished(); - }); - } - - function doMultipleSortTest() { - tests += 1; - Post.all({order: "title ASC, subject ASC"}, function(err, posts) { - if (err) console.log(err); - test.equal(posts.length, 6); - test.equal(posts[0].title, "Title A"); - test.equal(posts[0].subject, "A"); - test.equal(posts[1].title, "Title A"); - test.equal(posts[1].subject, "B"); - test.equal(posts[5].title, "Title Z"); - finished(); - }); - } - - function doMultipleReverseSortTest() { - tests += 1; - Post.all({order: "title ASC, subject DESC"}, function(err, posts) { - if (err) console.log(err); - test.equal(posts.length, 6); - test.equal(posts[0].title, "Title A"); - test.equal(posts[0].subject, "B"); - test.equal(posts[1].title,"Title A"); - test.equal(posts[1].subject, "A"); - test.equal(posts[5].title, "Title Z"); - finished(); - }); - } - - var fin = 0; - function finished() { - if (++fin === tests) { - test.done(); - } - } - - // TODO: do mixed test, do real dates tests, ensure that dates stored in UNIX timestamp format - - function numerically(a, b) { - return a - b; - } - - }); - - // if ( - // !dataSource.name.match(/redis/) && - // dataSource.name !== 'memory' && - // dataSource.name !== 'neo4j' && - // dataSource.name !== 'cradle' && - // dataSource.name !== 'nano' - // ) - // it('should allow advanced queying: lt, gt, lte, gte, between', function (test) { - // Post.destroyAll(function () { - // Post.create({date: new Date('Wed, 01 Feb 2012 13:56:12 GMT')}, done); - // Post.create({date: new Date('Thu, 02 Feb 2012 13:56:12 GMT')}, done); - // Post.create({date: new Date('Fri, 03 Feb 2012 13:56:12 GMT')}, done); - // Post.create({date: new Date('Sat, 04 Feb 2012 13:56:12 GMT')}, done); - // Post.create({date: new Date('Sun, 05 Feb 2012 13:56:12 GMT')}, done); - // Post.create({date: new Date('Mon, 06 Feb 2012 13:56:12 GMT')}, done); - // Post.create({date: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}, done); - // Post.create({date: new Date('Wed, 08 Feb 2012 13:56:12 GMT')}, done); - // Post.create({date: new Date('Thu, 09 Feb 2012 13:56:12 GMT')}, done); - // }); - - // var posts = 9; - // function done() { - // if (--posts === 0) makeTest(); - // } - - // function makeTest() { - // // gt - // Post.all({where: {date: {gt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { - // test.equal(posts.length, 2, 'gt'); - // ok(); - // }); - - // // gte - // Post.all({where: {date: {gte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { - // test.equal(posts.length, 3, 'gte'); - // ok(); - // }); - - // // lte - // Post.all({where: {date: {lte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { - // test.equal(posts.length, 7, 'lte'); - // ok(); - // }); - - // // lt - // Post.all({where: {date: {lt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { - // test.equal(posts.length, 6, 'lt'); - // ok(); - // }); - - // // between - // Post.all({where: {date: {between: [new Date('Tue, 05 Feb 2012 13:56:12 GMT'), new Date('Tue, 09 Feb 2012 13:56:12 GMT')]}}}, function (err, posts) { - // test.equal(posts.length, 5, 'between'); - // ok(); - // }); - // } - - // var tests = 5; - // function ok() { - // if (--tests === 0) test.done(); - // } - // }); - - - // if ( - // dataSource.name === 'mysql' || - // dataSource.name === 'postgres' - // ) - // it('should allow IN or NOT IN', function (test) { - // User.destroyAll(function () { - // User.create({name: 'User A', age: 21}, done); - // User.create({name: 'User B', age: 22}, done); - // User.create({name: 'User C', age: 23}, done); - // User.create({name: 'User D', age: 24}, done); - // User.create({name: 'User E', age: 25}, done); - // }); - - // var users = 5; - // function done() { - // if (--users === 0) makeTest(); - // } - - // function makeTest() { - // // IN with empty array should return nothing - // User.all({where: {name: {inq: []}}}, function (err, users) { - // test.equal(users.length, 0, 'IN with empty array returns nothing'); - // ok(); - // }); - - // // NOT IN with empty array should return everything - // User.all({where: {name: {nin: []}}}, function (err, users) { - // test.equal(users.length, 5, 'NOT IN with empty array returns everything'); - // ok(); - // }); - - // // IN [User A] returns user with name = User A - // User.all({where: {name: {inq: ['User A']}}}, function (err, users) { - // test.equal(users.length, 1, 'IN searching one existing value returns 1 user'); - // test.equal(users[0].name, 'User A', 'IN [User A] returns user with name = User A'); - // ok(); - // }); - - // // NOT IN [User A] returns users with name != User A - // User.all({where: {name: {nin: ['User A']}}}, function (err, users) { - // test.equal(users.length, 4, 'IN [User A] returns users with name != User A'); - // ok(); - // }); - - // // IN [User A, User B] returns users with name = User A OR name = User B - // User.all({where: {name: {inq: ['User A', 'User B']}}}, function (err, users) { - // test.equal(users.length, 2, 'IN searching two existing values returns 2 users'); - // ok(); - // }); - - // // NOT IN [User A, User B] returns users with name != User A AND name != User B - // User.all({where: {name: {nin: ['User A', 'User B']}}}, function (err, users) { - // test.equal(users.length, 3, 'NOT IN searching two existing values returns users with name != User A AND name != User B'); - // ok(); - // }); - - // // IN works with numbers too - // User.all({where: {age: {inq: [21, 22]}}}, function (err, users) { - // test.equal(users.length, 2, 'IN works with numbers too'); - // ok(); - // }); - - // // NOT IN works with numbers too - // User.all({where: {age: {nin: [21, 22]}}}, function (err, users) { - // test.equal(users.length, 3, 'NOT IN works with numbers too'); - // ok(); - // }); - // } - - // var tests = 8; - // function ok() { - // if (--tests === 0) test.done(); - // } - // }); - - it('should handle order clause with direction', function (test) { - var wait = 0; - var emails = [ - 'john@hcompany.com', - 'tom@hcompany.com', - 'admin@hcompany.com', - 'tin@hcompany.com', - 'mike@hcompany.com', - 'susan@hcompany.com', - 'test@hcompany.com' - ]; - User.destroyAll(function () { - emails.forEach(function (email) { - wait += 1; - User.create({email: email, name: 'Nick'}, done); - }); - }); - var tests = 2; - function done() { - process.nextTick(function () { - if (--wait === 0) { - doSortTest(); - doReverseSortTest(); - } - }); - } - - function doSortTest() { - User.all({order: 'email ASC', where: {name: 'Nick'}}, function (err, users) { - var _emails = emails.sort(); - users.forEach(function (user, i) { - test.equal(_emails[i], user.email, 'ASC sorting'); - }); - testDone(); - }); - } - - function doReverseSortTest() { - User.all({order: 'email DESC', where: {name: 'Nick'}}, function (err, users) { - var _emails = emails.sort().reverse(); - users.forEach(function (user, i) { - test.equal(_emails[i], user.email, 'DESC sorting'); - }); - testDone(); - }); - } - - function testDone() { - if (--tests === 0) test.done(); - } - }); - - it('should return id in find result even after updateAttributes', function (test) { - Post.create(function (err, post) { - var id = post.id; - test.ok(post.published === false); - post.updateAttributes({title: 'hey', published: true}, function () { - Post.find(id, function (err, post) { - test.ok(!!post.published, 'Update boolean field'); - test.ok(post.id); - test.done(); - }); - }); - }); - }); - - it('should handle belongsTo correctly', function (test) { - var passport = new Passport({ownerId: 16}); - // sync getter - test.equal(passport.owner(), 16); - // sync setter - passport.owner(18); - test.equal(passport.owner(), 18); - test.done(); - }); - - it('should query one record', function (test) { - test.expect(4); - Post.findOne(function (err, post) { - test.ok(post && post.id); - Post.findOne({ where: { title: 'hey' } }, function (err, post) { - if (err) { - console.log(err); - return test.done(); - } - test.equal(post && post.constructor.modelName, 'Post'); - test.equal(post && post.title, 'hey'); - Post.findOne({ where: { title: 'not exists' } }, function (err, post) { - test.ok(post === null); - test.done(); - }); - }); - }); - }); - - // if ( - // !dataSource.name.match(/redis/) && - // dataSource.name !== 'memory' && - // dataSource.name !== 'neo4j' && - // dataSource.name !== 'cradle' && - // dataSource.name !== 'nano' - // ) - // it('belongsTo should be cached', function (test) { - // User.findOne(function(err, user) { - - // var passport = new Passport({ownerId: user.id}); - // var passport2 = new Passport({ownerId: null}); - - // // There can't be any concurrency because we are counting requests - // // We are first testing cases when passport has an owner - // passport.owner(function(err, data) { - // var nbInitialRequests = nbSchemaRequests; - // passport.owner(function(err, data2) { - // test.equal(data.id, data2.id, 'The value should remain the same'); - // requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); - - // // We are now testing cases when passport has not an owner - // passport2.owner(function(err, data) { - // var nbInitialRequests2 = nbSchemaRequests; - // passport2.owner(function(err, data2) { - // test.equal(data, null, 'The value should be null since there is no owner'); - // test.equal(data, data2, 'The value should remain the same (null)'); - // requestsAreCounted && test.equal(nbInitialRequests2, nbSchemaRequests, 'There should not be any request because value is cached.'); - - // passport2.owner(user.id); - // passport2.owner(function(err, data3) { - // test.equal(data3.id, user.id, 'Owner should now be the user.'); - // requestsAreCounted && test.equal(nbInitialRequests2 + 1, nbSchemaRequests, 'If we changed owner id, there should be one more request.'); - - // passport2.owner(true, function(err, data4) { - // test.equal(data3.id, data3.id, 'The value should remain the same'); - // requestsAreCounted && test.equal(nbInitialRequests2 + 2, nbSchemaRequests, 'If we forced refreshing, there should be one more request.'); - // test.done(); - // }); - // }); - // }); - // }); - - // }); - // }); - // }); - - // }); - - if (dataSource.name !== 'mongoose' && dataSource.name !== 'neo4j') - it('should update or create record', function (test) { - var newData = { - id: 1, - title: 'New title (really new)', - content: 'Some example content (updated)' - }; - Post.updateOrCreate(newData, function (err, updatedPost) { - if (err) throw err; - test.ok(updatedPost); - if (!updatedPost) throw Error('No post!'); - - if (dataSource.name !== 'mongodb') { - test.equal(newData.id, updatedPost.toObject().id); - } - test.equal(newData.title, updatedPost.toObject().title); - test.equal(newData.content, updatedPost.toObject().content); - - Post.findById(updatedPost.id, function (err, post) { - if (err) throw err; - if (!post) throw Error('No post!'); - if (dataSource.name !== 'mongodb') { - test.equal(newData.id, post.toObject().id); - } - test.equal(newData.title, post.toObject().title); - test.equal(newData.content, post.toObject().content); - Post.updateOrCreate({id: 100001, title: 'hey'}, function (err, post) { - if (dataSource.name !== 'mongodb') test.equal(post.id, 100001); - test.equal(post.title, 'hey'); - Post.findById(post.id, function (err, post) { - if (!post) throw Error('No post!'); - test.done(); - }); - }); - }); - }); - }); - - it('should work with custom setters and getters', function (test) { - User.dataSource.defineForeignKey('User', 'passwd'); - User.setter.passwd = function (pass) { - this._passwd = pass + 'salt'; - }; - var u = new User({passwd: 'qwerty'}); - test.equal(u.passwd, 'qwertysalt'); - u.save(function (err, user) { - User.findById(user.id, function (err, user) { - test.ok(user !== u); - test.equal(user.passwd, 'qwertysalt'); - User.all({where: {passwd: 'qwertysalt'}}, function (err, users) { - test.ok(users[0] !== user); - test.equal(users[0].passwd, 'qwertysalt'); - User.create({passwd: 'asalat'}, function (err, usr) { - test.equal(usr.passwd, 'asalatsalt'); - User.upsert({passwd: 'heyman'}, function (err, us) { - test.equal(us.passwd, 'heymansalt'); - User.findById(us.id, function (err, user) { - test.equal(user.passwd, 'heymansalt'); test.done(); + }); }); + }); + }); - }); + }); + } + + }); }); + + }); }); - }); + }); + break; + } + } }); - it('should work with typed and untyped nested collections', function (test) { - var post = new Post; - var like = post.likes.push({foo: 'bar'}); - test.equal(like.constructor.name, 'ListItem'); - var related = post.related.push({hello: 'world'}); - test.ok(related.someMethod); - post.save(function (err, p) { - test.equal(p.likes.nextid, 2); - p.likes.push({second: 2}); - p.likes.push({third: 3}); - p.save(function (err) { - Post.findById(p.id, function (err, pp) { - test.equal(pp.likes.length, 3); - test.ok(pp.likes[3].third); - test.ok(pp.likes[2].second); - test.ok(pp.likes[1].foo); - pp.likes.remove(2); - test.equal(pp.likes.length, 2); - test.ok(!pp.likes[2]); - pp.likes.remove(pp.likes[1]); - test.equal(pp.likes.length, 1); - test.ok(!pp.likes[1]); - test.ok(pp.likes[3]); - pp.save(function () { - Post.findById(p.id, function (err, pp) { - test.equal(pp.likes.length, 1); - test.ok(!pp.likes[1]); - test.ok(pp.likes[3]); - test.done(); - }); - }); - }); - }); - }); + }); + + // it('should handle hasOne relationship', function (test) { + // User.create(function (err, u) { + // if (err) return console.log(err); + // }); + // }); + + it('should support scopes', function (test) { + var wait = 2; + + test.ok(Post.scope, 'Scope supported'); + Post.scope('published', {where: {published: true}}); + test.ok(typeof Post.published === 'function'); + test.ok(Post.published._scope.where.published === true); + var post = Post.published.build(); + test.ok(post.published, 'Can build'); + test.ok(post.isNewRecord()); + Post.published.create(function (err, psto) { + if (err) return console.log(err); + test.ok(psto.published); + test.ok(!psto.isNewRecord()); + done(); }); - it('should find or create', function (test) { - var email = 'some email ' + Math.random(); - User.findOrCreate({where: {email: email}}, function (err, u) { - test.ok(u); - test.ok(!u.age); - User.findOrCreate({where: {email: email}}, {age: 21}, function (err, u2) { - test.equals(u.id.toString(), u2.id.toString(), 'Same user ids'); - test.ok(!u2.age); + User.create(function (err, u) { + if (err) return console.log(err); + test.ok(typeof u.posts.published == 'function'); + test.ok(u.posts.published._scope.where.published); + console.log(u.posts.published._scope); + test.equal(u.posts.published._scope.where.userId, u.id); + done(); + }); + + function done() { + if (--wait === 0) test.done(); + }; + }); + + it('should return type of property', function (test) { + test.equal(Post.getPropertyType('title'), 'String'); + test.equal(Post.getPropertyType('content'), 'Text'); + var p = new Post; + test.equal(p.getPropertyType('title'), 'String'); + test.equal(p.getPropertyType('content'), 'Text'); + test.done(); + }); + + it('should handle ORDER clause', function (test) { + var titles = [ + { title: 'Title A', subject: "B" }, + { title: 'Title Z', subject: "A" }, + { title: 'Title M', subject: "C" }, + { title: 'Title A', subject: "A" }, + { title: 'Title B', subject: "A" }, + { title: 'Title C', subject: "D" } + ]; + var isRedis = Post.dataSource.name === 'redis'; + var dates = isRedis ? [ 5, 9, 0, 17, 10, 9 ] : [ + new Date(1000 * 5), + new Date(1000 * 9), + new Date(1000 * 0), + new Date(1000 * 17), + new Date(1000 * 10), + new Date(1000 * 9) + ]; + titles.forEach(function (t, i) { + Post.create({title: t.title, subject: t.subject, date: dates[i]}, done); + }); + + var i = 0, tests = 0; + + function done(err, obj) { + if (++i === titles.length) { + doFilterAndSortTest(); + doFilterAndSortReverseTest(); + doStringTest(); + doNumberTest(); + + if (dataSource.name == 'mongoose') { + doMultipleSortTest(); + doMultipleReverseSortTest(); + } + } + } + + function compare(a, b) { + if (a.title < b.title) return -1; + if (a.title > b.title) return 1; + return 0; + } + + // Post.dataSource.log = console.log; + + function doStringTest() { + tests += 1; + Post.all({order: 'title'}, function (err, posts) { + if (err) console.log(err); + test.equal(posts.length, 6); + titles.sort(compare).forEach(function (t, i) { + if (posts[i]) test.equal(posts[i].title, t.title); + }); + finished(); + }); + } + + function doNumberTest() { + tests += 1; + Post.all({order: 'date'}, function (err, posts) { + if (err) console.log(err); + test.equal(posts.length, 6); + dates.sort(numerically).forEach(function (d, i) { + if (posts[i]) + test.equal(posts[i].date.toString(), d.toString(), 'doNumberTest'); + }); + finished(); + }); + } + + function doFilterAndSortTest() { + tests += 1; + Post.all({where: {date: new Date(1000 * 9)}, order: 'title', limit: 3}, function (err, posts) { + if (err) console.log(err); + console.log(posts.length); + test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); + [ 'Title C', 'Title Z' ].forEach(function (t, i) { + if (posts[i]) { + test.equal(posts[i].title, t, 'doFilterAndSortTest'); + } + }); + finished(); + }); + } + + function doFilterAndSortReverseTest() { + tests += 1; + Post.all({where: {date: new Date(1000 * 9)}, order: 'title DESC', limit: 3}, function (err, posts) { + if (err) console.log(err); + test.equal(posts.length, 2, 'Exactly 2 posts returned by query'); + [ 'Title Z', 'Title C' ].forEach(function (t, i) { + if (posts[i]) { + test.equal(posts[i].title, t, 'doFilterAndSortReverseTest'); + } + }); + finished(); + }); + } + + function doMultipleSortTest() { + tests += 1; + Post.all({order: "title ASC, subject ASC"}, function (err, posts) { + if (err) console.log(err); + test.equal(posts.length, 6); + test.equal(posts[0].title, "Title A"); + test.equal(posts[0].subject, "A"); + test.equal(posts[1].title, "Title A"); + test.equal(posts[1].subject, "B"); + test.equal(posts[5].title, "Title Z"); + finished(); + }); + } + + function doMultipleReverseSortTest() { + tests += 1; + Post.all({order: "title ASC, subject DESC"}, function (err, posts) { + if (err) console.log(err); + test.equal(posts.length, 6); + test.equal(posts[0].title, "Title A"); + test.equal(posts[0].subject, "B"); + test.equal(posts[1].title, "Title A"); + test.equal(posts[1].subject, "A"); + test.equal(posts[5].title, "Title Z"); + finished(); + }); + } + + var fin = 0; + + function finished() { + if (++fin === tests) { + test.done(); + } + } + + // TODO: do mixed test, do real dates tests, ensure that dates stored in UNIX timestamp format + + function numerically(a, b) { + return a - b; + } + + }); + + // if ( + // !dataSource.name.match(/redis/) && + // dataSource.name !== 'memory' && + // dataSource.name !== 'neo4j' && + // dataSource.name !== 'cradle' && + // dataSource.name !== 'nano' + // ) + // it('should allow advanced queying: lt, gt, lte, gte, between', function (test) { + // Post.destroyAll(function () { + // Post.create({date: new Date('Wed, 01 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Thu, 02 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Fri, 03 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Sat, 04 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Sun, 05 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Mon, 06 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Wed, 08 Feb 2012 13:56:12 GMT')}, done); + // Post.create({date: new Date('Thu, 09 Feb 2012 13:56:12 GMT')}, done); + // }); + + // var posts = 9; + // function done() { + // if (--posts === 0) makeTest(); + // } + + // function makeTest() { + // // gt + // Post.all({where: {date: {gt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { + // test.equal(posts.length, 2, 'gt'); + // ok(); + // }); + + // // gte + // Post.all({where: {date: {gte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { + // test.equal(posts.length, 3, 'gte'); + // ok(); + // }); + + // // lte + // Post.all({where: {date: {lte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { + // test.equal(posts.length, 7, 'lte'); + // ok(); + // }); + + // // lt + // Post.all({where: {date: {lt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) { + // test.equal(posts.length, 6, 'lt'); + // ok(); + // }); + + // // between + // Post.all({where: {date: {between: [new Date('Tue, 05 Feb 2012 13:56:12 GMT'), new Date('Tue, 09 Feb 2012 13:56:12 GMT')]}}}, function (err, posts) { + // test.equal(posts.length, 5, 'between'); + // ok(); + // }); + // } + + // var tests = 5; + // function ok() { + // if (--tests === 0) test.done(); + // } + // }); + + // if ( + // dataSource.name === 'mysql' || + // dataSource.name === 'postgres' + // ) + // it('should allow IN or NOT IN', function (test) { + // User.destroyAll(function () { + // User.create({name: 'User A', age: 21}, done); + // User.create({name: 'User B', age: 22}, done); + // User.create({name: 'User C', age: 23}, done); + // User.create({name: 'User D', age: 24}, done); + // User.create({name: 'User E', age: 25}, done); + // }); + + // var users = 5; + // function done() { + // if (--users === 0) makeTest(); + // } + + // function makeTest() { + // // IN with empty array should return nothing + // User.all({where: {name: {inq: []}}}, function (err, users) { + // test.equal(users.length, 0, 'IN with empty array returns nothing'); + // ok(); + // }); + + // // NOT IN with empty array should return everything + // User.all({where: {name: {nin: []}}}, function (err, users) { + // test.equal(users.length, 5, 'NOT IN with empty array returns everything'); + // ok(); + // }); + + // // IN [User A] returns user with name = User A + // User.all({where: {name: {inq: ['User A']}}}, function (err, users) { + // test.equal(users.length, 1, 'IN searching one existing value returns 1 user'); + // test.equal(users[0].name, 'User A', 'IN [User A] returns user with name = User A'); + // ok(); + // }); + + // // NOT IN [User A] returns users with name != User A + // User.all({where: {name: {nin: ['User A']}}}, function (err, users) { + // test.equal(users.length, 4, 'IN [User A] returns users with name != User A'); + // ok(); + // }); + + // // IN [User A, User B] returns users with name = User A OR name = User B + // User.all({where: {name: {inq: ['User A', 'User B']}}}, function (err, users) { + // test.equal(users.length, 2, 'IN searching two existing values returns 2 users'); + // ok(); + // }); + + // // NOT IN [User A, User B] returns users with name != User A AND name != User B + // User.all({where: {name: {nin: ['User A', 'User B']}}}, function (err, users) { + // test.equal(users.length, 3, 'NOT IN searching two existing values returns users with name != User A AND name != User B'); + // ok(); + // }); + + // // IN works with numbers too + // User.all({where: {age: {inq: [21, 22]}}}, function (err, users) { + // test.equal(users.length, 2, 'IN works with numbers too'); + // ok(); + // }); + + // // NOT IN works with numbers too + // User.all({where: {age: {nin: [21, 22]}}}, function (err, users) { + // test.equal(users.length, 3, 'NOT IN works with numbers too'); + // ok(); + // }); + // } + + // var tests = 8; + // function ok() { + // if (--tests === 0) test.done(); + // } + // }); + + it('should handle order clause with direction', function (test) { + var wait = 0; + var emails = [ + 'john@hcompany.com', + 'tom@hcompany.com', + 'admin@hcompany.com', + 'tin@hcompany.com', + 'mike@hcompany.com', + 'susan@hcompany.com', + 'test@hcompany.com' + ]; + User.destroyAll(function () { + emails.forEach(function (email) { + wait += 1; + User.create({email: email, name: 'Nick'}, done); + }); + }); + var tests = 2; + + function done() { + process.nextTick(function () { + if (--wait === 0) { + doSortTest(); + doReverseSortTest(); + } + }); + } + + function doSortTest() { + User.all({order: 'email ASC', where: {name: 'Nick'}}, function (err, users) { + var _emails = emails.sort(); + users.forEach(function (user, i) { + test.equal(_emails[i], user.email, 'ASC sorting'); + }); + testDone(); + }); + } + + function doReverseSortTest() { + User.all({order: 'email DESC', where: {name: 'Nick'}}, function (err, users) { + var _emails = emails.sort().reverse(); + users.forEach(function (user, i) { + test.equal(_emails[i], user.email, 'DESC sorting'); + }); + testDone(); + }); + } + + function testDone() { + if (--tests === 0) test.done(); + } + }); + + it('should return id in find result even after updateAttributes', function (test) { + Post.create(function (err, post) { + var id = post.id; + test.ok(post.published === false); + post.updateAttributes({title: 'hey', published: true}, function () { + Post.find(id, function (err, post) { + test.ok(!!post.published, 'Update boolean field'); + test.ok(post.id); + test.done(); + }); + }); + }); + }); + + it('should handle belongsTo correctly', function (test) { + var passport = new Passport({ownerId: 16}); + // sync getter + test.equal(passport.owner(), 16); + // sync setter + passport.owner(18); + test.equal(passport.owner(), 18); + test.done(); + }); + + it('should query one record', function (test) { + test.expect(4); + Post.findOne(function (err, post) { + test.ok(post && post.id); + Post.findOne({ where: { title: 'hey' } }, function (err, post) { + if (err) { + console.log(err); + return test.done(); + } + test.equal(post && post.constructor.modelName, 'Post'); + test.equal(post && post.title, 'hey'); + Post.findOne({ where: { title: 'not exists' } }, function (err, post) { + test.ok(post === null); + test.done(); + }); + }); + }); + }); + + // if ( + // !dataSource.name.match(/redis/) && + // dataSource.name !== 'memory' && + // dataSource.name !== 'neo4j' && + // dataSource.name !== 'cradle' && + // dataSource.name !== 'nano' + // ) + // it('belongsTo should be cached', function (test) { + // User.findOne(function(err, user) { + + // var passport = new Passport({ownerId: user.id}); + // var passport2 = new Passport({ownerId: null}); + + // // There can't be any concurrency because we are counting requests + // // We are first testing cases when passport has an owner + // passport.owner(function(err, data) { + // var nbInitialRequests = nbSchemaRequests; + // passport.owner(function(err, data2) { + // test.equal(data.id, data2.id, 'The value should remain the same'); + // requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.'); + + // // We are now testing cases when passport has not an owner + // passport2.owner(function(err, data) { + // var nbInitialRequests2 = nbSchemaRequests; + // passport2.owner(function(err, data2) { + // test.equal(data, null, 'The value should be null since there is no owner'); + // test.equal(data, data2, 'The value should remain the same (null)'); + // requestsAreCounted && test.equal(nbInitialRequests2, nbSchemaRequests, 'There should not be any request because value is cached.'); + + // passport2.owner(user.id); + // passport2.owner(function(err, data3) { + // test.equal(data3.id, user.id, 'Owner should now be the user.'); + // requestsAreCounted && test.equal(nbInitialRequests2 + 1, nbSchemaRequests, 'If we changed owner id, there should be one more request.'); + + // passport2.owner(true, function(err, data4) { + // test.equal(data3.id, data3.id, 'The value should remain the same'); + // requestsAreCounted && test.equal(nbInitialRequests2 + 2, nbSchemaRequests, 'If we forced refreshing, there should be one more request.'); + // test.done(); + // }); + // }); + // }); + // }); + + // }); + // }); + // }); + + // }); + + if (dataSource.name !== 'mongoose' && dataSource.name !== 'neo4j') + it('should update or create record', function (test) { + var newData = { + id: 1, + title: 'New title (really new)', + content: 'Some example content (updated)' + }; + Post.updateOrCreate(newData, function (err, updatedPost) { + if (err) throw err; + test.ok(updatedPost); + if (!updatedPost) throw Error('No post!'); + + if (dataSource.name !== 'mongodb') { + test.equal(newData.id, updatedPost.toObject().id); + } + test.equal(newData.title, updatedPost.toObject().title); + test.equal(newData.content, updatedPost.toObject().content); + + Post.findById(updatedPost.id, function (err, post) { + if (err) throw err; + if (!post) throw Error('No post!'); + if (dataSource.name !== 'mongodb') { + test.equal(newData.id, post.toObject().id); + } + test.equal(newData.title, post.toObject().title); + test.equal(newData.content, post.toObject().content); + Post.updateOrCreate({id: 100001, title: 'hey'}, function (err, post) { + if (dataSource.name !== 'mongodb') test.equal(post.id, 100001); + test.equal(post.title, 'hey'); + Post.findById(post.id, function (err, post) { + if (!post) throw Error('No post!'); + test.done(); + }); + }); + }); + }); + }); + + it('should work with custom setters and getters', function (test) { + User.dataSource.defineForeignKey('User', 'passwd'); + User.setter.passwd = function (pass) { + this._passwd = pass + 'salt'; + }; + var u = new User({passwd: 'qwerty'}); + test.equal(u.passwd, 'qwertysalt'); + u.save(function (err, user) { + User.findById(user.id, function (err, user) { + test.ok(user !== u); + test.equal(user.passwd, 'qwertysalt'); + User.all({where: {passwd: 'qwertysalt'}}, function (err, users) { + test.ok(users[0] !== user); + test.equal(users[0].passwd, 'qwertysalt'); + User.create({passwd: 'asalat'}, function (err, usr) { + test.equal(usr.passwd, 'asalatsalt'); + User.upsert({passwd: 'heyman'}, function (err, us) { + test.equal(us.passwd, 'heymansalt'); + User.findById(us.id, function (err, user) { + test.equal(user.passwd, 'heymansalt'); test.done(); + }); }); + }); }); + }); }); + }); + + it('should work with typed and untyped nested collections', function (test) { + var post = new Post; + var like = post.likes.push({foo: 'bar'}); + test.equal(like.constructor.name, 'ListItem'); + var related = post.related.push({hello: 'world'}); + test.ok(related.someMethod); + post.save(function (err, p) { + test.equal(p.likes.nextid, 2); + p.likes.push({second: 2}); + p.likes.push({third: 3}); + p.save(function (err) { + Post.findById(p.id, function (err, pp) { + test.equal(pp.likes.length, 3); + test.ok(pp.likes[3].third); + test.ok(pp.likes[2].second); + test.ok(pp.likes[1].foo); + pp.likes.remove(2); + test.equal(pp.likes.length, 2); + test.ok(!pp.likes[2]); + pp.likes.remove(pp.likes[1]); + test.equal(pp.likes.length, 1); + test.ok(!pp.likes[1]); + test.ok(pp.likes[3]); + pp.save(function () { + Post.findById(p.id, function (err, pp) { + test.equal(pp.likes.length, 1); + test.ok(!pp.likes[1]); + test.ok(pp.likes[3]); + test.done(); + }); + }); + }); + }); + }); + }); + + it('should find or create', function (test) { + var email = 'some email ' + Math.random(); + User.findOrCreate({where: {email: email}}, function (err, u) { + test.ok(u); + test.ok(!u.age); + User.findOrCreate({where: {email: email}}, {age: 21}, function (err, u2) { + test.equals(u.id.toString(), u2.id.toString(), 'Same user ids'); + test.ok(!u2.age); + test.done(); + }); + }); + }); } diff --git a/test/datatype.test.js b/test/datatype.test.js index 6fd61e79..7fb65b68 100644 --- a/test/datatype.test.js +++ b/test/datatype.test.js @@ -3,64 +3,64 @@ var should = require('./init.js'); var db, Model; -describe('datatypes', function() { +describe('datatypes', function () { - before(function(done){ - db = getSchema(); - Model = db.define('Model', { - str: String, - date: Date, - num: Number, - bool: Boolean, - list: {type: []}, - }); - db.automigrate(function() { - Model.destroyAll(done); - }); + before(function (done) { + db = getSchema(); + Model = db.define('Model', { + str: String, + date: Date, + num: Number, + bool: Boolean, + list: {type: []}, + }); + db.automigrate(function () { + Model.destroyAll(done); + }); + }); + + it('should keep types when get read data from db', function (done) { + var d = new Date, id; + + Model.create({ + str: 'hello', date: d, num: '3', bool: 1, list: ['test'] + }, function (err, m) { + should.not.exist(err); + should.exist(m && m.id); + m.str.should.be.a('string'); + m.num.should.be.a('number'); + m.bool.should.be.a('boolean'); + id = m.id; + testFind(testAll); }); - it('should keep types when get read data from db', function(done) { - var d = new Date, id; + function testFind(next) { + debugger; + Model.findById(id, function (err, m) { + should.not.exist(err); + should.exist(m); + m.str.should.be.a('string'); + m.num.should.be.a('number'); + m.bool.should.be.a('boolean'); + m.date.should.be.an.instanceOf(Date); + m.date.toString().should.equal(d.toString(), 'Time must match'); + next(); + }); + } - Model.create({ - str: 'hello', date: d, num: '3', bool: 1, list: ['test'] - }, function(err, m) { - should.not.exist(err); - should.exist(m && m.id); - m.str.should.be.a('string'); - m.num.should.be.a('number'); - m.bool.should.be.a('boolean'); - id = m.id; - testFind(testAll); - }); + function testAll() { + Model.findOne(function (err, m) { + should.not.exist(err); + should.exist(m); + m.str.should.be.a('string'); + m.num.should.be.a('number'); + m.bool.should.be.a('boolean'); + m.date.should.be.an.instanceOf(Date); + m.date.toString().should.equal(d.toString(), 'Time must match'); + done(); + }); + } - function testFind(next) { - debugger; - Model.findById(id, function(err, m) { - should.not.exist(err); - should.exist(m); - m.str.should.be.a('string'); - m.num.should.be.a('number'); - m.bool.should.be.a('boolean'); - m.date.should.be.an.instanceOf(Date); - m.date.toString().should.equal(d.toString(), 'Time must match'); - next(); - }); - } - - function testAll() { - Model.findOne(function(err, m) { - should.not.exist(err); - should.exist(m); - m.str.should.be.a('string'); - m.num.should.be.a('number'); - m.bool.should.be.a('boolean'); - m.date.should.be.an.instanceOf(Date); - m.date.toString().should.equal(d.toString(), 'Time must match'); - done(); - }); - } - - }); + }); }); diff --git a/test/defaults.test.js b/test/defaults.test.js index 8b08f4c0..ce5be95c 100644 --- a/test/defaults.test.js +++ b/test/defaults.test.js @@ -3,36 +3,36 @@ var should = require('./init.js'); var db = getSchema(); -describe('defaults', function() { - var Server; +describe('defaults', function () { + var Server; - before(function() { - Server = db.define('Server', { - host: String, - port: {type: Number, default: 80} - }); + before(function () { + Server = db.define('Server', { + host: String, + port: {type: Number, default: 80} }); + }); - it('should apply defaults on new', function() { - var s = new Server; - s.port.should.equal(80); - }); + it('should apply defaults on new', function () { + var s = new Server; + s.port.should.equal(80); + }); - it('should apply defaults on create', function(done) { - Server.create(function(err, s) { - s.port.should.equal(80); - done(); - }); + it('should apply defaults on create', function (done) { + Server.create(function (err, s) { + s.port.should.equal(80); + done(); }); + }); - it('should apply defaults on read', function(done) { - db.defineProperty('Server', 'host', { - type: String, - default: 'localhost' - }); - Server.all(function (err, servers) { - (new String('localhost')).should.equal(servers[0].host); - done(); - }); + it('should apply defaults on read', function (done) { + db.defineProperty('Server', 'host', { + type: String, + default: 'localhost' }); + Server.all(function (err, servers) { + (new String('localhost')).should.equal(servers[0].host); + done(); + }); + }); }); diff --git a/test/hooks.test.js b/test/hooks.test.js index b1dc7b7b..2610f1b3 100644 --- a/test/hooks.test.js +++ b/test/hooks.test.js @@ -2,391 +2,423 @@ var should = require('./init.js'); var j = require('../'), - Schema = j.Schema, - AbstractClass = j.AbstractClass, - Hookable = j.Hookable, + Schema = j.Schema, + AbstractClass = j.AbstractClass, + Hookable = j.Hookable, - db, User; + db, User; -describe('hooks', function() { +describe('hooks', function () { - before(function(done) { - db = getSchema(); + before(function (done) { + db = getSchema(); - User = db.define('User', { - email: {type: String, index: true}, - name: String, - password: String, - state: String - }); - - db.automigrate(done); + User = db.define('User', { + email: {type: String, index: true}, + name: String, + password: String, + state: String }); - describe('initialize', function() { + db.automigrate(done); + }); - afterEach(function() { - User.afterInitialize = null; - }); - - it('should be triggered on new', function(done) { - User.afterInitialize = function() { - done(); - }; - new User; - }); - - it('should be triggered on create', function(done) { - var user; - User.afterInitialize = function() { - if (this.name === 'Nickolay') { - this.name += ' Rozental'; - } - }; - User.create({name: 'Nickolay'}, function(err, u) { - u.id.should.be.ok; - u.name.should.equal('Nickolay Rozental'); - done(); - }); - }); + describe('initialize', function () { + afterEach(function () { + User.afterInitialize = null; }); - describe('create', function() { - - afterEach(removeHooks('Create')); - - it('should be triggered on create', function(done) { - addHooks('Create', done); - User.create(); - }); - - it('should not be triggered on new', function() { - User.beforeCreate = function(next) { - should.fail('This should not be called'); - next(); - }; - var u = new User; - }); - - it('should be triggered on new+save', function(done) { - addHooks('Create', done); - (new User).save(); - }); - - it('afterCreate should not be triggered on failed create', function(done) { - var old = User.dataSource.connector.create; - User.dataSource.connector.create = function(modelName, id, cb) { - cb(new Error('error')); - } - - User.afterCreate = function() { - throw new Error('shouldn\'t be called') - }; - User.create(function (err, user) { - User.dataSource.connector.create = old; - done(); - }); - }); + it('should be triggered on new', function (done) { + User.afterInitialize = function () { + done(); + }; + new User; }); - describe('save', function() { - afterEach(removeHooks('Save')); - - it('should be triggered on create', function(done) { - addHooks('Save', done); - User.create(); - }); - - it('should be triggered on new+save', function(done) { - addHooks('Save', done); - (new User).save(); - }); - - it('should be triggered on updateAttributes', function(done) { - User.create(function(err, user) { - addHooks('Save', done); - user.updateAttributes({name: 'Anatoliy'}); - }); - }); - - it('should be triggered on save', function(done) { - User.create(function(err, user) { - addHooks('Save', done); - user.name = 'Hamburger'; - user.save(); - }); - }); - - it('should save full object', function(done) { - User.create(function(err, user) { - User.beforeSave = function(next, data) { - data.should.have.keys('id', 'name', 'email', - 'password', 'state') - done(); - }; - user.save(); - }); - }); - - it('should save actual modifications to database', function(done) { - User.beforeSave = function(next, data) { - data.password = 'hash'; - next(); - }; - User.destroyAll(function() { - User.create({ - email: 'james.bond@example.com', - password: '53cr3t' - }, function() { - User.findOne({ - where: {email: 'james.bond@example.com'} - }, function(err, jb) { - jb.password.should.equal('hash'); - done(); - }); - }); - }); - }); - - it('should save actual modifications on updateAttributes', function(done) { - User.beforeSave = function(next, data) { - data.password = 'hash'; - next(); - }; - User.destroyAll(function() { - User.create({ - email: 'james.bond@example.com' - }, function(err, u) { - u.updateAttribute('password', 'new password', function(e, u) { - should.not.exist(e); - should.exist(u); - u.password.should.equal('hash'); - User.findOne({ - where: {email: 'james.bond@example.com'} - }, function(err, jb) { - jb.password.should.equal('hash'); - done(); - }); - }); - }); - }); - }); - + it('should be triggered on create', function (done) { + var user; + User.afterInitialize = function () { + if (this.name === 'Nickolay') { + this.name += ' Rozental'; + } + }; + User.create({name: 'Nickolay'}, function (err, u) { + u.id.should.be.ok; + u.name.should.equal('Nickolay Rozental'); + done(); + }); }); - describe('update', function() { - afterEach(removeHooks('Update')); + }); - it('should not be triggered on create', function() { - User.beforeUpdate = function(next) { - should.fail('This should not be called'); - next(); - }; - User.create(); - }); + describe('create', function () { - it('should not be triggered on new+save', function() { - User.beforeUpdate = function(next) { - should.fail('This should not be called'); - next(); - }; - (new User).save(); - }); + afterEach(removeHooks('Create')); - it('should be triggered on updateAttributes', function(done) { - User.create(function (err, user) { - addHooks('Update', done); - user.updateAttributes({name: 'Anatoliy'}); - }); - }); - - it('should be triggered on save', function(done) { - User.create(function (err, user) { - addHooks('Update', done); - user.name = 'Hamburger'; - user.save(); - }); - }); - - it('should update limited set of fields', function(done) { - User.create(function (err, user) { - User.beforeUpdate = function(next, data) { - data.should.have.keys('name', 'email'); - done(); - }; - user.updateAttributes({name: 1, email: 2}); - }); - }); - - it('should not trigger after-hook on failed save', function(done) { - User.afterUpdate = function() { - should.fail('afterUpdate shouldn\'t be called') - }; - User.create(function (err, user) { - var save = User.dataSource.connector.save; - User.dataSource.connector.save = function(modelName, id, cb) { - User.dataSource.connector.save = save; - cb(new Error('Error')); - } - - user.save(function(err) { - done(); - }); - }); - }); + it('should be triggered on create', function (done) { + addHooks('Create', done); + User.create(); }); - describe('destroy', function() { - - afterEach(removeHooks('Destroy')); - - it('should be triggered on destroy', function(done) { - var hook = 'not called'; - User.beforeDestroy = function(next) { - hook = 'called'; - next(); - }; - User.afterDestroy = function(next) { - hook.should.eql('called'); - next(); - }; - User.create(function (err, user) { - user.destroy(done); - }); - }); - - it('should not trigger after-hook on failed destroy', function(done) { - var destroy = User.dataSource.connector.destroy; - User.dataSource.connector.destroy = function(modelName, id, cb) { - cb(new Error('error')); - } - User.afterDestroy = function() { - should.fail('afterDestroy shouldn\'t be called') - }; - User.create(function (err, user) { - user.destroy(function(err) { - User.dataSource.connector.destroy = destroy; - done(); - }); - }); - }); - + it('should not be triggered on new', function () { + User.beforeCreate = function (next) { + should.fail('This should not be called'); + next(); + }; + var u = new User; }); - describe('lifecycle', function() { - var life = [], user; - before(function(done) { - User.beforeSave = function(d){life.push('beforeSave'); d();}; - User.beforeCreate = function(d){life.push('beforeCreate'); d();}; - User.beforeUpdate = function(d){life.push('beforeUpdate'); d();}; - User.beforeDestroy = function(d){life.push('beforeDestroy');d();}; - User.beforeValidate = function(d){life.push('beforeValidate');d();}; - User.afterInitialize= function( ){life.push('afterInitialize'); }; - User.afterSave = function(d){life.push('afterSave'); d();}; - User.afterCreate = function(d){life.push('afterCreate'); d();}; - User.afterUpdate = function(d){life.push('afterUpdate'); d();}; - User.afterDestroy = function(d){life.push('afterDestroy'); d();}; - User.afterValidate = function(d){life.push('afterValidate');d();}; - User.create(function(e, u) { - user = u; - life = []; - done(); - }); - }); - beforeEach(function() { - life = []; - }); - - it('should describe create sequence', function(done) { - User.create(function() { - life.should.eql([ - 'afterInitialize', - 'beforeValidate', - 'afterValidate', - 'beforeCreate', - 'beforeSave', - 'afterSave', - 'afterCreate' - ]); - done(); - }); - }); - - it('should describe new+save sequence', function(done) { - var u = new User; - u.save(function() { - life.should.eql([ - 'afterInitialize', - 'beforeValidate', - 'afterValidate', - 'beforeCreate', - 'beforeSave', - 'afterSave', - 'afterCreate' - ]); - done(); - }); - }); - - it('should describe updateAttributes sequence', function(done) { - user.updateAttributes({name: 'Antony'}, function() { - life.should.eql([ - 'beforeValidate', - 'afterValidate', - 'beforeSave', - 'beforeUpdate', - 'afterUpdate', - 'afterSave', - ]); - done(); - }); - }); - - it('should describe isValid sequence', function(done) { - should.not.exist( - user.constructor._validations, - 'Expected user to have no validations, but she have'); - user.isValid(function(valid) { - valid.should.be.true; - life.should.eql([ - 'beforeValidate', - 'afterValidate' - ]); - done(); - }); - }); - - it('should describe destroy sequence', function(done) { - user.destroy(function() { - life.should.eql([ - 'beforeDestroy', - 'afterDestroy' - ]); - done(); - }); - }); - + it('should be triggered on new+save', function (done) { + addHooks('Create', done); + (new User).save(); }); + + it('afterCreate should not be triggered on failed create', function (done) { + var old = User.dataSource.connector.create; + User.dataSource.connector.create = function (modelName, id, cb) { + cb(new Error('error')); + } + + User.afterCreate = function () { + throw new Error('shouldn\'t be called') + }; + User.create(function (err, user) { + User.dataSource.connector.create = old; + done(); + }); + }); + }); + + describe('save', function () { + afterEach(removeHooks('Save')); + + it('should be triggered on create', function (done) { + addHooks('Save', done); + User.create(); + }); + + it('should be triggered on new+save', function (done) { + addHooks('Save', done); + (new User).save(); + }); + + it('should be triggered on updateAttributes', function (done) { + User.create(function (err, user) { + addHooks('Save', done); + user.updateAttributes({name: 'Anatoliy'}); + }); + }); + + it('should be triggered on save', function (done) { + User.create(function (err, user) { + addHooks('Save', done); + user.name = 'Hamburger'; + user.save(); + }); + }); + + it('should save full object', function (done) { + User.create(function (err, user) { + User.beforeSave = function (next, data) { + data.should.have.keys('id', 'name', 'email', + 'password', 'state') + done(); + }; + user.save(); + }); + }); + + it('should save actual modifications to database', function (done) { + User.beforeSave = function (next, data) { + data.password = 'hash'; + next(); + }; + User.destroyAll(function () { + User.create({ + email: 'james.bond@example.com', + password: '53cr3t' + }, function () { + User.findOne({ + where: {email: 'james.bond@example.com'} + }, function (err, jb) { + jb.password.should.equal('hash'); + done(); + }); + }); + }); + }); + + it('should save actual modifications on updateAttributes', function (done) { + User.beforeSave = function (next, data) { + data.password = 'hash'; + next(); + }; + User.destroyAll(function () { + User.create({ + email: 'james.bond@example.com' + }, function (err, u) { + u.updateAttribute('password', 'new password', function (e, u) { + should.not.exist(e); + should.exist(u); + u.password.should.equal('hash'); + User.findOne({ + where: {email: 'james.bond@example.com'} + }, function (err, jb) { + jb.password.should.equal('hash'); + done(); + }); + }); + }); + }); + }); + + }); + + describe('update', function () { + afterEach(removeHooks('Update')); + + it('should not be triggered on create', function () { + User.beforeUpdate = function (next) { + should.fail('This should not be called'); + next(); + }; + User.create(); + }); + + it('should not be triggered on new+save', function () { + User.beforeUpdate = function (next) { + should.fail('This should not be called'); + next(); + }; + (new User).save(); + }); + + it('should be triggered on updateAttributes', function (done) { + User.create(function (err, user) { + addHooks('Update', done); + user.updateAttributes({name: 'Anatoliy'}); + }); + }); + + it('should be triggered on save', function (done) { + User.create(function (err, user) { + addHooks('Update', done); + user.name = 'Hamburger'; + user.save(); + }); + }); + + it('should update limited set of fields', function (done) { + User.create(function (err, user) { + User.beforeUpdate = function (next, data) { + data.should.have.keys('name', 'email'); + done(); + }; + user.updateAttributes({name: 1, email: 2}); + }); + }); + + it('should not trigger after-hook on failed save', function (done) { + User.afterUpdate = function () { + should.fail('afterUpdate shouldn\'t be called') + }; + User.create(function (err, user) { + var save = User.dataSource.connector.save; + User.dataSource.connector.save = function (modelName, id, cb) { + User.dataSource.connector.save = save; + cb(new Error('Error')); + } + + user.save(function (err) { + done(); + }); + }); + }); + }); + + describe('destroy', function () { + + afterEach(removeHooks('Destroy')); + + it('should be triggered on destroy', function (done) { + var hook = 'not called'; + User.beforeDestroy = function (next) { + hook = 'called'; + next(); + }; + User.afterDestroy = function (next) { + hook.should.eql('called'); + next(); + }; + User.create(function (err, user) { + user.destroy(done); + }); + }); + + it('should not trigger after-hook on failed destroy', function (done) { + var destroy = User.dataSource.connector.destroy; + User.dataSource.connector.destroy = function (modelName, id, cb) { + cb(new Error('error')); + } + User.afterDestroy = function () { + should.fail('afterDestroy shouldn\'t be called') + }; + User.create(function (err, user) { + user.destroy(function (err) { + User.dataSource.connector.destroy = destroy; + done(); + }); + }); + }); + + }); + + describe('lifecycle', function () { + var life = [], user; + before(function (done) { + User.beforeSave = function (d) { + life.push('beforeSave'); + d(); + }; + User.beforeCreate = function (d) { + life.push('beforeCreate'); + d(); + }; + User.beforeUpdate = function (d) { + life.push('beforeUpdate'); + d(); + }; + User.beforeDestroy = function (d) { + life.push('beforeDestroy'); + d(); + }; + User.beforeValidate = function (d) { + life.push('beforeValidate'); + d(); + }; + User.afterInitialize = function () { + life.push('afterInitialize'); + }; + User.afterSave = function (d) { + life.push('afterSave'); + d(); + }; + User.afterCreate = function (d) { + life.push('afterCreate'); + d(); + }; + User.afterUpdate = function (d) { + life.push('afterUpdate'); + d(); + }; + User.afterDestroy = function (d) { + life.push('afterDestroy'); + d(); + }; + User.afterValidate = function (d) { + life.push('afterValidate'); + d(); + }; + User.create(function (e, u) { + user = u; + life = []; + done(); + }); + }); + beforeEach(function () { + life = []; + }); + + it('should describe create sequence', function (done) { + User.create(function () { + life.should.eql([ + 'afterInitialize', + 'beforeValidate', + 'afterValidate', + 'beforeCreate', + 'beforeSave', + 'afterSave', + 'afterCreate' + ]); + done(); + }); + }); + + it('should describe new+save sequence', function (done) { + var u = new User; + u.save(function () { + life.should.eql([ + 'afterInitialize', + 'beforeValidate', + 'afterValidate', + 'beforeCreate', + 'beforeSave', + 'afterSave', + 'afterCreate' + ]); + done(); + }); + }); + + it('should describe updateAttributes sequence', function (done) { + user.updateAttributes({name: 'Antony'}, function () { + life.should.eql([ + 'beforeValidate', + 'afterValidate', + 'beforeSave', + 'beforeUpdate', + 'afterUpdate', + 'afterSave', + ]); + done(); + }); + }); + + it('should describe isValid sequence', function (done) { + should.not.exist( + user.constructor._validations, + 'Expected user to have no validations, but she have'); + user.isValid(function (valid) { + valid.should.be.true; + life.should.eql([ + 'beforeValidate', + 'afterValidate' + ]); + done(); + }); + }); + + it('should describe destroy sequence', function (done) { + user.destroy(function () { + life.should.eql([ + 'beforeDestroy', + 'afterDestroy' + ]); + done(); + }); + }); + + }); }); function addHooks(name, done) { - var called = false, random = String(Math.floor(Math.random() * 1000)); - User['before' + name] = function(next, data) { - called = true; - data.email = random; - next(); - }; - User['after' + name] = function(next) { - (new Boolean(called)).should.equal(true); - this.email.should.equal(random); - done(); - }; + var called = false, random = String(Math.floor(Math.random() * 1000)); + User['before' + name] = function (next, data) { + called = true; + data.email = random; + next(); + }; + User['after' + name] = function (next) { + (new Boolean(called)).should.equal(true); + this.email.should.equal(random); + done(); + }; } function removeHooks(name) { - return function() { - User['after' + name] = null; - User['before' + name] = null; - }; + return function () { + User['after' + name] = null; + User['before' + name] = null; + }; } diff --git a/test/include.test.js b/test/include.test.js index e397d30d..6e265858 100644 --- a/test/include.test.js +++ b/test/include.test.js @@ -4,208 +4,209 @@ var should = require('./init.js'); var db, User, Post, Passport, City, Street, Building; var nbSchemaRequests = 0; -describe('include', function() { +describe('include', function () { - before(setup); + before(setup); - it('should fetch belongsTo relation', function(done) { - Passport.all({include: 'owner'}, function (err, passports) { - passports.length.should.be.ok; - passports.forEach(function(p) { - p.__cachedRelations.should.have.property('owner'); - var owner = p.__cachedRelations.owner; - if (!p.ownerId) { - should.not.exist(owner); - } else { - should.exist(owner); - owner.id.should.equal(p.ownerId); - } - }); - done(); - }); + it('should fetch belongsTo relation', function (done) { + Passport.all({include: 'owner'}, function (err, passports) { + passports.length.should.be.ok; + passports.forEach(function (p) { + p.__cachedRelations.should.have.property('owner'); + var owner = p.__cachedRelations.owner; + if (!p.ownerId) { + should.not.exist(owner); + } else { + should.exist(owner); + owner.id.should.equal(p.ownerId); + } + }); + done(); }); + }); - it('should fetch hasMany relation', function(done) { - User.all({include: 'posts'}, function (err, users) { - should.not.exist(err); - should.exist(users); - users.length.should.be.ok; - users.forEach(function(u) { - u.__cachedRelations.should.have.property('posts'); - u.__cachedRelations.posts.forEach(function(p) { - p.userId.should.equal(u.id); - }); - }); - done(); + it('should fetch hasMany relation', function (done) { + User.all({include: 'posts'}, function (err, users) { + should.not.exist(err); + should.exist(users); + users.length.should.be.ok; + users.forEach(function (u) { + u.__cachedRelations.should.have.property('posts'); + u.__cachedRelations.posts.forEach(function (p) { + p.userId.should.equal(u.id); }); + }); + done(); }); + }); - it('should fetch Passport - Owner - Posts', function(done) { - Passport.all({include: {owner: 'posts'}}, function(err, passports) { - should.not.exist(err); - should.exist(passports); - passports.length.should.be.ok; - passports.forEach(function(p) { - p.__cachedRelations.should.have.property('owner'); - var user = p.__cachedRelations.owner; - if (!p.ownerId) { - should.not.exist(user); - } else { - should.exist(user); - user.id.should.equal(p.ownerId); - user.__cachedRelations.should.have.property('posts'); - user.__cachedRelations.posts.forEach(function(pp) { - pp.userId.should.equal(user.id); - }); - } - }); - done(); - }); + it('should fetch Passport - Owner - Posts', function (done) { + Passport.all({include: {owner: 'posts'}}, function (err, passports) { + should.not.exist(err); + should.exist(passports); + passports.length.should.be.ok; + passports.forEach(function (p) { + p.__cachedRelations.should.have.property('owner'); + var user = p.__cachedRelations.owner; + if (!p.ownerId) { + should.not.exist(user); + } else { + should.exist(user); + user.id.should.equal(p.ownerId); + user.__cachedRelations.should.have.property('posts'); + user.__cachedRelations.posts.forEach(function (pp) { + pp.userId.should.equal(user.id); + }); + } + }); + done(); }); + }); - it('should fetch Passports - User - Posts - User', function(done) { - Passport.all({ - include: {owner: {posts: 'author'}} - }, function(err, passports) { - should.not.exist(err); - should.exist(passports); - passports.length.should.be.ok; - passports.forEach(function(p) { - p.__cachedRelations.should.have.property('owner'); - var user = p.__cachedRelations.owner; - if (!p.ownerId) { - should.not.exist(user); - } else { - should.exist(user); - user.id.should.equal(p.ownerId); - user.__cachedRelations.should.have.property('posts'); - user.__cachedRelations.posts.forEach(function(pp) { - pp.userId.should.equal(user.id); - pp.__cachedRelations.should.have.property('author'); - var author = pp.__cachedRelations.author; - author.id.should.equal(user.id); - }); - } - }); - done(); - }); + it('should fetch Passports - User - Posts - User', function (done) { + Passport.all({ + include: {owner: {posts: 'author'}} + }, function (err, passports) { + should.not.exist(err); + should.exist(passports); + passports.length.should.be.ok; + passports.forEach(function (p) { + p.__cachedRelations.should.have.property('owner'); + var user = p.__cachedRelations.owner; + if (!p.ownerId) { + should.not.exist(user); + } else { + should.exist(user); + user.id.should.equal(p.ownerId); + user.__cachedRelations.should.have.property('posts'); + user.__cachedRelations.posts.forEach(function (pp) { + pp.userId.should.equal(user.id); + pp.__cachedRelations.should.have.property('author'); + var author = pp.__cachedRelations.author; + author.id.should.equal(user.id); + }); + } + }); + done(); }); + }); - it('should fetch User - Posts AND Passports', function(done) { - User.all({include: ['posts', 'passports']}, function(err, users) { - should.not.exist(err); - should.exist(users); - users.length.should.be.ok; - users.forEach(function(user) { - user.__cachedRelations.should.have.property('posts'); - user.__cachedRelations.should.have.property('passports'); - user.__cachedRelations.posts.forEach(function(p) { - p.userId.should.equal(user.id); - }); - user.__cachedRelations.passports.forEach(function(pp) { - pp.ownerId.should.equal(user.id); - }); - }); - done(); + it('should fetch User - Posts AND Passports', function (done) { + User.all({include: ['posts', 'passports']}, function (err, users) { + should.not.exist(err); + should.exist(users); + users.length.should.be.ok; + users.forEach(function (user) { + user.__cachedRelations.should.have.property('posts'); + user.__cachedRelations.should.have.property('passports'); + user.__cachedRelations.posts.forEach(function (p) { + p.userId.should.equal(user.id); }); + user.__cachedRelations.passports.forEach(function (pp) { + pp.ownerId.should.equal(user.id); + }); + }); + done(); }); + }); }); function setup(done) { - db = getSchema(); - City = db.define('City'); - Street = db.define('Street'); - Building = db.define('Building'); - User = db.define('User', { - name: String, - age: Number - }); - Passport = db.define('Passport', { - number: String - }); - Post = db.define('Post', { - title: String - }); + db = getSchema(); + City = db.define('City'); + Street = db.define('Street'); + Building = db.define('Building'); + User = db.define('User', { + name: String, + age: Number + }); + Passport = db.define('Passport', { + number: String + }); + Post = db.define('Post', { + title: String + }); - Passport.belongsTo('owner', {model: User}); - User.hasMany('passports', {foreignKey: 'ownerId'}); - User.hasMany('posts', {foreignKey: 'userId'}); - Post.belongsTo('author', {model: User, foreignKey: 'userId'}); + Passport.belongsTo('owner', {model: User}); + User.hasMany('passports', {foreignKey: 'ownerId'}); + User.hasMany('posts', {foreignKey: 'userId'}); + Post.belongsTo('author', {model: User, foreignKey: 'userId'}); - db.automigrate(function() { - var createdUsers = []; - var createdPassports = []; - var createdPosts = []; - createUsers(); - function createUsers() { - clearAndCreate( - User, - [ - {name: 'User A', age: 21}, - {name: 'User B', age: 22}, - {name: 'User C', age: 23}, - {name: 'User D', age: 24}, - {name: 'User E', age: 25} - ], - function(items) { - createdUsers = items; - createPassports(); - } - ); + db.automigrate(function () { + var createdUsers = []; + var createdPassports = []; + var createdPosts = []; + createUsers(); + function createUsers() { + clearAndCreate( + User, + [ + {name: 'User A', age: 21}, + {name: 'User B', age: 22}, + {name: 'User C', age: 23}, + {name: 'User D', age: 24}, + {name: 'User E', age: 25} + ], + function (items) { + createdUsers = items; + createPassports(); } + ); + } - function createPassports() { - clearAndCreate( - Passport, - [ - {number: '1', ownerId: createdUsers[0].id}, - {number: '2', ownerId: createdUsers[1].id}, - {number: '3'} - ], - function(items) { - createdPassports = items; - createPosts(); - } - ); + function createPassports() { + clearAndCreate( + Passport, + [ + {number: '1', ownerId: createdUsers[0].id}, + {number: '2', ownerId: createdUsers[1].id}, + {number: '3'} + ], + function (items) { + createdPassports = items; + createPosts(); } + ); + } - function createPosts() { - clearAndCreate( - Post, - [ - {title: 'Post A', userId: createdUsers[0].id}, - {title: 'Post B', userId: createdUsers[0].id}, - {title: 'Post C', userId: createdUsers[0].id}, - {title: 'Post D', userId: createdUsers[1].id}, - {title: 'Post E'} - ], - function(items) { - createdPosts = items; - done(); - } - ); + function createPosts() { + clearAndCreate( + Post, + [ + {title: 'Post A', userId: createdUsers[0].id}, + {title: 'Post B', userId: createdUsers[0].id}, + {title: 'Post C', userId: createdUsers[0].id}, + {title: 'Post D', userId: createdUsers[1].id}, + {title: 'Post E'} + ], + function (items) { + createdPosts = items; + done(); } + ); + } - }); + }); } function clearAndCreate(model, data, callback) { - var createdItems = []; - model.destroyAll(function () { - nextItem(null, null); - }); + var createdItems = []; + model.destroyAll(function () { + nextItem(null, null); + }); - var itemIndex = 0; - function nextItem(err, lastItem) { - if (lastItem !== null) { - createdItems.push(lastItem); - } - if (itemIndex >= data.length) { - callback(createdItems); - return; - } - model.create(data[itemIndex], nextItem); - itemIndex++; + var itemIndex = 0; + + function nextItem(err, lastItem) { + if (lastItem !== null) { + createdItems.push(lastItem); } + if (itemIndex >= data.length) { + callback(createdItems); + return; + } + model.create(data[itemIndex], nextItem); + itemIndex++; + } } diff --git a/test/init.js b/test/init.js index c03a01a0..948ccecd 100644 --- a/test/init.js +++ b/test/init.js @@ -1,21 +1,21 @@ module.exports = require('should'); /* -if (!process.env.TRAVIS) { - if (typeof __cov === 'undefined') { - process.on('exit', function () { - require('semicov').report(); - }); - } + if (!process.env.TRAVIS) { + if (typeof __cov === 'undefined') { + process.on('exit', function () { + require('semicov').report(); + }); + } - require('semicov').init('lib'); -} -*/ + require('semicov').init('lib'); + } + */ var Schema = require('../').Schema; if (!('getSchema' in global)) { - global.getSchema = function() { - return new Schema('memory'); - }; + global.getSchema = function () { + return new Schema('memory'); + }; } diff --git a/test/introspection.test.js b/test/introspection.test.js index 8004e93a..6fda1bcb 100644 --- a/test/introspection.test.js +++ b/test/introspection.test.js @@ -3,99 +3,99 @@ var ModelBuilder = require('../lib/model-builder').ModelBuilder; var introspectType = require('../lib/introspection')(ModelBuilder); var traverse = require('traverse'); -describe('Introspection of model definitions from JSON', function() { +describe('Introspection of model definitions from JSON', function () { - it('should handle simple types', function() { - assert.equal(introspectType('123'), 'string'); - assert.equal(introspectType(true), 'boolean'); - assert.equal(introspectType(false), 'boolean'); - assert.equal(introspectType(12), 'number'); - assert.equal(introspectType(new Date()), 'date'); - }); + it('should handle simple types', function () { + assert.equal(introspectType('123'), 'string'); + assert.equal(introspectType(true), 'boolean'); + assert.equal(introspectType(false), 'boolean'); + assert.equal(introspectType(12), 'number'); + assert.equal(introspectType(new Date()), 'date'); + }); - it('should handle array types', function() { - var type = introspectType(['123']); - assert.deepEqual(type, ['string'], 'type should be ["string"]'); - type = introspectType([1]); - assert.deepEqual(type, ['number'], 'type should be ["number"]'); - // Stop at first known type - type = introspectType([1, '123']); - assert.deepEqual(type, ['number'], 'type should be ["number"]'); - type = introspectType([null, '123']); - assert.deepEqual(type, ['string'], 'type should be ["string"]'); + it('should handle array types', function () { + var type = introspectType(['123']); + assert.deepEqual(type, ['string'], 'type should be ["string"]'); + type = introspectType([1]); + assert.deepEqual(type, ['number'], 'type should be ["number"]'); + // Stop at first known type + type = introspectType([1, '123']); + assert.deepEqual(type, ['number'], 'type should be ["number"]'); + type = introspectType([null, '123']); + assert.deepEqual(type, ['string'], 'type should be ["string"]'); - type = introspectType([]); - assert.equal(type, 'array'); - }); + type = introspectType([]); + assert.equal(type, 'array'); + }); - it('should return Any for null or undefined', function() { - assert.equal(introspectType(null), ModelBuilder.Any); - assert.equal(introspectType(undefined), ModelBuilder.Any); - }); + it('should return Any for null or undefined', function () { + assert.equal(introspectType(null), ModelBuilder.Any); + assert.equal(introspectType(undefined), ModelBuilder.Any); + }); - it('should return a schema for object', function() { - var json = {a: 'str', b: 0, c: true}; - var type = introspectType(json); - assert.equal(type.a, 'string'); - assert.equal(type.b, 'number'); - assert.equal(type.c, 'boolean'); - }); + it('should return a schema for object', function () { + var json = {a: 'str', b: 0, c: true}; + var type = introspectType(json); + assert.equal(type.a, 'string'); + assert.equal(type.b, 'number'); + assert.equal(type.c, 'boolean'); + }); - it('should handle nesting objects', function() { - var json = {a: 'str', b: 0, c: true, d: {x: 10, y: 5}}; - var type = introspectType(json); - assert.equal(type.a, 'string'); - assert.equal(type.b, 'number'); - assert.equal(type.c, 'boolean'); - assert.equal(type.d.x, 'number'); - assert.equal(type.d.y, 'number'); - }); + it('should handle nesting objects', function () { + var json = {a: 'str', b: 0, c: true, d: {x: 10, y: 5}}; + var type = introspectType(json); + assert.equal(type.a, 'string'); + assert.equal(type.b, 'number'); + assert.equal(type.c, 'boolean'); + assert.equal(type.d.x, 'number'); + assert.equal(type.d.y, 'number'); + }); - it('should handle nesting arrays', function() { - var json = {a: 'str', b: 0, c: true, d: [1, 2]}; - var type = introspectType(json); - assert.equal(type.a, 'string'); - assert.equal(type.b, 'number'); - assert.equal(type.c, 'boolean'); - assert.deepEqual(type.d, ['number']); - }); + it('should handle nesting arrays', function () { + var json = {a: 'str', b: 0, c: true, d: [1, 2]}; + var type = introspectType(json); + assert.equal(type.a, 'string'); + assert.equal(type.b, 'number'); + assert.equal(type.c, 'boolean'); + assert.deepEqual(type.d, ['number']); + }); - it('should build a model from the introspected schema', function(done) { + it('should build a model from the introspected schema', function (done) { - var json = { - name: 'Joe', - age: 30, - birthday: new Date(), - vip: true, - address: { - street: '1 Main St', - city: 'San Jose', - state: 'CA', - zipcode: '95131', - country: 'US' - }, - friends: ['John', 'Mary'], - emails: [ - {label: 'work', id: 'x@sample.com'}, - {label: 'home', id: 'x@home.com'} - ], - tags: [] - }; + var json = { + name: 'Joe', + age: 30, + birthday: new Date(), + vip: true, + address: { + street: '1 Main St', + city: 'San Jose', + state: 'CA', + zipcode: '95131', + country: 'US' + }, + friends: ['John', 'Mary'], + emails: [ + {label: 'work', id: 'x@sample.com'}, + {label: 'home', id: 'x@home.com'} + ], + tags: [] + }; - var copy = traverse(json).clone(); + var copy = traverse(json).clone(); - var schema = introspectType(json); + var schema = introspectType(json); - var builder = new ModelBuilder(); - var Model = builder.define('MyModel', schema, {idInjection: false}); + var builder = new ModelBuilder(); + var Model = builder.define('MyModel', schema, {idInjection: false}); - // FIXME: [rfeng] The constructor mutates the arguments - var obj = new Model(json); + // FIXME: [rfeng] The constructor mutates the arguments + var obj = new Model(json); - obj = obj.toObject(); + obj = obj.toObject(); - assert.deepEqual(obj, copy); - done(); - }); + assert.deepEqual(obj, copy); + done(); + }); }); diff --git a/test/json.test.js b/test/json.test.js index 93b73180..43d2c263 100644 --- a/test/json.test.js +++ b/test/json.test.js @@ -4,36 +4,36 @@ var should = require('./init.js'); var Schema = require('../').Schema; var ModelBuilder = require('../').ModelBuilder; -describe('JSON property', function() { - var dataSource, Model; +describe('JSON property', function () { + var dataSource, Model; - it('should be defined', function() { - dataSource = getSchema(); - Model = dataSource.define('Model', {propertyName: ModelBuilder.JSON}); - var m = new Model; - (new Boolean('propertyName' in m)).should.eql(true); - should.not.exist(m.propertyName); - }); + it('should be defined', function () { + dataSource = getSchema(); + Model = dataSource.define('Model', {propertyName: ModelBuilder.JSON}); + var m = new Model; + (new Boolean('propertyName' in m)).should.eql(true); + should.not.exist(m.propertyName); + }); - it('should accept JSON in constructor and return object', function() { - var m = new Model({ - propertyName: '{"foo": "bar"}' - }); - m.propertyName.should.be.an.Object; - m.propertyName.foo.should.equal('bar'); + it('should accept JSON in constructor and return object', function () { + var m = new Model({ + propertyName: '{"foo": "bar"}' }); + m.propertyName.should.be.an.Object; + m.propertyName.foo.should.equal('bar'); + }); - it('should accept object in setter and return object', function() { - var m = new Model; - m.propertyName = {"foo": "bar"}; - m.propertyName.should.be.an.Object; - m.propertyName.foo.should.equal('bar'); - }); + it('should accept object in setter and return object', function () { + var m = new Model; + m.propertyName = {"foo": "bar"}; + m.propertyName.should.be.an.Object; + m.propertyName.foo.should.equal('bar'); + }); - it('should accept string in setter and return string', function() { - var m = new Model; - m.propertyName = '{"foo": "bar"}'; - m.propertyName.should.be.a.String; - m.propertyName.should.equal('{"foo": "bar"}'); - }); + it('should accept string in setter and return string', function () { + var m = new Model; + m.propertyName = '{"foo": "bar"}'; + m.propertyName.should.be.a.String; + m.propertyName.should.equal('{"foo": "bar"}'); + }); }); diff --git a/test/loopback-data.test.js b/test/loopback-data.test.js index 578a2fd7..d170ba14 100644 --- a/test/loopback-data.test.js +++ b/test/loopback-data.test.js @@ -3,8 +3,8 @@ var should = require('./init.js'); var loopbackData = require('../'); -describe('loopback-datasource-juggler', function() { - it('should expose version', function () { - loopbackData.version.should.equal(require('../package.json').version); - }); +describe('loopback-datasource-juggler', function () { + it('should expose version', function () { + loopbackData.version.should.equal(require('../package.json').version); + }); }); diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index 65c0f892..205ee4e1 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -8,413 +8,413 @@ var DataSource = jdb.DataSource; describe('ModelBuilder define model', function () { - it('should be able to define plain models', function (done) { - var modelBuilder = new ModelBuilder(); + it('should be able to define plain models', function (done) { + var modelBuilder = new ModelBuilder(); - var User = modelBuilder.define('User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number - }); - - // define any custom method - User.prototype.getNameAndAge = function () { - return this.name + ', ' + this.age; - }; - - modelBuilder.models.should.be.a('object').and.have.property('User', User); - modelBuilder.definitions.should.be.a('object').and.have.property('User'); - - var user = new User({name: 'Joe', age: 20}); - - 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); + var User = modelBuilder.define('User', { + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number }); - it('should not take unknown properties in strict mode', function (done) { - var modelBuilder = new ModelBuilder(); + // define any custom method + User.prototype.getNameAndAge = function () { + return this.name + ', ' + this.age; + }; - var User = modelBuilder.define('User', {name: String, bio: String}, {strict: true}); + modelBuilder.models.should.be.a('object').and.have.property('User', User); + modelBuilder.definitions.should.be.a('object').and.have.property('User'); - var user = new User({name: 'Joe', age: 20}); + var user = new User({name: 'Joe', age: 20}); - User.modelName.should.equal('User'); - user.should.be.a('object'); - 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); - }); + 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 throw when unknown properties are used if strict=throw', function (done) { - var modelBuilder = new ModelBuilder(); + it('should not take unknown properties in strict mode', function (done) { + var modelBuilder = new ModelBuilder(); - var User = modelBuilder.define('User', {name: String, bio: String}, {strict: 'throw'}); + var User = modelBuilder.define('User', {name: String, bio: String}, {strict: true}); - try { - var user = new User({name: 'Joe', age: 20}); - assert(false, 'The code should have thrown an error'); - } catch(e) { - assert(true, 'The code is expected to throw an error'); + var user = new User({name: 'Joe', age: 20}); + + User.modelName.should.equal('User'); + user.should.be.a('object'); + 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 throw when unknown properties are used if strict=throw', function (done) { + var modelBuilder = new ModelBuilder(); + + var User = modelBuilder.define('User', {name: String, bio: String}, {strict: 'throw'}); + + try { + var user = new User({name: 'Joe', age: 20}); + assert(false, 'The code should have thrown an error'); + } catch (e) { + assert(true, 'The code is expected to throw an error'); + } + done(null, User); + }); + + it('should be able to define open models', function (done) { + var modelBuilder = new ModelBuilder(); + + var User = modelBuilder.define('User', {}, {strict: false}); + + var user = new User({name: 'Joe', age: 20}); + + 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 use false as the default value for strict', function (done) { + var modelBuilder = new ModelBuilder(); + + var User = modelBuilder.define('User', {}); + + var user = new User({name: 'Joe', age: 20}); + + 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(); + + // simplier way to describe model + var User = modelBuilder.define('User', { + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number, + address: { + street: String, + city: String, + state: String, + zipCode: String, + country: String + }, + emails: [ + { + label: String, + email: String } - done(null, User); + ], + friends: [String] }); - it('should be able to define open models', function (done) { - var modelBuilder = new ModelBuilder(); + // define any custom method + User.prototype.getNameAndAge = function () { + return this.name + ', ' + this.age; + }; - var User = modelBuilder.define('User', {}, {strict: false}); + modelBuilder.models.should.be.a('object').and.have.property('User', User); + modelBuilder.definitions.should.be.a('object').and.have.property('User'); - var user = new User({name: 'Joe', age: 20}); - - 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); + var user = new User({ + name: 'Joe', age: 20, + address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}, + emails: [ + {label: 'work', email: 'xyz@sample.com'} + ], + friends: ['Mary', 'John'] }); - it('should use false as the default value for strict', function (done) { - var modelBuilder = new ModelBuilder(); + 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'); + user.should.have.property('address'); + user.address.should.have.property('city', 'San Jose'); + user.address.should.have.property('state', 'CA'); - var User = modelBuilder.define('User', {}); + user = user.toObject(); + user.emails.should.have.property('length', 1); + user.emails[0].should.have.property('label', 'work'); + user.emails[0].should.have.property('email', 'xyz@sample.com'); + user.friends.should.have.property('length', 2); + assert.equal(user.friends[0], 'Mary'); + assert.equal(user.friends[1], 'John'); + done(null, User); + }); - var user = new User({name: 'Joe', age: 20}); + it('should be able to reference models by name before they are defined', function (done) { + var modelBuilder = new ModelBuilder(); - 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); + var User = modelBuilder.define('User', {name: String, address: 'Address'}); + + var user; + try { + user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}}); + assert(false, 'An exception should have been thrown'); + } catch (e) { + // Ignore + } + + var Address = modelBuilder.define('Address', { + street: String, + city: String, + state: String, + zipCode: String, + country: String }); + user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}}); - it('should be able to define nesting models', function (done) { - var modelBuilder = new ModelBuilder(); - - // simplier way to describe model - var User = modelBuilder.define('User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number, - address: { - street: String, - city: String, - state: String, - zipCode: String, - country: String - }, - emails: [{ - label: String, - email: String - }], - friends: [String] - }); - - // define any custom method - User.prototype.getNameAndAge = function () { - return this.name + ', ' + this.age; - }; - - modelBuilder.models.should.be.a('object').and.have.property('User', User); - modelBuilder.definitions.should.be.a('object').and.have.property('User'); - - var user = new User({ - name: 'Joe', age: 20, - address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}, - emails: [{label: 'work', email: 'xyz@sample.com'}], - friends: ['Mary', 'John'] - }); - - 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'); - user.should.have.property('address'); - user.address.should.have.property('city', 'San Jose'); - user.address.should.have.property('state', 'CA'); - - user = user.toObject(); - user.emails.should.have.property('length', 1); - user.emails[0].should.have.property('label', 'work'); - user.emails[0].should.have.property('email', 'xyz@sample.com'); - user.friends.should.have.property('length', 2); - assert.equal(user.friends[0], 'Mary'); - assert.equal(user.friends[1], 'John'); - done(null, User); - }); - - it('should be able to reference models by name before they are defined', function (done) { - var modelBuilder = new ModelBuilder(); - - var User = modelBuilder.define('User', {name: String, address: 'Address'}); - - var user; - try { - user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}}); - assert(false, 'An exception should have been thrown'); - } catch (e) { - // Ignore - } - - var Address = modelBuilder.define('Address', { - street: String, - city: String, - state: String, - zipCode: String, - country: String - }); - - user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}}); - - User.modelName.should.equal('User'); - User.definition.properties.address.should.have.property('type', Address); - user.should.be.a('object'); - assert(user.name === 'Joe'); - user.address.should.have.property('city', 'San Jose'); - user.address.should.have.property('state', 'CA'); - done(null, User); - }); - + User.modelName.should.equal('User'); + User.definition.properties.address.should.have.property('type', Address); + user.should.be.a('object'); + assert(user.name === 'Joe'); + user.address.should.have.property('city', 'San Jose'); + user.address.should.have.property('state', 'CA'); + done(null, User); + }); }); - describe('DataSource define model', function () { - it('should be able to define plain models', function () { - var ds = new DataSource('memory'); + it('should be able to define plain models', function () { + var ds = new DataSource('memory'); // define models - var Post = ds.define('Post', { - title: { type: String, length: 255 }, - content: { type: ModelBuilder.Text }, - date: { type: Date, default: function () { - return new Date(); - } }, - timestamp: { type: Number, default: Date.now }, - published: { type: Boolean, default: false, index: true } - }); + var Post = ds.define('Post', { + title: { type: String, length: 255 }, + content: { type: ModelBuilder.Text }, + date: { type: Date, default: function () { + return new Date(); + } }, + timestamp: { type: Number, default: Date.now }, + published: { type: Boolean, default: false, index: true } + }); // simpler way to describe model - var User = ds.define('User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number - }); + var User = ds.define('User', { + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number + }); - var Group = ds.define('Group', {group: String}); - User.mixin(Group); + var Group = ds.define('Group', {group: String}); + User.mixin(Group); // define any custom method - User.prototype.getNameAndAge = function () { - return this.name + ', ' + this.age; - }; + User.prototype.getNameAndAge = function () { + return this.name + ', ' + this.age; + }; - var user = new User({name: 'Joe', group: 'G1'}); - assert.equal(user.name, 'Joe'); - assert.equal(user.group, 'G1'); + var user = new User({name: 'Joe', group: 'G1'}); + assert.equal(user.name, 'Joe'); + assert.equal(user.group, 'G1'); - // setup relationships - User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); + // setup relationships + User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); - Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); + Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); - User.hasAndBelongsToMany('groups'); + User.hasAndBelongsToMany('groups'); - var user2 = new User({name: 'Smith'}); - user2.save(function (err) { - var post = user2.posts.build({title: 'Hello world'}); - post.save(function (err, data) { - // console.log(err ? err : data); - }); - }); - - Post.findOne({where: {published: false}, order: 'date DESC'}, function (err, data) { - // console.log(data); - }); - - User.create({name: 'Jeff'}, function (err, data) { - if (err) { - console.log(err); - return; - } - var post = data.posts.build({title: 'My Post'}); - }); - - User.create({name: 'Ray'}, function (err, data) { - // console.log(data); - }); - - var Article = ds.define('Article', {title: String}); - var Tag = ds.define('Tag', {name: String}); - Article.hasAndBelongsToMany('tags'); - - Article.create(function (e, article) { - article.tags.create({name: 'popular'}, function (err, data) { - Article.findOne(function (e, article) { - article.tags(function (e, tags) { - // console.log(tags); - }); - }); - }); + var user2 = new User({name: 'Smith'}); + user2.save(function (err) { + var post = user2.posts.build({title: 'Hello world'}); + post.save(function (err, data) { + // console.log(err ? err : data); + }); + }); + + Post.findOne({where: {published: false}, order: 'date DESC'}, function (err, data) { + // console.log(data); + }); + + User.create({name: 'Jeff'}, function (err, data) { + if (err) { + console.log(err); + return; + } + var post = data.posts.build({title: 'My Post'}); + }); + + User.create({name: 'Ray'}, function (err, data) { + // console.log(data); + }); + + var Article = ds.define('Article', {title: String}); + var Tag = ds.define('Tag', {name: String}); + Article.hasAndBelongsToMany('tags'); + + Article.create(function (e, article) { + article.tags.create({name: 'popular'}, function (err, data) { + Article.findOne(function (e, article) { + article.tags(function (e, tags) { + // console.log(tags); + }); }); + }); + }); // should be able to attach a data source to an existing model - var modelBuilder = new ModelBuilder(); + var modelBuilder = new ModelBuilder(); - var Color = modelBuilder.define('Color', { - name: String - }); + var Color = modelBuilder.define('Color', { + name: String + }); - Color.should.not.have.property('create'); + Color.should.not.have.property('create'); // attach - ds.attach(Color); - Color.should.have.property('create'); + ds.attach(Color); + Color.should.have.property('create'); - Color.create({name: 'red'}); - Color.create({name: 'green'}); - Color.create({name: 'blue'}); - - Color.all(function (err, colors) { - colors.should.have.lengthOf(3); - }); + Color.create({name: 'red'}); + Color.create({name: 'green'}); + Color.create({name: 'blue'}); + Color.all(function (err, colors) { + colors.should.have.lengthOf(3); }); - it('should not take unknown properties in strict mode', function (done) { - var ds = new DataSource('memory'); + }); - var User = ds.define('User', {name: String, bio: String}, {strict: true}); + it('should not take unknown properties in strict mode', function (done) { + var ds = new DataSource('memory'); - User.create({name: 'Joe', age: 20}, function (err, user) { + var User = ds.define('User', {name: String, bio: String}, {strict: true}); - User.modelName.should.equal('User'); - user.should.be.a('object'); - 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); - }); + User.create({name: 'Joe', age: 20}, function (err, user) { + + User.modelName.should.equal('User'); + user.should.be.a('object'); + 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 throw when unknown properties are used if strict=throw', function (done) { - var ds = new DataSource('memory'); + it('should throw when unknown properties are used if strict=throw', function (done) { + var ds = new DataSource('memory'); - var User = ds.define('User', {name: String, bio: String}, {strict: 'throw'}); + var User = ds.define('User', {name: String, bio: String}, {strict: 'throw'}); - try { - var user = new User({name: 'Joe', age: 20}); - assert(false, 'The code should have thrown an error'); - } catch(e) { - assert(true, 'The code is expected to throw an error'); - } + try { + var user = new User({name: 'Joe', age: 20}); + assert(false, 'The code should have thrown an error'); + } catch (e) { + assert(true, 'The code is expected to throw an error'); + } + done(null, User); + }); + + it('should be able to define open models', function (done) { + var ds = new DataSource('memory'); + + var User = ds.define('User', {}, {strict: false}); + User.modelName.should.equal('User'); + + User.create({name: 'Joe', age: 20}, function (err, 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'); + + User.findById(user.id, function (err, 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 open models', function (done) { - var ds = new DataSource('memory'); + it('should use false as the default value for strict', function (done) { + var ds = new DataSource('memory'); - var User = ds.define('User', {}, {strict: false}); - User.modelName.should.equal('User'); + var User = ds.define('User', {}); - User.create({name: 'Joe', age: 20}, function (err, user) { + User.create({name: 'Joe', age: 20}, function (err, 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'); - - User.findById(user.id, function(err, 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); - }); - }); + 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 use false as the default value for strict', function (done) { - var ds = new DataSource('memory'); + it('should use true as the default value for strict for relational DBs', function (done) { + var ds = new DataSource('memory'); + ds.connector.relational = true; // HACK - var User = ds.define('User', {}); + var User = ds.define('User', {name: String, bio: String}, {strict: true}); - User.create({name: 'Joe', age: 20}, function (err, user) { + var user = new User({name: 'Joe', age: 20}); - 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 use true as the default value for strict for relational DBs', function (done) { - var ds = new DataSource('memory'); - ds.connector.relational = true; // HACK - - var User = ds.define('User', {name: String, bio: String}, {strict: true}); - - var user = new User({name: 'Joe', age: 20}); - - User.modelName.should.equal('User'); - user.should.be.a('object'); - 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 throw when unknown properties are used if strict=false for relational DBs', function (done) { - var ds = new DataSource('memory'); - ds.connector.relational = true; // HACK - - var User = ds.define('User', {name: String, bio: String}, {strict: 'throw'}); - - try { - var user = new User({name: 'Joe', age: 20}); - assert(false, 'The code should have thrown an error'); - } catch(e) { - assert(true, 'The code is expected to throw an error'); - } - done(null, User); - }); - - - it('should change the property value for save if strict=false', function (done) { - var ds = new DataSource('memory');// define models - var Post = ds.define('Post'); - - Post.create({price: 900}, function(err, post) { - assert.equal(post.price, 900); - post.price = 1000; - post.save(function(err, result) { - assert.equal(1000, result.price); - done(err, result); - }); - }); + User.modelName.should.equal('User'); + user.should.be.a('object'); + 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 throw when unknown properties are used if strict=false for relational DBs', function (done) { + var ds = new DataSource('memory'); + ds.connector.relational = true; // HACK + + var User = ds.define('User', {name: String, bio: String}, {strict: 'throw'}); + + try { + var user = new User({name: 'Joe', age: 20}); + assert(false, 'The code should have thrown an error'); + } catch (e) { + assert(true, 'The code is expected to throw an error'); + } + done(null, User); + }); + + it('should change the property value for save if strict=false', function (done) { + var ds = new DataSource('memory');// define models + var Post = ds.define('Post'); + + Post.create({price: 900}, function (err, post) { + assert.equal(post.price, 900); + post.price = 1000; + post.save(function (err, result) { + assert.equal(1000, result.price); + done(err, result); + }); }); + }); }); @@ -424,8 +424,10 @@ describe('Load models with base', function () { var User = ds.define('User', {name: String}); - User.staticMethod = function staticMethod() {}; - User.prototype.instanceMethod = function instanceMethod() {}; + User.staticMethod = function staticMethod() { + }; + User.prototype.instanceMethod = function instanceMethod() { + }; var Customer = ds.define('Customer', {vip: Boolean}, {base: 'User'}); @@ -433,10 +435,9 @@ describe('Load models with base', function () { assert(Customer.staticMethod === User.staticMethod); assert(Customer.prototype.instanceMethod === User.prototype.instanceMethod); - try { var Customer1 = ds.define('Customer1', {vip: Boolean}, {base: 'User1'}); - } catch(e) { + } catch (e) { assert(e); } @@ -444,45 +445,45 @@ describe('Load models with base', function () { }); }); -describe('DataSource constructor', function() { +describe('DataSource constructor', function () { // Mocked require - var loader = function(name) { + var loader = function (name) { return { name: name } }; - it('should resolve connector by path', function() { + it('should resolve connector by path', function () { var connector = DataSource._resolveConnector(__dirname + '/../lib/connectors/memory'); assert(connector.connector); }); - it('should resolve connector by internal name', function() { + it('should resolve connector by internal name', function () { var connector = DataSource._resolveConnector('memory'); assert(connector.connector); }); - it('should try to resolve connector by module name starts with loopback-connector-', function() { + it('should try to resolve connector by module name starts with loopback-connector-', function () { var connector = DataSource._resolveConnector('loopback-connector-xyz', loader); assert(connector.connector); }); - it('should try to resolve connector by short module name', function() { + it('should try to resolve connector by short module name', function () { var connector = DataSource._resolveConnector('xyz', loader); assert(connector.connector); }); - it('should try to resolve connector by full module name', function() { + it('should try to resolve connector by full module name', function () { var connector = DataSource._resolveConnector('loopback-xyz', loader); assert(connector.connector); }); - it('should fail to resolve connector by module name starts with loopback-connector-', function() { + it('should fail to resolve connector by module name starts with loopback-connector-', function () { var connector = DataSource._resolveConnector('loopback-connector-xyz'); assert(!connector.connector); assert(connector.error.indexOf('loopback-connector-xyz') !== -1); }); - it('should fail to resolve connector by short module name', function() { + it('should fail to resolve connector by short module name', function () { var connector = DataSource._resolveConnector('xyz'); assert(!connector.connector); assert(connector.error.indexOf('loopback-connector-xyz') !== -1); }); - it('should fail to resolve connector by full module name', function() { + it('should fail to resolve connector by full module name', function () { var connector = DataSource._resolveConnector('loopback-xyz'); assert(!connector.connector); assert(connector.error.indexOf('loopback-connector-loopback-xyz') !== -1); @@ -490,143 +491,142 @@ describe('DataSource constructor', function() { }); describe('Load models with relations', function () { - it('should set up relations', function (done) { - var ds = new DataSource('memory'); + it('should set up relations', function (done) { + var ds = new DataSource('memory'); - var Post = ds.define('Post', {userId: Number, content: String}); - var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}}}); + var Post = ds.define('Post', {userId: Number, content: String}); + var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}}}); - assert(User.relations['posts']); - done(); + assert(User.relations['posts']); + done(); + }); + + it('should set up belongsTo relations', function (done) { + var ds = new DataSource('memory'); + + var User = ds.define('User', {name: String}); + var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + + assert(Post.relations['user']); + done(); + }); + + it('should set up foreign key with the correct type', function (done) { + var ds = new DataSource('memory'); + + var User = ds.define('User', {name: String, id: {type: String, id: true}}); + var Post = ds.define('Post', {content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + + var fk = Post.definition.properties['userId']; + assert(fk, 'The foreign key should be added'); + assert(fk.type === String, 'The foreign key should be the same type as primary key'); + assert(Post.relations['user'], 'User relation should be set'); + done(); + }); + + it('should set up hasMany and belongsTo relations', function (done) { + var ds = new DataSource('memory'); + + var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}, accounts: {type: 'hasMany', model: 'Account'}}}); + + assert(!User.relations['posts']); + assert(!User.relations['accounts']); + + var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + + var Account = ds.define('Account', {userId: Number, type: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + + assert(Post.relations['user']); + assert.deepEqual(Post.relations['user'], { + type: 'belongsTo', + keyFrom: 'userId', + keyTo: 'id', + modelTo: User, + multiple: false + }); + assert(User.relations['posts']); + assert.deepEqual(User.relations['posts'], { + type: 'hasMany', + keyFrom: 'id', + keyTo: 'userId', + modelTo: Post, + multiple: true + }); + assert(User.relations['accounts']); + assert.deepEqual(User.relations['accounts'], { + type: 'hasMany', + keyFrom: 'id', + keyTo: 'userId', + modelTo: Account, + multiple: true }); - it('should set up belongsTo relations', function (done) { - var ds = new DataSource('memory'); + done(); + }); - var User = ds.define('User', {name: String}); - var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + it('should throw if a relation is missing type', function (done) { + var ds = new DataSource('memory'); - assert(Post.relations['user']); - done(); - }); + var Post = ds.define('Post', {userId: Number, content: String}); - it('should set up foreign key with the correct type', function (done) { - var ds = new DataSource('memory'); + try { + var User = ds.define('User', {name: String}, {relations: {posts: {model: 'Post'}}}); + } catch (e) { + done(); + } - var User = ds.define('User', {name: String, id: {type: String, id: true}}); - var Post = ds.define('Post', {content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + }); - var fk = Post.definition.properties['userId']; - assert(fk, 'The foreign key should be added'); - assert(fk.type === String, 'The foreign key should be the same type as primary key'); - assert(Post.relations['user'], 'User relation should be set'); - done(); - }); + it('should throw if the relation type is invalid', function (done) { + var ds = new DataSource('memory'); - it('should set up hasMany and belongsTo relations', function (done) { - var ds = new DataSource('memory'); + var Post = ds.define('Post', {userId: Number, content: String}); - var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}, accounts: {type: 'hasMany', model: 'Account'}}}); + try { + var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasXYZ', model: 'Post'}}}); + } catch (e) { + done(); + } - assert(!User.relations['posts']); - assert(!User.relations['accounts']); + }); - var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + it('should handle hasMany through', function (done) { + var ds = new DataSource('memory'); + var Physician = ds.createModel('Physician', { + name: String + }, {relations: {patients: {model: 'Patient', type: 'hasMany', through: 'Appointment'}}}); - var Account = ds.define('Account', {userId: Number, type: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}}); + var Patient = ds.createModel('Patient', { + name: String + }, {relations: {physicians: {model: 'Physician', type: 'hasMany', through: 'Appointment'}}}); - assert(Post.relations['user']); - assert.deepEqual(Post.relations['user'], { - type: 'belongsTo', - keyFrom: 'userId', - keyTo: 'id', - modelTo: User, - multiple: false - }); - assert(User.relations['posts']); - assert.deepEqual(User.relations['posts'], { - type: 'hasMany', - keyFrom: 'id', - keyTo: 'userId', - modelTo: Post, - multiple: true - }); - assert(User.relations['accounts']); - assert.deepEqual(User.relations['accounts'], { - type: 'hasMany', - keyFrom: 'id', - keyTo: 'userId', - modelTo: Account, - multiple: true - }); + assert(!Physician.relations['patients']); // Appointment hasn't been resolved yet + assert(!Patient.relations['physicians']); // Appointment hasn't been resolved yet - done(); - }); + var Appointment = ds.createModel('Appointment', { + physicianId: Number, + patientId: Number, + appointmentDate: Date + }, {relations: {patient: {type: 'belongsTo', model: 'Patient'}, physician: {type: 'belongsTo', model: 'Physician'}}}); - it('should throw if a relation is missing type', function (done) { - var ds = new DataSource('memory'); + assert(Physician.relations['patients']); + assert(Patient.relations['physicians']); + done(); + }); - var Post = ds.define('Post', {userId: Number, content: String}); + it('should set up relations after attach', function (done) { + var ds = new DataSource('memory'); + var modelBuilder = new ModelBuilder(); - try { - var User = ds.define('User', {name: String}, {relations: {posts: {model: 'Post'}}}); - } catch (e) { - done(); - } - - }); - - it('should throw if the relation type is invalid', function (done) { - var ds = new DataSource('memory'); - - var Post = ds.define('Post', {userId: Number, content: String}); - - try { - var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasXYZ', model: 'Post'}}}); - } catch (e) { - done(); - } - - }); - - it('should handle hasMany through', function (done) { - var ds = new DataSource('memory'); - var Physician = ds.createModel('Physician', { - name: String - }, {relations: {patients: {model: 'Patient', type: 'hasMany', through: 'Appointment'}}}); - - var Patient = ds.createModel('Patient', { - name: String - }, {relations: {physicians: {model: 'Physician', type: 'hasMany', through: 'Appointment'}}}); - - assert(!Physician.relations['patients']); // Appointment hasn't been resolved yet - assert(!Patient.relations['physicians']); // Appointment hasn't been resolved yet - - var Appointment = ds.createModel('Appointment', { - physicianId: Number, - patientId: Number, - appointmentDate: Date - }, {relations: {patient: {type: 'belongsTo', model: 'Patient'}, physician: {type: 'belongsTo', model: 'Physician'}}}); - - assert(Physician.relations['patients']); - assert(Patient.relations['physicians']); - done(); - }); - - it('should set up relations after attach', function (done) { - var ds = new DataSource('memory'); - var modelBuilder = new ModelBuilder(); - - var Post = modelBuilder.define('Post', {userId: Number, content: String}); - var User = modelBuilder.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}}}); - - assert(!User.relations['posts']); - Post.attachTo(ds); - User.attachTo(ds); - assert(User.relations['posts']); - done(); - }); + var Post = modelBuilder.define('Post', {userId: Number, content: String}); + var User = modelBuilder.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}}}); + assert(!User.relations['posts']); + Post.attachTo(ds); + User.attachTo(ds); + assert(User.relations['posts']); + done(); + }); }); @@ -724,95 +724,94 @@ describe('DataAccessObject', function () { it('should skip conversion if a simple property produces NaN for numbers', function () { - where = model._coerce({age: 'xyz'}); - assert.deepEqual(where, {age: 'xyz'}); - }); + where = model._coerce({age: 'xyz'}); + assert.deepEqual(where, {age: 'xyz'}); + }); it('should skip conversion if an array property produces NaN for numbers', function () { - where = model._coerce({age: {inq: ['xyz', '12']}}); - assert.deepEqual(where, {age: {inq: ['xyz', 12]}}); - }); + where = model._coerce({age: {inq: ['xyz', '12']}}); + assert.deepEqual(where, {age: {inq: ['xyz', 12]}}); + }); }); describe('Load models from json', function () { - it('should be able to define models from json', function () { - var path = require('path'), - fs = require('fs'); + it('should be able to define models from json', function () { + var path = require('path'), + fs = require('fs'); + /** + * Load LDL schemas from a json doc + * @param schemaFile The dataSource json file + * @returns A map of schemas keyed by name + */ + function loadSchemasSync(schemaFile, dataSource) { + // Set up the data source + if (!dataSource) { + dataSource = new DataSource('memory'); + } - /** - * Load LDL schemas from a json doc - * @param schemaFile The dataSource json file - * @returns A map of schemas keyed by name - */ - function loadSchemasSync(schemaFile, dataSource) { - // Set up the data source - if (!dataSource) { - dataSource = new DataSource('memory'); - } + // Read the dataSource JSON file + var schemas = JSON.parse(fs.readFileSync(schemaFile)); - // Read the dataSource JSON file - var schemas = JSON.parse(fs.readFileSync(schemaFile)); + return dataSource.modelBuilder.buildModels(schemas); - return dataSource.modelBuilder.buildModels(schemas); + } - } + var models = loadSchemasSync(path.join(__dirname, 'test1-schemas.json')); - var models = loadSchemasSync(path.join(__dirname, 'test1-schemas.json')); + models.should.have.property('AnonymousModel_0'); + models.AnonymousModel_0.should.have.property('modelName', 'AnonymousModel_0'); - models.should.have.property('AnonymousModel_0'); - models.AnonymousModel_0.should.have.property('modelName', 'AnonymousModel_0'); + var m1 = new models.AnonymousModel_0({title: 'Test'}); + m1.should.have.property('title', 'Test'); + m1.should.have.property('author', 'Raymond'); - var m1 = new models.AnonymousModel_0({title: 'Test'}); - m1.should.have.property('title', 'Test'); - m1.should.have.property('author', 'Raymond'); + models = loadSchemasSync(path.join(__dirname, 'test2-schemas.json')); + models.should.have.property('Address'); + models.should.have.property('Account'); + models.should.have.property('Customer'); + for (var s in models) { + var m = models[s]; + assert(new m()); + } + }); - models = loadSchemasSync(path.join(__dirname, 'test2-schemas.json')); - models.should.have.property('Address'); - models.should.have.property('Account'); - models.should.have.property('Customer'); - for (var s in models) { - var m = models[s]; - assert(new m()); - } + it('should be able to extend models', function (done) { + var modelBuilder = new ModelBuilder(); + + var User = modelBuilder.define('User', { + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number }); - it('should be able to extend models', function (done) { - var modelBuilder = new ModelBuilder(); + var Customer = User.extend('Customer', {customerId: {type: String, id: true}}); - var User = modelBuilder.define('User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number - }); + var customer = new Customer({name: 'Joe', age: 20, customerId: 'c01'}); - var Customer = User.extend('Customer', {customerId: {type: String, id: true}}); + customer.should.be.a('object').and.have.property('name', 'Joe'); + customer.should.have.property('name', 'Joe'); + customer.should.have.property('age', 20); + customer.should.have.property('customerId', 'c01'); + customer.should.not.have.property('bio'); - var customer = new Customer({name: 'Joe', age: 20, customerId: 'c01'}); + // The properties are defined at prototype level + assert.equal(Object.keys(customer).length, 0); + var count = 0; + for (var p in customer) { + if (typeof customer[p] !== 'function') { + count++; + } + } + assert.equal(count, 7); // Please note there is an injected id from User prototype + assert.equal(Object.keys(customer.toObject()).length, 6); - customer.should.be.a('object').and.have.property('name', 'Joe'); - customer.should.have.property('name', 'Joe'); - customer.should.have.property('age', 20); - customer.should.have.property('customerId', 'c01'); - customer.should.not.have.property('bio'); - - // The properties are defined at prototype level - assert.equal(Object.keys(customer).length, 0); - var count = 0; - for(var p in customer) { - if(typeof customer[p] !== 'function') { - count++; - } - } - assert.equal(count, 7); // Please note there is an injected id from User prototype - assert.equal(Object.keys(customer.toObject()).length, 6); - - done(null, customer); - }); + done(null, customer); + }); it('should be able to extend models with merged settings', function (done) { var modelBuilder = new ModelBuilder(); @@ -831,57 +830,16 @@ describe('Load models from json', function () { relations: { posts: { type: 'hasMany', - model:'Post' + model: 'Post' } } }); - var Customer = User.extend('Customer', - {customerId: {type: String, id: true}}, - { - defaultPermission: 'DENY', - acls: [ - { - principalType: 'ROLE', - principalId: '$unauthenticated', - permission: 'DENY' - } - ], - relations: { - orders: { - type: 'hasMany', - model:'Order' - } - } - } - ); - - assert.deepEqual(User.settings, { - defaultPermission: 'ALLOW', - acls: [ - { - principalType: 'ROLE', - principalId: '$everyone', - permission: 'ALLOW' - } - ], - relations: { - posts: { - type: 'hasMany', - model:'Post' - } - }, - strict: false - }); - - assert.deepEqual(Customer.settings, { + var Customer = User.extend('Customer', + {customerId: {type: String, id: true}}, + { defaultPermission: 'DENY', acls: [ - { - principalType: 'ROLE', - principalId: '$everyone', - permission: 'ALLOW' - }, { principalType: 'ROLE', principalId: '$unauthenticated', @@ -889,47 +847,87 @@ describe('Load models from json', function () { } ], relations: { - posts: { - type: 'hasMany', - model:'Post' - }, orders: { type: 'hasMany', - model:'Order' + model: 'Order' } + } + } + ); + + assert.deepEqual(User.settings, { + defaultPermission: 'ALLOW', + acls: [ + { + principalType: 'ROLE', + principalId: '$everyone', + permission: 'ALLOW' + } + ], + relations: { + posts: { + type: 'hasMany', + model: 'Post' + } + }, + strict: false + }); + + assert.deepEqual(Customer.settings, { + defaultPermission: 'DENY', + acls: [ + { + principalType: 'ROLE', + principalId: '$everyone', + permission: 'ALLOW' }, - strict: false - }); - - done(); + { + principalType: 'ROLE', + principalId: '$unauthenticated', + permission: 'DENY' + } + ], + relations: { + posts: { + type: 'hasMany', + model: 'Post' + }, + orders: { + type: 'hasMany', + model: 'Order' + } + }, + strict: false }); + + done(); + }); }); -describe('DataSource constructor', function(){ - it('Takes url as the settings', function() { - var ds = new DataSource('memory://localhost/mydb?x=1'); - assert.equal(ds.connector.name, 'memory'); - }); +describe('DataSource constructor', function () { + it('Takes url as the settings', function () { + var ds = new DataSource('memory://localhost/mydb?x=1'); + assert.equal(ds.connector.name, 'memory'); + }); - it('Takes connector name', function() { - var ds = new DataSource('memory'); - assert.equal(ds.connector.name, 'memory'); - }); + it('Takes connector name', function () { + var ds = new DataSource('memory'); + assert.equal(ds.connector.name, 'memory'); + }); - it('Takes settings object', function() { - var ds = new DataSource({connector: 'memory'}); - assert.equal(ds.connector.name, 'memory'); - }); + it('Takes settings object', function () { + var ds = new DataSource({connector: 'memory'}); + assert.equal(ds.connector.name, 'memory'); + }); - it('Takes settings object and name', function() { - var ds = new DataSource('x', {connector: 'memory'}); - assert.equal(ds.connector.name, 'memory'); - }); + it('Takes settings object and name', function () { + var ds = new DataSource('x', {connector: 'memory'}); + assert.equal(ds.connector.name, 'memory'); + }); }); - -describe('Injected methods from connectors', function(){ - it('are not shared across models for remote methods', function() { +describe('Injected methods from connectors', function () { + it('are not shared across models for remote methods', function () { var ds = new DataSource('memory'); var M1 = ds.createModel('M1'); var M2 = ds.createModel('M2'); @@ -942,7 +940,7 @@ describe('Injected methods from connectors', function(){ assert.equal(M2.create.shared, true, 'M2.create should stay remotable'); }); - it('are not shared across models for non-remote methods', function() { + it('are not shared across models for non-remote methods', function () { var ds = new DataSource('memory'); var M1 = ds.createModel('M1'); var M2 = ds.createModel('M2'); @@ -965,9 +963,8 @@ describe('Injected methods from connectors', function(){ }); - -describe('ModelBuilder options.models', function(){ - it('should inject model classes from models', function() { +describe('ModelBuilder options.models', function () { + it('should inject model classes from models', function () { var builder = new ModelBuilder(); var M1 = builder.define('M1'); var M2 = builder.define('M2', {}, {models: { @@ -977,7 +974,7 @@ describe('ModelBuilder options.models', function(){ assert.equal(M2.M1, M1, 'M1 should be injected to M2'); }); - it('should inject model classes by name in the models', function() { + it('should inject model classes by name in the models', function () { var builder = new ModelBuilder(); var M1 = builder.define('M1'); var M2 = builder.define('M2', {}, {models: { @@ -988,7 +985,7 @@ describe('ModelBuilder options.models', function(){ }); it('should inject model classes by name in the models before the class is defined', - function() { + function () { var builder = new ModelBuilder(); var M2 = builder.define('M2', {}, {models: { 'M1': 'M1' @@ -997,7 +994,7 @@ describe('ModelBuilder options.models', function(){ assert(M2.M1.settings.unresolved, 'M1 is still a proxy'); var M1 = builder.define('M1'); assert.equal(M2.M1, M1, 'M1 should be injected to M2'); - }); + }); }); diff --git a/test/manipulation.test.js b/test/manipulation.test.js index 295160f6..5fd6ce84 100644 --- a/test/manipulation.test.js +++ b/test/manipulation.test.js @@ -4,254 +4,258 @@ var should = require('./init.js'); var db, Person; var ValidationError = require('..').ValidationError; -describe('manipulation', function() { +describe('manipulation', function () { - before(function(done) { - db = getSchema(); - - Person = db.define('Person', { - name: String, - gender: String, - married: Boolean, - age: {type: Number, index: true}, - dob: Date, - createdAt: {type: Number, default: Date.now} - }); - - db.automigrate(done); + before(function (done) { + db = getSchema(); + Person = db.define('Person', { + name: String, + gender: String, + married: Boolean, + age: {type: Number, index: true}, + dob: Date, + createdAt: {type: Number, default: Date.now} }); - describe('create', function() { + db.automigrate(done); - before(function(done) { - Person.destroyAll(done); - }); + }); - it('should create instance', function(done) { - Person.create({name: 'Anatoliy'}, function(err, p) { - p.name.should.equal('Anatoliy'); - should.not.exist(err); - should.exist(p); - Person.findById(p.id, function(err, person) { - person.id.should.equal(p.id); - person.name.should.equal('Anatoliy'); - done(); - }); - }); - }); + describe('create', function () { - it('should return instance of object', function(done) { - var person = Person.create(function(err, p) { - p.id.should.eql(person.id); - done(); - }); - should.exist(person); - person.should.be.an.instanceOf(Person); - should.not.exist(person.id); - }); - - it('should work when called without callback', function(done) { - Person.afterCreate = function(next) { - this.should.be.an.instanceOf(Person); - this.name.should.equal('Nickolay'); - should.exist(this.id); - Person.afterCreate = null; - next(); - setTimeout(done, 10); - }; - Person.create({name: 'Nickolay'}); - }); - - it('should create instance with blank data', function(done) { - Person.create(function(err, p) { - should.not.exist(err); - should.exist(p); - should.not.exists(p.name); - Person.findById(p.id, function(err, person) { - person.id.should.equal(p.id); - should.not.exists(person.name); - done(); - }); - }); - }); - - it('should work when called with no data and callback', function(done) { - Person.afterCreate = function(next) { - this.should.be.an.instanceOf(Person); - should.not.exist(this.name); - should.exist(this.id); - Person.afterCreate = null; - next(); - setTimeout(done, 30); - }; - Person.create(); - }); - - it('should create batch of objects', function(done) { - var batch = [{name: 'Shaltay'}, {name: 'Boltay'}, {}]; - Person.create(batch, function(e, ps) { - should.not.exist(e); - should.exist(ps); - ps.should.be.instanceOf(Array); - ps.should.have.lengthOf(batch.length); - - Person.validatesPresenceOf('name'); - Person.create(batch, function(errors, persons) { - delete Person._validations; - should.exist(errors); - errors.should.have.lengthOf(batch.length); - should.not.exist(errors[0]); - should.not.exist(errors[1]); - should.exist(errors[2]); - - should.exist(persons); - persons.should.have.lengthOf(batch.length); - persons[0].errors.should.be.false; - done(); - }).should.be.instanceOf(Array); - }).should.have.lengthOf(3); - }); + before(function (done) { + Person.destroyAll(done); }); - describe('save', function() { - - it('should save new object', function(done) { - var p = new Person; - p.save(function(err) { - should.not.exist(err); - should.exist(p.id); - done(); - }); + it('should create instance', function (done) { + Person.create({name: 'Anatoliy'}, function (err, p) { + p.name.should.equal('Anatoliy'); + should.not.exist(err); + should.exist(p); + Person.findById(p.id, function (err, person) { + person.id.should.equal(p.id); + person.name.should.equal('Anatoliy'); + done(); }); - - it('should save existing object', function(done) { - Person.findOne(function(err, p) { - should.not.exist(err); - p.name = 'Hans'; - p.propertyChanged('name').should.be.true; - p.save(function(err) { - should.not.exist(err); - p.propertyChanged('name').should.be.false; - Person.findOne(function(err, p) { - should.not.exist(err); - p.name.should.equal('Hans'); - p.propertyChanged('name').should.be.false; - done(); - }); - }); - }); - }); - - it('should save invalid object (skipping validation)', function(done) { - Person.findOne(function(err, p) { - should.not.exist(err); - p.isValid = function(done) { - process.nextTick(done); - return false; - }; - p.name = 'Nana'; - p.save(function(err) { - should.exist(err); - p.propertyChanged('name').should.be.true; - p.save({validate: false}, function(err) { - should.not.exist(err); - p.propertyChanged('name').should.be.false; - done(); - }); - }); - }); - }); - - it('should save throw error on validation', function() { - Person.findOne(function(err, p) { - should.not.exist(err); - p.isValid = function(cb) { - cb(false); - return false; - }; - (function() { - p.save({ - 'throws': true - }); - }).should.throw(ValidationError); - }); - }); - + }); }); - describe('updateAttributes', function() { - var person; - - before(function(done) { - Person.destroyAll(function() { - person = Person.create(done); - }); - }); - - it('should update one attribute', function(done) { - person.updateAttribute('name', 'Paul Graham', function(err, p) { - should.not.exist(err); - Person.all(function(e, ps) { - should.not.exist(err); - ps.should.have.lengthOf(1); - ps.pop().name.should.equal('Paul Graham'); - done(); - }); - }); - }); + it('should return instance of object', function (done) { + var person = Person.create(function (err, p) { + p.id.should.eql(person.id); + done(); + }); + should.exist(person); + person.should.be.an.instanceOf(Person); + should.not.exist(person.id); }); - describe('destroy', function() { - - it('should destroy record', function(done) { - Person.create(function(err, p){ - p.destroy(function(err) { - should.not.exist(err); - Person.exists(p.id, function(err, ex) { - ex.should.not.be.ok; - done(); - }); - }); - }); - }); - - it('should destroy all records', function (done) { - Person.destroyAll(function (err) { - should.not.exist(err); - Person.all(function (err, posts) { - posts.should.have.lengthOf(0); - Person.count(function (err, count) { - count.should.eql(0); - done(); - }); - }); - }); - }); - - // TODO: implement destroy with filtered set - it('should destroy filtered set of records'); + it('should work when called without callback', function (done) { + Person.afterCreate = function (next) { + this.should.be.an.instanceOf(Person); + this.name.should.equal('Nickolay'); + should.exist(this.id); + Person.afterCreate = null; + next(); + setTimeout(done, 10); + }; + Person.create({name: 'Nickolay'}); }); - describe('initialize', function() { - it('should initialize object properly', function() { - var hw = 'Hello word', - now = Date.now(), - person = new Person({name: hw}); - - person.name.should.equal(hw); - person.propertyChanged('name').should.be.false; - person.name = 'Goodbye, Lenin'; - person.name$was.should.equal(hw); - person.propertyChanged('name').should.be.true; - (person.createdAt >= now).should.be.true; - person.isNewRecord().should.be.true; + it('should create instance with blank data', function (done) { + Person.create(function (err, p) { + should.not.exist(err); + should.exist(p); + should.not.exists(p.name); + Person.findById(p.id, function (err, person) { + person.id.should.equal(p.id); + should.not.exists(person.name); + done(); }); - - // it('should work when constructor called as function', function() { - // var p = Person({name: 'John Resig'}); - // p.should.be.an.instanceOf(Person); - // p.name.should.equal('John Resig'); - // }); + }); }); + + it('should work when called with no data and callback', function (done) { + Person.afterCreate = function (next) { + this.should.be.an.instanceOf(Person); + should.not.exist(this.name); + should.exist(this.id); + Person.afterCreate = null; + next(); + setTimeout(done, 30); + }; + Person.create(); + }); + + it('should create batch of objects', function (done) { + var batch = [ + {name: 'Shaltay'}, + {name: 'Boltay'}, + {} + ]; + Person.create(batch,function (e, ps) { + should.not.exist(e); + should.exist(ps); + ps.should.be.instanceOf(Array); + ps.should.have.lengthOf(batch.length); + + Person.validatesPresenceOf('name'); + Person.create(batch,function (errors, persons) { + delete Person._validations; + should.exist(errors); + errors.should.have.lengthOf(batch.length); + should.not.exist(errors[0]); + should.not.exist(errors[1]); + should.exist(errors[2]); + + should.exist(persons); + persons.should.have.lengthOf(batch.length); + persons[0].errors.should.be.false; + done(); + }).should.be.instanceOf(Array); + }).should.have.lengthOf(3); + }); + }); + + describe('save', function () { + + it('should save new object', function (done) { + var p = new Person; + p.save(function (err) { + should.not.exist(err); + should.exist(p.id); + done(); + }); + }); + + it('should save existing object', function (done) { + Person.findOne(function (err, p) { + should.not.exist(err); + p.name = 'Hans'; + p.propertyChanged('name').should.be.true; + p.save(function (err) { + should.not.exist(err); + p.propertyChanged('name').should.be.false; + Person.findOne(function (err, p) { + should.not.exist(err); + p.name.should.equal('Hans'); + p.propertyChanged('name').should.be.false; + done(); + }); + }); + }); + }); + + it('should save invalid object (skipping validation)', function (done) { + Person.findOne(function (err, p) { + should.not.exist(err); + p.isValid = function (done) { + process.nextTick(done); + return false; + }; + p.name = 'Nana'; + p.save(function (err) { + should.exist(err); + p.propertyChanged('name').should.be.true; + p.save({validate: false}, function (err) { + should.not.exist(err); + p.propertyChanged('name').should.be.false; + done(); + }); + }); + }); + }); + + it('should save throw error on validation', function () { + Person.findOne(function (err, p) { + should.not.exist(err); + p.isValid = function (cb) { + cb(false); + return false; + }; + (function () { + p.save({ + 'throws': true + }); + }).should.throw(ValidationError); + }); + }); + + }); + + describe('updateAttributes', function () { + var person; + + before(function (done) { + Person.destroyAll(function () { + person = Person.create(done); + }); + }); + + it('should update one attribute', function (done) { + person.updateAttribute('name', 'Paul Graham', function (err, p) { + should.not.exist(err); + Person.all(function (e, ps) { + should.not.exist(err); + ps.should.have.lengthOf(1); + ps.pop().name.should.equal('Paul Graham'); + done(); + }); + }); + }); + }); + + describe('destroy', function () { + + it('should destroy record', function (done) { + Person.create(function (err, p) { + p.destroy(function (err) { + should.not.exist(err); + Person.exists(p.id, function (err, ex) { + ex.should.not.be.ok; + done(); + }); + }); + }); + }); + + it('should destroy all records', function (done) { + Person.destroyAll(function (err) { + should.not.exist(err); + Person.all(function (err, posts) { + posts.should.have.lengthOf(0); + Person.count(function (err, count) { + count.should.eql(0); + done(); + }); + }); + }); + }); + + // TODO: implement destroy with filtered set + it('should destroy filtered set of records'); + }); + + describe('initialize', function () { + it('should initialize object properly', function () { + var hw = 'Hello word', + now = Date.now(), + person = new Person({name: hw}); + + person.name.should.equal(hw); + person.propertyChanged('name').should.be.false; + person.name = 'Goodbye, Lenin'; + person.name$was.should.equal(hw); + person.propertyChanged('name').should.be.true; + (person.createdAt >= now).should.be.true; + person.isNewRecord().should.be.true; + }); + + // it('should work when constructor called as function', function() { + // var p = Person({name: 'John Resig'}); + // p.should.be.an.instanceOf(Person); + // p.name.should.equal('John Resig'); + // }); + }); }); diff --git a/test/model-definition.test.js b/test/model-definition.test.js index c2bc5e53..a50d87d6 100644 --- a/test/model-definition.test.js +++ b/test/model-definition.test.js @@ -11,243 +11,236 @@ var ModelDefinition = require('../lib/model-definition'); describe('ModelDefinition class', function () { - it('should be able to define plain models', function (done) { - var modelBuilder = new ModelBuilder(); + it('should be able to define plain models', function (done) { + var modelBuilder = new ModelBuilder(); - var User = new ModelDefinition(modelBuilder, 'User', { - name: "string", - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: "number" - }); + var User = new ModelDefinition(modelBuilder, 'User', { + name: "string", + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: "number" + }); - User.build(); - assert.equal(User.properties.name.type, String); - assert.equal(User.properties.bio.type, ModelBuilder.Text); - assert.equal(User.properties.approved.type, Boolean); - assert.equal(User.properties.joinedAt.type, Date); - assert.equal(User.properties.age.type, Number); + User.build(); + assert.equal(User.properties.name.type, String); + assert.equal(User.properties.bio.type, ModelBuilder.Text); + assert.equal(User.properties.approved.type, Boolean); + assert.equal(User.properties.joinedAt.type, Date); + assert.equal(User.properties.age.type, Number); + var json = User.toJSON(); + assert.equal(json.name, "User"); + assert.equal(json.properties.name.type, "String"); + assert.equal(json.properties.bio.type, "Text"); + assert.equal(json.properties.approved.type, "Boolean"); + assert.equal(json.properties.joinedAt.type, "Date"); + assert.equal(json.properties.age.type, "Number"); - var json = User.toJSON(); - assert.equal(json.name, "User"); - assert.equal(json.properties.name.type, "String"); - assert.equal(json.properties.bio.type, "Text"); - assert.equal(json.properties.approved.type, "Boolean"); - assert.equal(json.properties.joinedAt.type, "Date"); - assert.equal(json.properties.age.type, "Number"); + done(); - done(); + }); + it('should be able to define additional properties', function (done) { + var modelBuilder = new ModelBuilder(); + + var User = new ModelDefinition(modelBuilder, 'User', { + name: "string", + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: "number" + }); + + User.build(); + + User.defineProperty("id", {type: "number", id: true}); + assert.equal(User.properties.name.type, String); + assert.equal(User.properties.bio.type, ModelBuilder.Text); + assert.equal(User.properties.approved.type, Boolean); + assert.equal(User.properties.joinedAt.type, Date); + assert.equal(User.properties.age.type, Number); + + assert.equal(User.properties.id.type, Number); + done(); + + }); + + it('should be able to define nesting models', function (done) { + var modelBuilder = new ModelBuilder(); + + var User = new ModelDefinition(modelBuilder, 'User', { + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number, + address: { + street: String, + city: String, + zipCode: String, + state: String + } + }); + + User.build(); + assert.equal(User.properties.name.type, String); + assert.equal(User.properties.bio.type, ModelBuilder.Text); + assert.equal(User.properties.approved.type, Boolean); + assert.equal(User.properties.joinedAt.type, Date); + assert.equal(User.properties.age.type, Number); + assert.equal(typeof User.properties.address.type, 'function'); + + var json = User.toJSON(); + assert.equal(json.name, "User"); + assert.equal(json.properties.name.type, "String"); + assert.equal(json.properties.bio.type, "Text"); + assert.equal(json.properties.approved.type, "Boolean"); + assert.equal(json.properties.joinedAt.type, "Date"); + assert.equal(json.properties.age.type, "Number"); + + assert.deepEqual(json.properties.address.type, { street: { type: 'String' }, + city: { type: 'String' }, + zipCode: { type: 'String' }, + state: { type: 'String' } }); + + done(); + + }); + + it('should be able to define referencing models', function (done) { + var modelBuilder = new ModelBuilder(); + + var Address = modelBuilder.define('Address', { + street: String, + city: String, + zipCode: String, + state: String + }); + var User = new ModelDefinition(modelBuilder, 'User', { + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number, + address: Address }); - it('should be able to define additional properties', function (done) { - var modelBuilder = new ModelBuilder(); + User.build(); + assert.equal(User.properties.name.type, String); + assert.equal(User.properties.bio.type, ModelBuilder.Text); + assert.equal(User.properties.approved.type, Boolean); + assert.equal(User.properties.joinedAt.type, Date); + assert.equal(User.properties.age.type, Number); + assert.equal(User.properties.address.type, Address); - var User = new ModelDefinition(modelBuilder, 'User', { - name: "string", - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: "number" - }); + var json = User.toJSON(); + assert.equal(json.name, "User"); + assert.equal(json.properties.name.type, "String"); + assert.equal(json.properties.bio.type, "Text"); + assert.equal(json.properties.approved.type, "Boolean"); + assert.equal(json.properties.joinedAt.type, "Date"); + assert.equal(json.properties.age.type, "Number"); - User.build(); + assert.equal(json.properties.address.type, 'Address'); - User.defineProperty("id", {type: "number", id: true}); - assert.equal(User.properties.name.type, String); - assert.equal(User.properties.bio.type, ModelBuilder.Text); - assert.equal(User.properties.approved.type, Boolean); - assert.equal(User.properties.joinedAt.type, Date); - assert.equal(User.properties.age.type, Number); + done(); - assert.equal(User.properties.id.type, Number); - done(); + }); + it('should be able to define referencing models by name', function (done) { + var modelBuilder = new ModelBuilder(); + + var Address = modelBuilder.define('Address', { + street: String, + city: String, + zipCode: String, + state: String + }); + var User = new ModelDefinition(modelBuilder, 'User', { + name: String, + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: Number, + address: 'Address' }); + User.build(); + assert.equal(User.properties.name.type, String); + assert.equal(User.properties.bio.type, ModelBuilder.Text); + assert.equal(User.properties.approved.type, Boolean); + assert.equal(User.properties.joinedAt.type, Date); + assert.equal(User.properties.age.type, Number); + assert.equal(User.properties.address.type, Address); - it('should be able to define nesting models', function (done) { - var modelBuilder = new ModelBuilder(); + var json = User.toJSON(); + assert.equal(json.name, "User"); + assert.equal(json.properties.name.type, "String"); + assert.equal(json.properties.bio.type, "Text"); + assert.equal(json.properties.approved.type, "Boolean"); + assert.equal(json.properties.joinedAt.type, "Date"); + assert.equal(json.properties.age.type, "Number"); - var User = new ModelDefinition(modelBuilder, 'User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number, - address: { - street: String, - city: String, - zipCode: String, - state: String - } - }); + assert.equal(json.properties.address.type, 'Address'); - User.build(); - assert.equal(User.properties.name.type, String); - assert.equal(User.properties.bio.type, ModelBuilder.Text); - assert.equal(User.properties.approved.type, Boolean); - assert.equal(User.properties.joinedAt.type, Date); - assert.equal(User.properties.age.type, Number); - assert.equal(typeof User.properties.address.type, 'function'); + done(); - var json = User.toJSON(); - assert.equal(json.name, "User"); - assert.equal(json.properties.name.type, "String"); - assert.equal(json.properties.bio.type, "Text"); - assert.equal(json.properties.approved.type, "Boolean"); - assert.equal(json.properties.joinedAt.type, "Date"); - assert.equal(json.properties.age.type, "Number"); + }); - assert.deepEqual(json.properties.address.type, { street: { type: 'String' }, - city: { type: 'String' }, - zipCode: { type: 'String' }, - state: { type: 'String' } }); - - done(); + it('should report correct id names', function (done) { + var modelBuilder = new ModelBuilder(); + var User = new ModelDefinition(modelBuilder, 'User', { + userId: {type: String, id: true}, + name: "string", + bio: ModelBuilder.Text, + approved: Boolean, + joinedAt: Date, + age: "number" }); + assert.equal(User.idName(), 'userId'); + assert.deepEqual(User.idNames(), ['userId']); + done(); + }); - it('should be able to define referencing models', function (done) { - var modelBuilder = new ModelBuilder(); + it('should report correct table/column names', function (done) { + var modelBuilder = new ModelBuilder(); - var Address = modelBuilder.define('Address', { - street: String, - city: String, - zipCode: String, - state: String - }); - var User = new ModelDefinition(modelBuilder, 'User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number, - address: Address + var User = new ModelDefinition(modelBuilder, 'User', { + userId: {type: String, id: true, oracle: {column: 'ID'}}, + name: "string" + }, {oracle: {table: 'USER'}}); - }); - - User.build(); - assert.equal(User.properties.name.type, String); - assert.equal(User.properties.bio.type, ModelBuilder.Text); - assert.equal(User.properties.approved.type, Boolean); - assert.equal(User.properties.joinedAt.type, Date); - assert.equal(User.properties.age.type, Number); - assert.equal(User.properties.address.type, Address); - - - var json = User.toJSON(); - assert.equal(json.name, "User"); - assert.equal(json.properties.name.type, "String"); - assert.equal(json.properties.bio.type, "Text"); - assert.equal(json.properties.approved.type, "Boolean"); - assert.equal(json.properties.joinedAt.type, "Date"); - assert.equal(json.properties.age.type, "Number"); - - assert.equal(json.properties.address.type, 'Address'); - - done(); + assert.equal(User.tableName('oracle'), 'USER'); + assert.equal(User.tableName('mysql'), 'User'); + assert.equal(User.columnName('oracle', 'userId'), 'ID'); + assert.equal(User.columnName('mysql', 'userId'), 'userId'); + done(); + }); + it('should inherit prototype using option.base', function () { + var memory = new DataSource({connector: Memory}); + var modelBuilder = memory.modelBuilder; + var parent = memory.createModel('parent', {}, { + relations: { + children: { + type: 'hasMany', + model: 'anotherChild' + } + } }); + var baseChild = modelBuilder.define('baseChild'); + baseChild.attachTo(memory); + // the name of this must begin with a letter < b + // for this test to fail + var anotherChild = baseChild.extend('anotherChild'); - it('should be able to define referencing models by name', function (done) { - var modelBuilder = new ModelBuilder(); - - var Address = modelBuilder.define('Address', { - street: String, - city: String, - zipCode: String, - state: String - }); - var User = new ModelDefinition(modelBuilder, 'User', { - name: String, - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: Number, - address: 'Address' - - }); - - User.build(); - assert.equal(User.properties.name.type, String); - assert.equal(User.properties.bio.type, ModelBuilder.Text); - assert.equal(User.properties.approved.type, Boolean); - assert.equal(User.properties.joinedAt.type, Date); - assert.equal(User.properties.age.type, Number); - assert.equal(User.properties.address.type, Address); - - - var json = User.toJSON(); - assert.equal(json.name, "User"); - assert.equal(json.properties.name.type, "String"); - assert.equal(json.properties.bio.type, "Text"); - assert.equal(json.properties.approved.type, "Boolean"); - assert.equal(json.properties.joinedAt.type, "Date"); - assert.equal(json.properties.age.type, "Number"); - - assert.equal(json.properties.address.type, 'Address'); - - done(); - - }); - - it('should report correct id names', function (done) { - var modelBuilder = new ModelBuilder(); - - var User = new ModelDefinition(modelBuilder, 'User', { - userId: {type: String, id: true}, - name: "string", - bio: ModelBuilder.Text, - approved: Boolean, - joinedAt: Date, - age: "number" - }); - - assert.equal(User.idName(), 'userId'); - assert.deepEqual(User.idNames(), ['userId']); - done(); - }); - - it('should report correct table/column names', function (done) { - var modelBuilder = new ModelBuilder(); - - var User = new ModelDefinition(modelBuilder, 'User', { - userId: {type: String, id: true, oracle: {column: 'ID'}}, - name: "string" - }, {oracle: {table: 'USER'}}); - - assert.equal(User.tableName('oracle'), 'USER'); - assert.equal(User.tableName('mysql'), 'User'); - assert.equal(User.columnName('oracle', 'userId'), 'ID'); - assert.equal(User.columnName('mysql', 'userId'), 'userId'); - done(); - }); - - it('should inherit prototype using option.base', function () { - var memory = new DataSource({connector: Memory}); - var modelBuilder = memory.modelBuilder; - var parent = memory.createModel('parent', {}, { - relations: { - children: { - type: 'hasMany', - model: 'anotherChild' - } - } - }); - var baseChild = modelBuilder.define('baseChild'); - baseChild.attachTo(memory); - // the name of this must begin with a letter < b - // for this test to fail - var anotherChild = baseChild.extend('anotherChild'); - - assert(anotherChild.prototype instanceof baseChild); - }); + assert(anotherChild.prototype instanceof baseChild); + }); }); diff --git a/test/performance.coffee b/test/performance.coffee index bb91af50..7e8da156 100644 --- a/test/performance.coffee +++ b/test/performance.coffee @@ -4,80 +4,81 @@ Text = Schema.Text require('./spec_helper').init exports schemas = - neo4j: - url: 'http://localhost:7474/' - mongoose: - url: 'mongodb://localhost/test' - redis: {} - memory: {} - cradle: {} - nano: - url: 'http://localhost:5984/nano-test' + neo4j: + url: 'http://localhost:7474/' + mongoose: + url: 'mongodb://localhost/test' + redis: {} + memory: {} + cradle: {} + nano: + url: 'http://localhost:5984/nano-test' testOrm = (dataSource) -> + User = Post = 'unknown' + maxUsers = 100 + maxPosts = 50000 + users = [] - User = Post = 'unknown' - maxUsers = 100 - maxPosts = 50000 - users = [] + it 'should define simple', (test) -> + User = dataSource.define 'User', { + name: String, + bio: Text, + approved: Boolean, + joinedAt: Date, + age: Number + } - it 'should define simple', (test) -> + Post = dataSource.define 'Post', + title: { type: String, length: 255, index: true } + content: { type: Text } + date: { type: Date, detault: Date.now } + published: { type: Boolean, default: false } - User = dataSource.define 'User', { - name: String, - bio: Text, - approved: Boolean, - joinedAt: Date, - age: Number - } + User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}) + Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}) - Post = dataSource.define 'Post', - title: { type: String, length: 255, index: true } - content: { type: Text } - date: { type: Date, detault: Date.now } - published: { type: Boolean, default: false } + test.done() - User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}) - Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}) + it 'should create users', (test) -> + wait = maxUsers + done = (e, u) -> + users.push(u) + test.done() if --wait == 0 + User.create(done) for i in [1..maxUsers] - test.done() + it 'should create bunch of data', (test) -> + wait = maxPosts + done = -> + test.done() if --wait == 0 + rnd = (title) -> + { + userId: users[Math.floor(Math.random() * maxUsers)].id + title: 'Post number ' + (title % 5) + } + Post.create(rnd(num), done) for num in [1..maxPosts] - it 'should create users', (test) -> - wait = maxUsers - done = (e, u) -> - users.push(u) - test.done() if --wait == 0 - User.create(done) for i in [1..maxUsers] + it 'do some queries using foreign keys', (test) -> + wait = 4 + done = -> + test.done() if --wait == 0 + ts = Date.now() + query = (num) -> + users[num].posts { title: 'Post number 3' }, (err, collection) -> + console.log('User ' + num + ':', collection.length, 'posts in', + Date.now() - ts, 'ms') + done() + query num for num in [0..4] - it 'should create bunch of data', (test) -> - wait = maxPosts - done = -> test.done() if --wait == 0 - rnd = (title) -> - { - userId: users[Math.floor(Math.random() * maxUsers)].id - title: 'Post number ' + (title % 5) - } - Post.create(rnd(num), done) for num in [1..maxPosts] + return - it 'do some queries using foreign keys', (test) -> - wait = 4 - done = -> test.done() if --wait == 0 - ts = Date.now() - query = (num) -> - users[num].posts { title: 'Post number 3' }, (err, collection) -> - console.log('User ' + num + ':', collection.length, 'posts in', Date.now() - ts,'ms') - done() - query num for num in [0..4] - - return - - it 'should destroy all data', (test) -> - Post.destroyAll -> - User.destroyAll(test.done) + it 'should destroy all data', (test) -> + Post.destroyAll -> + User.destroyAll(test.done) Object.keys(schemas).forEach (schemaName) -> - return if process.env.ONLY && process.env.ONLY != schemaName - context schemaName, -> - dataSource = new Schema schemaName, schemas[schemaName] - testOrm(dataSource) + return if process.env.ONLY && process.env.ONLY != schemaName + context schemaName, -> + dataSource = new Schema schemaName, schemas[schemaName] + testOrm(dataSource) diff --git a/test/relations.test.js b/test/relations.test.js index 83a795dd..5aefe62d 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -3,251 +3,251 @@ var should = require('./init.js'); var db, Book, Chapter, Author, Reader; -describe('relations', function() { - before(function(done) { - db = getSchema(); - Book = db.define('Book', {name: String}); - Chapter = db.define('Chapter', {name: {type: String, index: true}}); - Author = db.define('Author', {name: String}); - Reader = db.define('Reader', {name: String}); +describe('relations', function () { + before(function (done) { + db = getSchema(); + Book = db.define('Book', {name: String}); + Chapter = db.define('Chapter', {name: {type: String, index: true}}); + Author = db.define('Author', {name: String}); + Reader = db.define('Reader', {name: String}); - db.automigrate(function() { - Book.destroyAll(function() { - Chapter.destroyAll(function() { - Author.destroyAll(function() { - Reader.destroyAll(done); - }); - }); - }); + db.automigrate(function () { + Book.destroyAll(function () { + Chapter.destroyAll(function () { + Author.destroyAll(function () { + Reader.destroyAll(done); + }); }); + }); + }); + }); + + after(function () { + db.disconnect(); + }); + + describe('hasMany', function () { + it('can be declared in different ways', function (done) { + Book.hasMany(Chapter); + Book.hasMany(Reader, {as: 'users'}); + Book.hasMany(Author, {foreignKey: 'projectId'}); + var b = new Book; + b.chapters.should.be.an.instanceOf(Function); + b.users.should.be.an.instanceOf(Function); + b.authors.should.be.an.instanceOf(Function); + Object.keys((new Chapter).toObject()).should.include('bookId'); + Object.keys((new Author).toObject()).should.include('projectId'); + + db.automigrate(done); }); - after(function() { - db.disconnect(); + it('can be declared in short form', function (done) { + Author.hasMany('readers'); + (new Author).readers.should.be.an.instanceOf(Function); + Object.keys((new Reader).toObject()).should.include('authorId'); + + db.autoupdate(done); }); - describe('hasMany', function() { - it('can be declared in different ways', function(done) { - Book.hasMany(Chapter); - Book.hasMany(Reader, {as: 'users'}); - Book.hasMany(Author, {foreignKey: 'projectId'}); - var b = new Book; - b.chapters.should.be.an.instanceOf(Function); - b.users.should.be.an.instanceOf(Function); - b.authors.should.be.an.instanceOf(Function); - Object.keys((new Chapter).toObject()).should.include('bookId'); - Object.keys((new Author).toObject()).should.include('projectId'); - - db.automigrate(done); - }); - - it('can be declared in short form', function(done) { - Author.hasMany('readers'); - (new Author).readers.should.be.an.instanceOf(Function); - Object.keys((new Reader).toObject()).should.include('authorId'); - - db.autoupdate(done); - }); - - it('should build record on scope', function(done) { - Book.create(function(err, book) { - var c = book.chapters.build(); - c.bookId.should.equal(book.id); - c.save(done); - }); - }); - - it('should create record on scope', function(done) { - Book.create(function(err, book) { - book.chapters.create(function(err, c) { - should.not.exist(err); - should.exist(c); - c.bookId.should.equal(book.id); - done(); - }); - }); - }); - - it.skip('should fetch all scoped instances', function(done) { - Book.create(function(err, book) { - book.chapters.create({name: 'a'}, function() { - book.chapters.create({name: 'z'}, function() { - book.chapters.create({name: 'c'}, function() { - fetch(book); - }); - }); - }); - }); - function fetch(book) { - book.chapters(function(err, ch) { - should.not.exist(err); - should.exist(ch); - ch.should.have.lengthOf(3); - - book.chapters({order: 'name DESC'}, function(e, c) { - should.not.exist(e); - should.exist(c); - c.shift().name.should.equal('z'); - c.pop().name.should.equal('a'); - done(); - }); - }); - } - }); - - it('should find scoped record', function(done) { - var id; - Book.create(function(err, book) { - book.chapters.create({name: 'a'}, function(err, ch) { - id = ch.id; - book.chapters.create({name: 'z'}, function() { - book.chapters.create({name: 'c'}, function() { - fetch(book); - }); - }); - }); - }); - - function fetch(book) { - book.chapters.findById(id, function(err, ch) { - should.not.exist(err); - should.exist(ch); - ch.id.should.equal(id); - done(); - }); - } - }); + it('should build record on scope', function (done) { + Book.create(function (err, book) { + var c = book.chapters.build(); + c.bookId.should.equal(book.id); + c.save(done); + }); }); - describe('belongsTo', function() { - var List, Item, Fear, Mind; - - it('can be declared in different ways', function() { - List = db.define('List', {name: String}); - Item = db.define('Item', {name: String}); - Fear = db.define('Fear'); - Mind = db.define('Mind'); - - // syntax 1 (old) - Item.belongsTo(List); - Object.keys((new Item).toObject()).should.include('listId'); - (new Item).list.should.be.an.instanceOf(Function); - - // syntax 2 (new) - Fear.belongsTo('mind'); - Object.keys((new Fear).toObject()).should.include('mindId'); - (new Fear).mind.should.be.an.instanceOf(Function); - // (new Fear).mind.build().should.be.an.instanceOf(Mind); + it('should create record on scope', function (done) { + Book.create(function (err, book) { + book.chapters.create(function (err, c) { + should.not.exist(err); + should.exist(c); + c.bookId.should.equal(book.id); + done(); }); - - it('can be used to query data', function(done) { - List.hasMany('todos', {model: Item}); - db.automigrate(function() { - List.create(function(e, list) { - should.not.exist(e); - should.exist(list); - list.todos.create(function(err, todo) { - todo.list(function(e, l) { - should.not.exist(e); - should.exist(l); - l.should.be.an.instanceOf(List); - todo.list().should.equal(l.id); - done(); - }); - }); - }); - }); - }); - - it('could accept objects when creating on scope', function(done) { - List.create(function(e, list) { - should.not.exist(e); - should.exist(list); - Item.create({list: list}, function(err, item) { - should.not.exist(err); - should.exist(item); - should.exist(item.listId); - item.listId.should.equal(list.id); - item.__cachedRelations.list.should.equal(list); - done(); - }); - }); - }); - + }); }); - describe('hasAndBelongsToMany', function() { - var Article, Tag, ArticleTag; - it('can be declared', function(done) { - Article = db.define('Article', {title: String}); - Tag = db.define('Tag', {name: String}); - Article.hasAndBelongsToMany('tags'); - ArticleTag = db.models.ArticleTag; - db.automigrate(function() { - Article.destroyAll(function() { - Tag.destroyAll(function() { - ArticleTag.destroyAll(done) - }); - }); + it.skip('should fetch all scoped instances', function (done) { + Book.create(function (err, book) { + book.chapters.create({name: 'a'}, function () { + book.chapters.create({name: 'z'}, function () { + book.chapters.create({name: 'c'}, function () { + fetch(book); }); + }); }); + }); + function fetch(book) { + book.chapters(function (err, ch) { + should.not.exist(err); + should.exist(ch); + ch.should.have.lengthOf(3); - it('should allow to create instances on scope', function(done) { - Article.create(function(e, article) { - article.tags.create({name: 'popular'}, function(e, t) { - t.should.be.an.instanceOf(Tag); - // console.log(t); - ArticleTag.findOne(function(e, at) { - should.exist(at); - at.tagId.toString().should.equal(t.id.toString()); - at.articleId.toString().should.equal(article.id.toString()); - done(); - }); - }); - }); + book.chapters({order: 'name DESC'}, function (e, c) { + should.not.exist(e); + should.exist(c); + c.shift().name.should.equal('z'); + c.pop().name.should.equal('a'); + done(); + }); }); - - it('should allow to fetch scoped instances', function(done) { - Article.findOne(function(e, article) { - article.tags(function(e, tags) { - should.not.exist(e); - should.exist(tags); - done(); - }); - }); - }); - - it('should allow to add connection with instance', function(done) { - Article.findOne(function(e, article) { - Tag.create({name: 'awesome'}, function(e, tag) { - article.tags.add(tag, function(e, at) { - should.not.exist(e); - should.exist(at); - at.should.be.an.instanceOf(ArticleTag); - at.tagId.should.equal(tag.id); - at.articleId.should.equal(article.id); - done(); - }); - }); - }); - }); - - it('should allow to remove connection with instance', function(done) { - Article.findOne(function(e, article) { - article.tags(function(e, tags) { - var len = tags.length; - tags.should.not.be.empty; - article.tags.remove(tags[0], function(e) { - should.not.exist(e); - article.tags(true, function(e, tags) { - tags.should.have.lengthOf(len - 1); - done(); - }); - }); - }); - }); - }); - + } }); + it('should find scoped record', function (done) { + var id; + Book.create(function (err, book) { + book.chapters.create({name: 'a'}, function (err, ch) { + id = ch.id; + book.chapters.create({name: 'z'}, function () { + book.chapters.create({name: 'c'}, function () { + fetch(book); + }); + }); + }); + }); + + function fetch(book) { + book.chapters.findById(id, function (err, ch) { + should.not.exist(err); + should.exist(ch); + ch.id.should.equal(id); + done(); + }); + } + }); + }); + + describe('belongsTo', function () { + var List, Item, Fear, Mind; + + it('can be declared in different ways', function () { + List = db.define('List', {name: String}); + Item = db.define('Item', {name: String}); + Fear = db.define('Fear'); + Mind = db.define('Mind'); + + // syntax 1 (old) + Item.belongsTo(List); + Object.keys((new Item).toObject()).should.include('listId'); + (new Item).list.should.be.an.instanceOf(Function); + + // syntax 2 (new) + Fear.belongsTo('mind'); + Object.keys((new Fear).toObject()).should.include('mindId'); + (new Fear).mind.should.be.an.instanceOf(Function); + // (new Fear).mind.build().should.be.an.instanceOf(Mind); + }); + + it('can be used to query data', function (done) { + List.hasMany('todos', {model: Item}); + db.automigrate(function () { + List.create(function (e, list) { + should.not.exist(e); + should.exist(list); + list.todos.create(function (err, todo) { + todo.list(function (e, l) { + should.not.exist(e); + should.exist(l); + l.should.be.an.instanceOf(List); + todo.list().should.equal(l.id); + done(); + }); + }); + }); + }); + }); + + it('could accept objects when creating on scope', function (done) { + List.create(function (e, list) { + should.not.exist(e); + should.exist(list); + Item.create({list: list}, function (err, item) { + should.not.exist(err); + should.exist(item); + should.exist(item.listId); + item.listId.should.equal(list.id); + item.__cachedRelations.list.should.equal(list); + done(); + }); + }); + }); + + }); + + describe('hasAndBelongsToMany', function () { + var Article, Tag, ArticleTag; + it('can be declared', function (done) { + Article = db.define('Article', {title: String}); + Tag = db.define('Tag', {name: String}); + Article.hasAndBelongsToMany('tags'); + ArticleTag = db.models.ArticleTag; + db.automigrate(function () { + Article.destroyAll(function () { + Tag.destroyAll(function () { + ArticleTag.destroyAll(done) + }); + }); + }); + }); + + it('should allow to create instances on scope', function (done) { + Article.create(function (e, article) { + article.tags.create({name: 'popular'}, function (e, t) { + t.should.be.an.instanceOf(Tag); + // console.log(t); + ArticleTag.findOne(function (e, at) { + should.exist(at); + at.tagId.toString().should.equal(t.id.toString()); + at.articleId.toString().should.equal(article.id.toString()); + done(); + }); + }); + }); + }); + + it('should allow to fetch scoped instances', function (done) { + Article.findOne(function (e, article) { + article.tags(function (e, tags) { + should.not.exist(e); + should.exist(tags); + done(); + }); + }); + }); + + it('should allow to add connection with instance', function (done) { + Article.findOne(function (e, article) { + Tag.create({name: 'awesome'}, function (e, tag) { + article.tags.add(tag, function (e, at) { + should.not.exist(e); + should.exist(at); + at.should.be.an.instanceOf(ArticleTag); + at.tagId.should.equal(tag.id); + at.articleId.should.equal(article.id); + done(); + }); + }); + }); + }); + + it('should allow to remove connection with instance', function (done) { + Article.findOne(function (e, article) { + article.tags(function (e, tags) { + var len = tags.length; + tags.should.not.be.empty; + article.tags.remove(tags[0], function (e) { + should.not.exist(e); + article.tags(true, function (e, tags) { + tags.should.have.lengthOf(len - 1); + done(); + }); + }); + }); + }); + }); + + }); + }); diff --git a/test/schema.test.js b/test/schema.test.js index 2f9be1a4..1f1caf7a 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -3,55 +3,55 @@ var should = require('./init.js'); var db = getSchema(), slave = getSchema(), Model, SlaveModel; -describe('dataSource', function() { +describe('dataSource', function () { - it('should define Model', function() { - Model = db.define('Model'); - Model.dataSource.should.eql(db); - var m = new Model; - m.getDataSource().should.eql(db); + it('should define Model', function () { + Model = db.define('Model'); + Model.dataSource.should.eql(db); + var m = new Model; + m.getDataSource().should.eql(db); + }); + + it('should clone existing model', function () { + SlaveModel = slave.copyModel(Model); + SlaveModel.dataSource.should.eql(slave); + slave.should.not.eql(db); + var sm = new SlaveModel; + sm.should.be.instanceOf(Model); + sm.getDataSource().should.not.eql(db); + sm.getDataSource().should.eql(slave); + }); + + it('should automigrate', function (done) { + db.automigrate(done); + }); + + it('should create transaction', function (done) { + var tr = db.transaction(); + tr.connected.should.be.false; + tr.connecting.should.be.false; + var called = false; + tr.models.Model.create(Array(3), function () { + called = true; }); + tr.connected.should.be.false; + tr.connecting.should.be.true; - it('should clone existing model', function() { - SlaveModel = slave.copyModel(Model); - SlaveModel.dataSource.should.eql(slave); - slave.should.not.eql(db); - var sm = new SlaveModel; - sm.should.be.instanceOf(Model); - sm.getDataSource().should.not.eql(db); - sm.getDataSource().should.eql(slave); - }); - - it('should automigrate', function(done) { - db.automigrate(done); - }); - - it('should create transaction', function(done) { - var tr = db.transaction(); - tr.connected.should.be.false; - tr.connecting.should.be.false; - var called = false; - tr.models.Model.create(Array(3), function () { - called = true; - }); - tr.connected.should.be.false; - tr.connecting.should.be.true; - - db.models.Model.count(function(err, c) { - should.not.exist(err); - should.exist(c); - c.should.equal(0); - called.should.be.false; - tr.exec(function () { - setTimeout(function() { - called.should.be.true; - db.models.Model.count(function(err, c) { - c.should.equal(3); - done(); - }); - }, 100); - }); - }); + db.models.Model.count(function (err, c) { + should.not.exist(err); + should.exist(c); + c.should.equal(0); + called.should.be.false; + tr.exec(function () { + setTimeout(function () { + called.should.be.true; + db.models.Model.count(function (err, c) { + c.should.equal(3); + done(); + }); + }, 100); + }); }); + }); }); diff --git a/test/scope.test.js b/test/scope.test.js index 8ea42bea..d927bcc7 100644 --- a/test/scope.test.js +++ b/test/scope.test.js @@ -3,62 +3,62 @@ var should = require('./init.js'); var db, Railway, Station; -describe('sc0pe', function() { +describe('sc0pe', function () { - before(function() { - db = getSchema(); - Railway = db.define('Railway', { - URID: {type: String, index: true} - }); - Station = db.define('Station', { - USID: {type: String, index: true}, - capacity: {type: Number, index: true}, - thoughput: {type: Number, index: true}, - isActive: {type: Boolean, index: true}, - isUndeground: {type: Boolean, index: true} - }); + before(function () { + db = getSchema(); + Railway = db.define('Railway', { + URID: {type: String, index: true} }); + Station = db.define('Station', { + USID: {type: String, index: true}, + capacity: {type: Number, index: true}, + thoughput: {type: Number, index: true}, + isActive: {type: Boolean, index: true}, + isUndeground: {type: Boolean, index: true} + }); + }); - beforeEach(function(done) { - Railway.destroyAll(function() { - Station.destroyAll(done); - }); + beforeEach(function (done) { + Railway.destroyAll(function () { + Station.destroyAll(done); }); + }); - it('should define scope with query', function(done) { - Station.scope('active', {where: {isActive: true}}); - Station.active.create(function(err, station) { - should.not.exist(err); - should.exist(station); - should.exist(station.isActive); - station.isActive.should.be.true; - done(); - }); + it('should define scope with query', function (done) { + Station.scope('active', {where: {isActive: true}}); + Station.active.create(function (err, station) { + should.not.exist(err); + should.exist(station); + should.exist(station.isActive); + station.isActive.should.be.true; + done(); }); + }); - it('should allow scope chaining', function(done) { - Station.scope('active', {where: {isActive: true}}); - Station.scope('subway', {where: {isUndeground: true}}); - Station.active.subway.create(function(err, station) { - should.not.exist(err); - should.exist(station); - station.isActive.should.be.true; - station.isUndeground.should.be.true; - done(); - }) - }); + it('should allow scope chaining', function (done) { + Station.scope('active', {where: {isActive: true}}); + Station.scope('subway', {where: {isUndeground: true}}); + Station.active.subway.create(function (err, station) { + should.not.exist(err); + should.exist(station); + station.isActive.should.be.true; + station.isUndeground.should.be.true; + done(); + }) + }); - it('should query all', function(done) { - Station.scope('active', {where: {isActive: true}}); - Station.scope('inactive', {where: {isActive: false}}); - Station.scope('ground', {where: {isUndeground: true}}); - Station.active.ground.create(function() { - Station.inactive.ground.create(function() { - Station.ground.inactive(function(err, ss) { - ss.should.have.lengthOf(1); - done(); - }); - }); + it('should query all', function (done) { + Station.scope('active', {where: {isActive: true}}); + Station.scope('inactive', {where: {isActive: false}}); + Station.scope('ground', {where: {isUndeground: true}}); + Station.active.ground.create(function () { + Station.inactive.ground.create(function () { + Station.ground.inactive(function (err, ss) { + ss.should.have.lengthOf(1); + done(); }); + }); }); + }); }); diff --git a/test/spec_helper.js b/test/spec_helper.js index f375b65d..24b1ea6b 100644 --- a/test/spec_helper.js +++ b/test/spec_helper.js @@ -1,56 +1,56 @@ /* -if (!process.env.TRAVIS) { - var semicov = require('semicov'); - semicov.init('lib', 'LoopbackData'); - process.on('exit', semicov.report); -} -*/ + if (!process.env.TRAVIS) { + var semicov = require('semicov'); + semicov.init('lib', 'LoopbackData'); + process.on('exit', semicov.report); + } + */ try { - global.sinon = require('sinon'); + global.sinon = require('sinon'); } catch (e) { - // ignore + // ignore } var group_name = false, EXT_EXP; function it(should, test_case) { - check_external_exports(); - if (group_name) { - EXT_EXP[group_name][should] = test_case; - } else { - EXT_EXP[should] = test_case; - } + check_external_exports(); + if (group_name) { + EXT_EXP[group_name][should] = test_case; + } else { + EXT_EXP[should] = test_case; + } } global.it = it; function context(name, tests) { - check_external_exports(); - EXT_EXP[name] = {}; - group_name = name; - tests({ - before: function (f) { - it('setUp', f); - }, - after: function (f) { - it('tearDown', f); - } - }); - group_name = false; + check_external_exports(); + EXT_EXP[name] = {}; + group_name = name; + tests({ + before: function (f) { + it('setUp', f); + }, + after: function (f) { + it('tearDown', f); + } + }); + group_name = false; } global.context = context; exports.init = function init(external_exports) { - EXT_EXP = external_exports; - if (external_exports.done) { - external_exports.done(); - } + EXT_EXP = external_exports; + if (external_exports.done) { + external_exports.done(); + } }; function check_external_exports() { - if (!EXT_EXP) throw new Error( - 'Before run this, please ensure that ' + - 'require("spec_helper").init(exports); called'); + if (!EXT_EXP) throw new Error( + 'Before run this, please ensure that ' + + 'require("spec_helper").init(exports); called'); } diff --git a/test/test1-schemas.json b/test/test1-schemas.json index 05dd5079..e18f0f89 100644 --- a/test/test1-schemas.json +++ b/test/test1-schemas.json @@ -1,15 +1,15 @@ { - "title": { - "type": "String" - }, - "author": { - "type": "String", - "default": "Raymond" - }, - "body": "String", - "date": { - "type": "Date" - }, - "hidden": "Boolean", - "comments": ["String"] + "title": { + "type": "String" + }, + "author": { + "type": "String", + "default": "Raymond" + }, + "body": "String", + "date": { + "type": "Date" + }, + "hidden": "Boolean", + "comments": ["String"] } \ No newline at end of file diff --git a/test/test2-schemas.json b/test/test2-schemas.json index 0ca01ff5..c8de32ba 100644 --- a/test/test2-schemas.json +++ b/test/test2-schemas.json @@ -1,83 +1,83 @@ [ - { - "name": "Address", - "properties": { - "label": "string", - "street": "string", - "city": "string", - "zipCode": "string" - } - }, - - { - "name": "Account", - "properties": { - "id": "string", - "customer": { - "type": "Customer", - "relation": { - "type": "belongsTo", - "as": "account" - } - }, - "balance": "number" - } - }, - - { - "name": "Customer", - "options": { - "oracle": { - "owner": "STRONGLOOP", - "table": "CUSTOMER" - } - }, - "properties": { - "id": { - "type": "number", - "id": true, - "doc": "Customer ID" - }, - "firstName": { - "type": "string", - "trim": true, - "required": true, - "oracle": { - "column": "FNAME", - "type": "VARCHAR", - "length": 32 - } - }, - "lastName": { - "type": "string", - "trim": true, - "required": true, - "oracle": { - "column": "LNAME", - "type": "VARCHAR", - "length": 32 - } - }, - "vip": { - "type": "boolean", - "doc": "indicate if the customer is a VIP", - "oracle": { - "column": "VIP", - "type": "CHAR", - "length": 1 - } - }, - "emails": [ - { - "type": "string", - "trim": true - } - ], - "address": { - "type": "Address" - }, - "account": "Account" - } + { + "name": "Address", + "properties": { + "label": "string", + "street": "string", + "city": "string", + "zipCode": "string" } + }, + + { + "name": "Account", + "properties": { + "id": "string", + "customer": { + "type": "Customer", + "relation": { + "type": "belongsTo", + "as": "account" + } + }, + "balance": "number" + } + }, + + { + "name": "Customer", + "options": { + "oracle": { + "owner": "STRONGLOOP", + "table": "CUSTOMER" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "doc": "Customer ID" + }, + "firstName": { + "type": "string", + "trim": true, + "required": true, + "oracle": { + "column": "FNAME", + "type": "VARCHAR", + "length": 32 + } + }, + "lastName": { + "type": "string", + "trim": true, + "required": true, + "oracle": { + "column": "LNAME", + "type": "VARCHAR", + "length": 32 + } + }, + "vip": { + "type": "boolean", + "doc": "indicate if the customer is a VIP", + "oracle": { + "column": "VIP", + "type": "CHAR", + "length": 1 + } + }, + "emails": [ + { + "type": "string", + "trim": true + } + ], + "address": { + "type": "Address" + }, + "account": "Account" + } + } ] \ No newline at end of file diff --git a/test/util.test.js b/test/util.test.js index 815a01e6..81de4a75 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -4,11 +4,9 @@ var fieldsToArray = utils.fieldsToArray; var removeUndefined = utils.removeUndefined; var mergeSettings = utils.mergeSettings; +describe('util.fieldsToArray', function () { + it('Turn objects and strings into an array of fields to include when finding models', function () { -describe('util.fieldsToArray', function(){ - it('Turn objects and strings into an array of fields to include when finding models', function() { - - function sample(fields) { var properties = ['foo', 'bar', 'bat', 'baz']; return { @@ -17,7 +15,7 @@ describe('util.fieldsToArray', function(){ } } } - + sample(false).expect(undefined); sample(null).expect(undefined); sample({}).expect(undefined); @@ -30,90 +28,90 @@ describe('util.fieldsToArray', function(){ }); }); -describe('util.removeUndefined', function(){ - it('Remove undefined values from the query object', function() { - var q1 = {where: {x: 1, y: undefined}}; - should.deepEqual(removeUndefined(q1), {where: {x: 1}}); +describe('util.removeUndefined', function () { + it('Remove undefined values from the query object', function () { + var q1 = {where: {x: 1, y: undefined}}; + should.deepEqual(removeUndefined(q1), {where: {x: 1}}); - var q2 = {where: {x: 1, y: 2}}; - should.deepEqual(removeUndefined(q2), {where: {x: 1, y: 2}}); + var q2 = {where: {x: 1, y: 2}}; + should.deepEqual(removeUndefined(q2), {where: {x: 1, y: 2}}); - var q3 = {where: {x: 1, y: {in: [2, undefined]}}}; - should.deepEqual(removeUndefined(q3), {where: {x: 1, y: {in: [2]}}}); + var q3 = {where: {x: 1, y: {in: [2, undefined]}}}; + should.deepEqual(removeUndefined(q3), {where: {x: 1, y: {in: [2]}}}); - should.equal(removeUndefined(null), null); + should.equal(removeUndefined(null), null); - should.equal(removeUndefined(undefined), undefined); + should.equal(removeUndefined(undefined), undefined); - should.equal(removeUndefined('x'), 'x'); + should.equal(removeUndefined('x'), 'x'); - var date = new Date(); - var q4 = {where: {x: 1, y: date}}; - should.deepEqual(removeUndefined(q4), {where: {x: 1, y: date}}); + var date = new Date(); + var q4 = {where: {x: 1, y: date}}; + should.deepEqual(removeUndefined(q4), {where: {x: 1, y: date}}); - }); + }); }); -describe('util.parseSettings', function(){ - it('Parse a full url into a settings object', function() { - var url = 'mongodb://x:y@localhost:27017/mydb?w=2'; - var settings = utils.parseSettings(url); - should.equal(settings.hostname, 'localhost'); - should.equal(settings.port, 27017); - should.equal(settings.host, 'localhost'); - should.equal(settings.user, 'x'); - should.equal(settings.password, 'y'); - should.equal(settings.database, 'mydb'); - should.equal(settings.connector, 'mongodb'); - should.equal(settings.w, '2'); - should.equal(settings.url, 'mongodb://x:y@localhost:27017/mydb?w=2'); +describe('util.parseSettings', function () { + it('Parse a full url into a settings object', function () { + var url = 'mongodb://x:y@localhost:27017/mydb?w=2'; + var settings = utils.parseSettings(url); + should.equal(settings.hostname, 'localhost'); + should.equal(settings.port, 27017); + should.equal(settings.host, 'localhost'); + should.equal(settings.user, 'x'); + should.equal(settings.password, 'y'); + should.equal(settings.database, 'mydb'); + should.equal(settings.connector, 'mongodb'); + should.equal(settings.w, '2'); + should.equal(settings.url, 'mongodb://x:y@localhost:27017/mydb?w=2'); - }); + }); - it('Parse a url without auth into a settings object', function() { - var url = 'mongodb://localhost:27017/mydb/abc?w=2'; - var settings = utils.parseSettings(url); - should.equal(settings.hostname, 'localhost'); - should.equal(settings.port, 27017); - should.equal(settings.host, 'localhost'); - should.equal(settings.user, undefined); - should.equal(settings.password, undefined); - should.equal(settings.database, 'mydb'); - should.equal(settings.connector, 'mongodb'); - should.equal(settings.w, '2'); - should.equal(settings.url, 'mongodb://localhost:27017/mydb/abc?w=2'); + it('Parse a url without auth into a settings object', function () { + var url = 'mongodb://localhost:27017/mydb/abc?w=2'; + var settings = utils.parseSettings(url); + should.equal(settings.hostname, 'localhost'); + should.equal(settings.port, 27017); + should.equal(settings.host, 'localhost'); + should.equal(settings.user, undefined); + should.equal(settings.password, undefined); + should.equal(settings.database, 'mydb'); + should.equal(settings.connector, 'mongodb'); + should.equal(settings.w, '2'); + should.equal(settings.url, 'mongodb://localhost:27017/mydb/abc?w=2'); - }); + }); - it('Parse a url with complex query into a settings object', function() { - var url = 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB'; - var settings = utils.parseSettings(url); - should.equal(settings.hostname, '127.0.0.1'); - should.equal(settings.port, 3306); - should.equal(settings.host, '127.0.0.1'); - should.equal(settings.user, undefined); - should.equal(settings.password, undefined); - should.equal(settings.database, 'mydb'); - should.equal(settings.connector, 'mysql'); - should.equal(settings.x.a, '1'); - should.equal(settings.x.b, '2'); - should.equal(settings.engine, 'InnoDB'); - should.equal(settings.url, 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB'); + it('Parse a url with complex query into a settings object', function () { + var url = 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB'; + var settings = utils.parseSettings(url); + should.equal(settings.hostname, '127.0.0.1'); + should.equal(settings.port, 3306); + should.equal(settings.host, '127.0.0.1'); + should.equal(settings.user, undefined); + should.equal(settings.password, undefined); + should.equal(settings.database, 'mydb'); + should.equal(settings.connector, 'mysql'); + should.equal(settings.x.a, '1'); + should.equal(settings.x.b, '2'); + should.equal(settings.engine, 'InnoDB'); + should.equal(settings.url, 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB'); - }); + }); - it('Parse a url without auth into a settings object', function() { - var url = 'memory://?x=1'; - var settings = utils.parseSettings(url); - should.equal(settings.hostname, ''); - should.equal(settings.user, undefined); - should.equal(settings.password, undefined); - should.equal(settings.database, undefined); - should.equal(settings.connector, 'memory'); - should.equal(settings.x, '1'); - should.equal(settings.url, 'memory://?x=1'); + it('Parse a url without auth into a settings object', function () { + var url = 'memory://?x=1'; + var settings = utils.parseSettings(url); + should.equal(settings.hostname, ''); + should.equal(settings.user, undefined); + should.equal(settings.password, undefined); + should.equal(settings.database, undefined); + should.equal(settings.connector, 'memory'); + should.equal(settings.x, '1'); + should.equal(settings.url, 'memory://?x=1'); - }); + }); }); diff --git a/test/validations.test.js b/test/validations.test.js index c54b46ec..50556a5e 100644 --- a/test/validations.test.js +++ b/test/validations.test.js @@ -5,198 +5,198 @@ var j = require('../'), db, User; var ValidationError = j.ValidationError; function getValidAttributes() { - return { - name: 'Anatoliy', - email: 'email@example.com', - state: '', - age: 26, - gender: 'male', - createdByAdmin: false, - createdByScript: true - }; + return { + name: 'Anatoliy', + email: 'email@example.com', + state: '', + age: 26, + gender: 'male', + createdByAdmin: false, + createdByScript: true + }; } -describe('validations', function() { +describe('validations', function () { + + before(function (done) { + db = getSchema(); + User = db.define('User', { + email: String, + name: String, + password: String, + state: String, + age: Number, + gender: String, + domain: String, + pendingPeriod: Number, + createdByAdmin: Boolean, + createdByScript: Boolean, + updatedAt: Date + }); + db.automigrate(done); + }); + + beforeEach(function (done) { + User.destroyAll(function () { + delete User._validations; + done(); + }); + }); + + after(function () { + db.disconnect(); + }); + + describe('commons', function () { + + describe('skipping', function () { + + it('should allow to skip using if: attribute', function () { + User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'}); + var user = new User; + user.createdByAdmin = true; + user.isValid().should.be.false; + user.errors.pendingPeriod.should.eql(['can\'t be blank']); + user.pendingPeriod = 1 + user.isValid().should.be.true; + }); - before(function(done) { - db = getSchema(); - User = db.define('User', { - email: String, - name: String, - password: String, - state: String, - age: Number, - gender: String, - domain: String, - pendingPeriod: Number, - createdByAdmin: Boolean, - createdByScript: Boolean, - updatedAt: Date - }); - db.automigrate(done); }); - beforeEach(function(done) { - User.destroyAll(function() { - delete User._validations; + describe('lifecycle', function () { + + it('should work on create', function (done) { + delete User._validations; + User.validatesPresenceOf('name'); + User.create(function (e, u) { + should.exist(e); + User.create({name: 'Valid'}, function (e, d) { + should.not.exist(e); done(); + }); }); - }); + }); - after(function() { - db.disconnect(); - }); - - describe('commons', function() { - - describe('skipping', function() { - - it('should allow to skip using if: attribute', function() { - User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'}); - var user = new User; - user.createdByAdmin = true; - user.isValid().should.be.false; - user.errors.pendingPeriod.should.eql(['can\'t be blank']); - user.pendingPeriod = 1 - user.isValid().should.be.true; + it('should work on update', function (done) { + delete User._validations; + User.validatesPresenceOf('name'); + User.create({name: 'Valid'}, function (e, d) { + d.updateAttribute('name', null, function (e) { + should.exist(e); + e.should.be.instanceOf(Error); + e.should.be.instanceOf(ValidationError); + d.updateAttribute('name', 'Vasiliy', function (e) { + should.not.exist(e); + done(); }); - + }) }); + }); - describe('lifecycle', function() { - - it('should work on create', function(done) { - delete User._validations; - User.validatesPresenceOf('name'); - User.create(function(e, u) { - should.exist(e); - User.create({name: 'Valid'}, function(e, d) { - should.not.exist(e); - done(); - }); - }); - }); - - it('should work on update', function(done) { - delete User._validations; - User.validatesPresenceOf('name'); - User.create({name: 'Valid'}, function(e, d) { - d.updateAttribute('name', null, function(e) { - should.exist(e); - e.should.be.instanceOf(Error); - e.should.be.instanceOf(ValidationError); - d.updateAttribute('name', 'Vasiliy', function(e) { - should.not.exist(e); - done(); - }); - }) - }); - }); - - it('should return error code', function(done) { - delete User._validations; - User.validatesPresenceOf('name'); - User.create(function(e, u) { - should.exist(e); - e.details.codes.name.should.eql(['presence']); - done(); - }); - }); - - it('should allow to modify error after validation', function(done) { - User.afterValidate = function(next) { - next(); - }; - done(); - }); - + it('should return error code', function (done) { + delete User._validations; + User.validatesPresenceOf('name'); + User.create(function (e, u) { + should.exist(e); + e.details.codes.name.should.eql(['presence']); + done(); }); + }); + + it('should allow to modify error after validation', function (done) { + User.afterValidate = function (next) { + next(); + }; + done(); + }); + + }); + }); + + describe('presence', function () { + + it('should validate presence', function () { + User.validatesPresenceOf('name', 'email'); + var u = new User; + u.isValid().should.not.be.true; + u.name = 1; + u.email = 2; + u.isValid().should.be.true; }); - describe('presence', function() { + it('should skip validation by property (if/unless)', function () { + User.validatesPresenceOf('domain', {unless: 'createdByScript'}); - it('should validate presence', function() { - User.validatesPresenceOf('name', 'email'); - var u = new User; - u.isValid().should.not.be.true; - u.name = 1; - u.email = 2; - u.isValid().should.be.true; + var user = new User(getValidAttributes()) + user.isValid().should.be.true; + + user.createdByScript = false; + user.isValid().should.be.false; + user.errors.domain.should.eql(['can\'t be blank']); + + user.domain = 'domain'; + user.isValid().should.be.true; + }); + + }); + + describe('uniqueness', function () { + it('should validate uniqueness', function (done) { + User.validatesUniquenessOf('email'); + var u = new User({email: 'hey'}); + Boolean(u.isValid(function (valid) { + valid.should.be.true; + u.save(function () { + var u2 = new User({email: 'hey'}); + u2.isValid(function (valid) { + valid.should.be.false; + done(); + }); }); + })).should.be.false; + }); - it('should skip validation by property (if/unless)', function() { - User.validatesPresenceOf('domain', {unless: 'createdByScript'}); - - var user = new User(getValidAttributes()) - user.isValid().should.be.true; - - user.createdByScript = false; - user.isValid().should.be.false; - user.errors.domain.should.eql(['can\'t be blank']); - - user.domain = 'domain'; - user.isValid().should.be.true; + it('should handle same object modification', function (done) { + User.validatesUniquenessOf('email'); + var u = new User({email: 'hey'}); + Boolean(u.isValid(function (valid) { + valid.should.be.true; + u.save(function () { + u.name = 'Goghi'; + u.isValid(function (valid) { + valid.should.be.true; + u.save(done); + }); }); - + // async validations always falsy when called as sync + })).should.not.be.ok; }); - describe('uniqueness', function() { - it('should validate uniqueness', function(done) { - User.validatesUniquenessOf('email'); - var u = new User({email: 'hey'}); - Boolean(u.isValid(function(valid) { - valid.should.be.true; - u.save(function() { - var u2 = new User({email: 'hey'}); - u2.isValid(function(valid) { - valid.should.be.false; - done(); - }); - }); - })).should.be.false; - }); + }); - it('should handle same object modification', function(done) { - User.validatesUniquenessOf('email'); - var u = new User({email: 'hey'}); - Boolean(u.isValid(function(valid) { - valid.should.be.true; - u.save(function() { - u.name = 'Goghi'; - u.isValid(function(valid) { - valid.should.be.true; - u.save(done); - }); - }); - // async validations always falsy when called as sync - })).should.not.be.ok; - }); + describe('format', function () { + it('should validate format'); + it('should overwrite default blank message with custom format message'); + }); - }); + describe('numericality', function () { + it('should validate numericality'); + }); - describe('format', function() { - it('should validate format'); - it('should overwrite default blank message with custom format message'); - }); + describe('inclusion', function () { + it('should validate inclusion'); + }); - describe('numericality', function() { - it('should validate numericality'); - }); + describe('exclusion', function () { + it('should validate exclusion'); + }); - describe('inclusion', function() { - it('should validate inclusion'); - }); + describe('length', function () { + it('should validate length'); + }); - describe('exclusion', function() { - it('should validate exclusion'); - }); - - describe('length', function() { - it('should validate length'); - }); - - describe('custom', function() { - it('should validate using custom sync validation'); - it('should validate using custom async validation'); - }); + describe('custom', function () { + it('should validate using custom sync validation'); + it('should validate using custom async validation'); + }); }); From 3cc51a2dff5b232d51d2791e566ab7395b02f871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 27 Jan 2014 10:14:15 +0100 Subject: [PATCH 6/6] v1.2.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7e86399b..a2ec7e96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-datasource-juggler", - "version": "1.2.12", + "version": "1.2.13", "description": "LoopBack DataSoure Juggler", "keywords": [ "StrongLoop",