Reformat the code

This commit is contained in:
Raymond Feng 2014-01-24 09:09:53 -08:00
parent 58a06272c3
commit 2b8c1ebaee
53 changed files with 8825 additions and 8792 deletions

View File

@ -8,56 +8,56 @@ var ds = new DataSource('memory');
var Application = ds.createModel('Schemaless', {}, {strict: false}); var Application = ds.createModel('Schemaless', {}, {strict: false});
var application = { var application = {
owner: 'rfeng', owner: 'rfeng',
name: 'MyApp1', name: 'MyApp1',
description: 'My first app', description: 'My first app',
pushSettings: [ pushSettings: [
{ "platform": "apns", { "platform": "apns",
"apns": { "apns": {
"pushOptions": { "pushOptions": {
"gateway": "gateway.sandbox.push.apple.com", "gateway": "gateway.sandbox.push.apple.com",
"cert": "credentials/apns_cert_dev.pem", "cert": "credentials/apns_cert_dev.pem",
"key": "credentials/apns_key_dev.pem" "key": "credentials/apns_key_dev.pem"
}, },
"feedbackOptions": { "feedbackOptions": {
"gateway": "feedback.sandbox.push.apple.com", "gateway": "feedback.sandbox.push.apple.com",
"cert": "credentials/apns_cert_dev.pem", "cert": "credentials/apns_cert_dev.pem",
"key": "credentials/apns_key_dev.pem", "key": "credentials/apns_key_dev.pem",
"batchFeedback": true, "batchFeedback": true,
"interval": 300 "interval": 300
} }
}} }}
]} ]}
console.log(new Application(application).toObject()); console.log(new Application(application).toObject());
Application.create(application, function (err, app1) { Application.create(application, function (err, app1) {
console.log('Created: ', app1.toObject()); console.log('Created: ', app1.toObject());
Application.findById(app1.id, function (err, app2) { Application.findById(app1.id, function (err, app2) {
console.log('Found: ', app2.toObject()); console.log('Found: ', app2.toObject());
}); });
}); });
// Instance JSON document // Instance JSON document
var user = { var user = {
name: 'Joe', name: 'Joe',
age: 30, age: 30,
birthday: new Date(), birthday: new Date(),
vip: true, vip: true,
address: { address: {
street: '1 Main St', street: '1 Main St',
city: 'San Jose', city: 'San Jose',
state: 'CA', state: 'CA',
zipcode: '95131', zipcode: '95131',
country: 'US' country: 'US'
}, },
friends: ['John', 'Mary'], friends: ['John', 'Mary'],
emails: [ emails: [
{label: 'work', id: 'x@sample.com'}, {label: 'work', id: 'x@sample.com'},
{label: 'home', id: 'x@home.com'} {label: 'home', id: 'x@home.com'}
], ],
tags: [] tags: []
}; };
// Introspect the JSON document to generate a schema // Introspect the JSON document to generate a schema
@ -72,10 +72,10 @@ var obj = new User(user);
console.log(obj.toObject()); console.log(obj.toObject());
User.create(user, function (err, u1) { User.create(user, function (err, u1) {
console.log('Created: ', u1.toObject()); console.log('Created: ', u1.toObject());
User.findById(u1.id, function (err, u2) { User.findById(u1.id, function (err, u2) {
console.log('Found: ', u2.toObject()); console.log('Found: ', u2.toObject());
}); });
}); });

View File

@ -2,27 +2,29 @@ var ModelBuilder = require('../../loopback-datasource-juggler').ModelBuilder;
var modelBuilder = new ModelBuilder(); var modelBuilder = new ModelBuilder();
// define models // define models
var Post = modelBuilder.define('Post', { var Post = modelBuilder.define('Post', {
title: { type: String, length: 255 }, title: { type: String, length: 255 },
content: { type: ModelBuilder.Text }, content: { type: ModelBuilder.Text },
date: { type: Date, default: function () { return new Date;} }, date: { type: Date, default: function () {
timestamp: { type: Number, default: Date.now }, return new Date();
published: { type: Boolean, default: false, index: true } } },
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', { var User = modelBuilder.define('User', {
name: String, name: String,
bio: ModelBuilder.Text, bio: ModelBuilder.Text,
approved: Boolean, approved: Boolean,
joinedAt: Date, joinedAt: Date,
age: Number age: Number
}); });
var Group = modelBuilder.define('Group', {group: String}); var Group = modelBuilder.define('Group', {group: String});
// define any custom method // define any custom method
User.prototype.getNameAndAge = function () { User.prototype.getNameAndAge = function () {
return this.name + ', ' + this.age; return this.name + ', ' + this.age;
}; };
var user = new User({name: 'Joe'}); var user = new User({name: 'Joe'});

View File

@ -4,29 +4,29 @@ var ds = new DataSource('memory');
// define models // define models
var Post = ds.define('Post', { var Post = ds.define('Post', {
title: { type: String, length: 255 }, title: { type: String, length: 255 },
content: { type: DataSource.Text }, content: { type: DataSource.Text },
date: { type: Date, default: function () { date: { type: Date, default: function () {
return new Date; return new Date;
} }, } },
timestamp: { type: Number, default: Date.now }, timestamp: { type: Number, default: Date.now },
published: { type: Boolean, default: false, index: true } published: { type: Boolean, default: false, index: true }
}); });
// simplier way to describe model // simplier way to describe model
var User = ds.define('User', { var User = ds.define('User', {
name: String, name: String,
bio: DataSource.Text, bio: DataSource.Text,
approved: Boolean, approved: Boolean,
joinedAt: Date, joinedAt: Date,
age: Number age: Number
}); });
var Group = ds.define('Group', {name: String}); var Group = ds.define('Group', {name: String});
// define any custom method // define any custom method
User.prototype.getNameAndAge = function () { User.prototype.getNameAndAge = function () {
return this.name + ', ' + this.age; return this.name + ', ' + this.age;
}; };
var user = new User({name: 'Joe'}); var user = new User({name: 'Joe'});
@ -53,48 +53,48 @@ User.hasAndBelongsToMany('groups');
var user2 = new User({name: 'Smith'}); var user2 = new User({name: 'Smith'});
user2.save(function (err) { user2.save(function (err) {
console.log(user2); console.log(user2);
var post = user2.posts.build({title: 'Hello world'}); var post = user2.posts.build({title: 'Hello world'});
post.save(function(err, data) { post.save(function (err, data) {
console.log(err ? err: data); console.log(err ? err : data);
}); });
}); });
Post.findOne({where: {published: false}, order: 'date DESC'}, function (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) { User.create({name: 'Jeff'}, function (err, data) {
if (err) { if (err) {
console.log(err); console.log(err);
return; return;
} }
console.log(data); console.log(data);
var post = data.posts.build({title: 'My Post'}); var post = data.posts.build({title: 'My Post'});
console.log(post); console.log(post);
}); });
User.create({name: 'Ray'}, function (err, data) { User.create({name: 'Ray'}, function (err, data) {
console.log(data); console.log(data);
}); });
User.scope('minors', {age: {le: 16}}); User.scope('minors', {age: {le: 16}});
User.minors(function(err, kids) { User.minors(function (err, kids) {
console.log('Kids: ', kids); console.log('Kids: ', kids);
}); });
var Article = ds.define('Article', {title: String}); var Article = ds.define('Article', {title: String});
var Tag = ds.define('Tag', {name: String}); var Tag = ds.define('Tag', {name: String});
Article.hasAndBelongsToMany('tags'); Article.hasAndBelongsToMany('tags');
Article.create(function(e, article) { Article.create(function (e, article) {
article.tags.create({name: 'popular'}, function (err, data) { article.tags.create({name: 'popular'}, function (err, data) {
Article.findOne(function(e, article) { Article.findOne(function (e, article) {
article.tags(function(e, tags) { article.tags(function (e, tags) {
console.log(tags); console.log(tags);
}); });
});
}); });
});
}); });
// should be able to attach a data source to an existing model // should be able to attach a data source to an existing model

View File

@ -1,15 +1,15 @@
{ {
"title": { "title": {
"type": "String" "type": "String"
}, },
"author": { "author": {
"type": "String", "type": "String",
"default": "Raymond" "default": "Raymond"
}, },
"body": "String", "body": "String",
"date": { "date": {
"type": "Date" "type": "Date"
}, },
"hidden": "Boolean", "hidden": "Boolean",
"comments": ["String"] "comments": ["String"]
} }

View File

@ -1,6 +1,6 @@
var path = require('path'), var path = require('path'),
fs = require('fs'), fs = require('fs'),
DataSource = require('../lib/datasource').DataSource; DataSource = require('../lib/datasource').DataSource;
/** /**
* Load LDL schemas from a json doc * Load LDL schemas from a json doc
@ -8,27 +8,27 @@ var path = require('path'),
* @returns A map of schemas keyed by name * @returns A map of schemas keyed by name
*/ */
function loadSchemasSync(schemaFile, dataSource) { function loadSchemasSync(schemaFile, dataSource) {
// Set up the data source // Set up the data source
if(!dataSource) { if (!dataSource) {
dataSource = new DataSource('memory'); dataSource = new DataSource('memory');
} }
// Read the dataSource JSON file // Read the dataSource JSON file
var schemas = JSON.parse(fs.readFileSync(schemaFile)); var schemas = JSON.parse(fs.readFileSync(schemaFile));
return dataSource.buildModels(schemas); return dataSource.buildModels(schemas);
} }
var models = loadSchemasSync(path.join(__dirname, 'jdb-schemas.json')); var models = loadSchemasSync(path.join(__dirname, 'jdb-schemas.json'));
for (var s in models) { for (var s in models) {
var m = models[s]; var m = models[s];
console.log(m.modelName, new m()); console.log(m.modelName, new m());
} }
models = loadSchemasSync(path.join(__dirname, 'schemas.json')); models = loadSchemasSync(path.join(__dirname, 'schemas.json'));
for (var s in models) { for (var s in models) {
var m = models[s]; var m = models[s];
console.log(m.modelName, new m()); console.log(m.modelName, new m());
} }

View File

@ -3,26 +3,30 @@ var modelBuilder = new ModelBuilder();
// simplier way to describe model // simplier way to describe model
var User = modelBuilder.define('User', { var User = modelBuilder.define('User', {
name: String, name: String,
bio: ModelBuilder.Text, bio: ModelBuilder.Text,
approved: Boolean, approved: Boolean,
joinedAt: Date, joinedAt: Date,
age: Number, age: Number,
address: { address: {
street: String, street: String,
city: String, city: String,
state: String, state: String,
zipCode: String, zipCode: String,
country: String country: String
}, },
emails: [{ emails: [
label: String, {
email: String label: String,
}], email: String
friends: [String] }
],
friends: [String]
}); });
var user = new User({name: 'Joe', age: 20, address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}, 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'}], emails: [
friends: ['John', 'Mary']}); {label: 'work', email: 'xyz@sample.com'}
],
friends: ['John', 'Mary']});
console.log(user.toObject()); console.log(user.toObject());

View File

@ -2,58 +2,56 @@ var DataSource = require('../index').DataSource;
var ds = new DataSource('memory'); var ds = new DataSource('memory');
var Order = ds.createModel('Order', { var Order = ds.createModel('Order', {
customerId: Number, customerId: Number,
orderDate: Date orderDate: Date
}); });
var Customer = ds.createModel('Customer', { var Customer = ds.createModel('Customer', {
name: String name: String
}); });
Order.belongsTo(Customer); Order.belongsTo(Customer);
Customer.create({name: 'John'}, function (err, customer) { Customer.create({name: 'John'}, function (err, customer) {
Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) { Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) {
order.customer(console.log); order.customer(console.log);
order.customer(true, console.log); order.customer(true, console.log);
Customer.create({name: 'Mary'}, function (err, customer2) { Customer.create({name: 'Mary'}, function (err, customer2) {
order.customer(customer2); order.customer(customer2);
order.customer(console.log); order.customer(console.log);
});
}); });
});
}); });
Customer.hasMany(Order, {as: 'orders', foreignKey: 'customerId'}); Customer.hasMany(Order, {as: 'orders', foreignKey: 'customerId'});
Customer.create({name: 'Ray'}, function (err, customer) { Customer.create({name: 'Ray'}, function (err, customer) {
Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) { Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) {
customer.orders(console.log); customer.orders(console.log);
customer.orders.create({orderDate: new Date()}, function(err, order) { customer.orders.create({orderDate: new Date()}, function (err, order) {
console.log(order); console.log(order);
Customer.include([customer], 'orders', function(err, results) { Customer.include([customer], 'orders', function (err, results) {
console.log('Results: ', results); console.log('Results: ', results);
}); });
customer.orders.findById('2', console.log); customer.orders.findById('2', console.log);
customer.orders.destroy('2', console.log); customer.orders.destroy('2', console.log);
});
}); });
});
}); });
var Physician = ds.createModel('Physician', { var Physician = ds.createModel('Physician', {
name: String name: String
}); });
var Patient = ds.createModel('Patient', { var Patient = ds.createModel('Patient', {
name: String name: String
}); });
var Appointment = ds.createModel('Appointment', { var Appointment = ds.createModel('Appointment', {
physicianId: Number, physicianId: Number,
patientId: Number, patientId: Number,
appointmentDate: Date appointmentDate: Date
}); });
Appointment.belongsTo(Patient); Appointment.belongsTo(Patient);
@ -63,33 +61,32 @@ Physician.hasMany(Patient, {through: Appointment});
Patient.hasMany(Physician, {through: Appointment}); Patient.hasMany(Physician, {through: Appointment});
Physician.create({name: 'Smith'}, function (err, physician) { Physician.create({name: 'Smith'}, function (err, physician) {
Patient.create({name: 'Mary'}, function (err, patient) { Patient.create({name: 'Mary'}, function (err, patient) {
Appointment.create({appointmentDate: new Date(), physicianId: physician.id, patientId: patient.id}, Appointment.create({appointmentDate: new Date(), physicianId: physician.id, patientId: patient.id},
function (err, appt) { function (err, appt) {
physician.patients(console.log); physician.patients(console.log);
patient.physicians(console.log); patient.physicians(console.log);
}); });
}); });
}); });
var Assembly = ds.createModel('Assembly', { var Assembly = ds.createModel('Assembly', {
name: String name: String
}); });
var Part = ds.createModel('Part', { var Part = ds.createModel('Part', {
partNumber: String partNumber: String
}); });
Assembly.hasAndBelongsToMany(Part); Assembly.hasAndBelongsToMany(Part);
Part.hasAndBelongsToMany(Assembly); Part.hasAndBelongsToMany(Assembly);
Assembly.create({name: 'car'}, function (err, assembly) { Assembly.create({name: 'car'}, function (err, assembly) {
Part.create({partNumber: 'engine'}, function (err, part) { Part.create({partNumber: 'engine'}, function (err, part) {
assembly.parts.add(part, function(err) { assembly.parts.add(part, function (err) {
assembly.parts(console.log); assembly.parts(console.log);
});
}); });
});
}); });

View File

@ -1,83 +1,83 @@
[ [
{ {
"name": "Address", "name": "Address",
"properties": { "properties": {
"label": "string", "label": "string",
"street": "string", "street": "string",
"city": "string", "city": "string",
"zipCode": "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": "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"
}
}
] ]

View File

@ -6,9 +6,9 @@ module.exports = Connector;
* @constructor * @constructor
*/ */
function Connector(name, settings) { function Connector(name, settings) {
this._models = {}; this._models = {};
this.name = name; this.name = name;
this.settings = settings || {}; this.settings = settings || {};
} }
/** /**
@ -24,21 +24,20 @@ Connector.prototype.relational = false;
* @param {Function} [callback] The callback function * @param {Function} [callback] The callback function
*/ */
Connector.prototype.execute = function (command, params, callback) { 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 * Look up the data source by model name
* @param {String} model The model name * @param {String} model The model name
* @returns {DataSource} The data source * @returns {DataSource} The data source
*/ */
Connector.prototype.getDataSource = function(model) { Connector.prototype.getDataSource = function (model) {
var m = this._models[model]; var m = this._models[model];
if(!m) { if (!m) {
console.trace('Model not found: ' + model); console.trace('Model not found: ' + model);
} }
return m && m.model.dataSource; return m && m.model.dataSource;
}; };
/** /**
@ -47,7 +46,7 @@ Connector.prototype.getDataSource = function(model) {
* @returns {String} The id property name * @returns {String} The id property name
*/ */
Connector.prototype.idName = function (model) { 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 * @returns {[String]} The id property names
*/ */
Connector.prototype.idNames = function (model) { 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) * Get the id index (sequence number, starting from 1)
* @param {String} model The model name * @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 * @returns {Number} The id index, undefined if the property is not part of the primary key
*/ */
Connector.prototype.id = function (model, prop) { Connector.prototype.id = function (model, prop) {
var p = this._models[model].properties[prop]; var p = this._models[model].properties[prop];
if(!p) { if (!p) {
console.trace('Property not found: ' + model +'.' + prop); console.trace('Property not found: ' + model + '.' + prop);
} }
return p.id; return p.id;
}; };
/** /**
@ -79,10 +77,10 @@ Connector.prototype.id = function (model, prop) {
* @param {Object} modelDefinition The model definition * @param {Object} modelDefinition The model definition
*/ */
Connector.prototype.define = function (modelDefinition) { Connector.prototype.define = function (modelDefinition) {
if (!modelDefinition.settings) { if (!modelDefinition.settings) {
modelDefinition.settings = {}; modelDefinition.settings = {};
} }
this._models[modelDefinition.model.modelName] = modelDefinition; this._models[modelDefinition.model.modelName] = modelDefinition;
}; };
/** /**
@ -92,14 +90,14 @@ Connector.prototype.define = function (modelDefinition) {
* @param {Object} propertyDefinition The object for property metadata * @param {Object} propertyDefinition The object for property metadata
*/ */
Connector.prototype.defineProperty = function (model, propertyName, propertyDefinition) { Connector.prototype.defineProperty = function (model, propertyName, propertyDefinition) {
this._models[model].properties[propertyName] = propertyDefinition; this._models[model].properties[propertyName] = propertyDefinition;
}; };
/** /**
* Disconnect from the connector * Disconnect from the connector
*/ */
Connector.prototype.disconnect = function disconnect(cb) { Connector.prototype.disconnect = function disconnect(cb) {
// NO-OP // NO-OP
}; };
/** /**
@ -109,8 +107,8 @@ Connector.prototype.disconnect = function disconnect(cb) {
* @returns {*} The id value * @returns {*} The id value
* *
*/ */
Connector.prototype.getIdValue = function(model, data) { Connector.prototype.getIdValue = function (model, data) {
return data && data[this.idName(model)]; return data && data[this.idName(model)];
}; };
/** /**
@ -120,10 +118,14 @@ Connector.prototype.getIdValue = function(model, data) {
* @param {*} value The id value * @param {*} value The id value
* *
*/ */
Connector.prototype.setIdValue = function(model, data, value) { Connector.prototype.setIdValue = function (model, data, value) {
if(data) { if (data) {
data[this.idName(model)] = value; data[this.idName(model)] = value;
} }
};
Connector.prototype.getType = function () {
return this.type;
}; };

View File

@ -9,328 +9,335 @@ var cradle = safeRequire('cradle');
* Private functions for internal use * Private functions for internal use
*/ */
function CradleAdapter(client) { function CradleAdapter(client) {
this._models = {}; this._models = {};
this.client = client; this.client = client;
} }
function createdbif(client, callback) { function createdbif(client, callback) {
client.exists(function (err, exists) { client.exists(function (err, exists) {
if(err) callback(err); if (err) callback(err);
if (!exists) { client.create(function() { callback(); }); } if (!exists) {
else { callback(); } client.create(function () {
}); callback();
});
}
else {
callback();
}
});
} }
function naturalize(data, model) { function naturalize(data, model) {
data.nature = model; data.nature = model;
//TODO: maybe this is not a really good idea //TODO: maybe this is not a really good idea
if(data.date) data.date = data.date.toString(); if (data.date) data.date = data.date.toString();
return data; return data;
} }
function idealize(data) { function idealize(data) {
data.id = data._id; data.id = data._id;
return data; return data;
} }
function stringify(data) { function stringify(data) {
return data ? data.toString() : data return data ? data.toString() : data
} }
function errorHandler(callback, func) { function errorHandler(callback, func) {
return function(err, res) { return function (err, res) {
if (err) { if (err) {
console.log('cradle', err); console.log('cradle', err);
callback(err); callback(err);
} else { } else {
if(func) { if (func) {
func(res, function(res) { func(res, function (res) {
callback(null, res); callback(null, res);
}); });
} else { } else {
callback(null, res); callback(null, res);
} }
}
} }
}
}; };
function synchronize(functions, args, callback) { function synchronize(functions, args, callback) {
if(functions.length === 0) callback(); if (functions.length === 0) callback();
if(functions.length > 0 && args.length === functions.length) { if (functions.length > 0 && args.length === functions.length) {
functions[0](args[0][0], args[0][1], function(err, res) { functions[0](args[0][0], args[0][1], function (err, res) {
if(err) callback(err); if (err) callback(err);
functions.splice(0, 1); functions.splice(0, 1);
args.splice(0, 1); args.splice(0, 1);
synchronize(functions, args, callback); synchronize(functions, args, callback);
}); });
} }
}; };
function applyFilter(filter) { function applyFilter(filter) {
if (typeof filter.where === 'function') { if (typeof filter.where === 'function') {
return filter.where; return filter.where;
} }
var keys = Object.keys(filter.where); var keys = Object.keys(filter.where);
return function (obj) { return function (obj) {
var pass = true; var pass = true;
keys.forEach(function (key) { keys.forEach(function (key) {
if (!test(filter.where[key], obj[key])) { if (!test(filter.where[key], obj[key])) {
pass = false; pass = false;
} }
}); });
return pass; return pass;
} }
function test(example, value) { function test(example, value) {
if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { if (typeof value === 'string' && example && example.constructor.name === 'RegExp') {
return value.match(example); return value.match(example);
}
// not strict equality
return example == value;
} }
// not strict equality
return example == value;
}
} }
function numerically(a, b) { function numerically(a, b) {
return a[this[0]] - b[this[0]]; return a[this[0]] - b[this[0]];
} }
function literally(a, b) { function literally(a, b) {
return a[this[0]] > b[this[0]]; return a[this[0]] > b[this[0]];
} }
function filtering(res, model, filter, instance) { function filtering(res, model, filter, instance) {
if(model) { if (model) {
if(filter == null) filter = {}; if (filter == null) filter = {};
if(filter.where == null) filter.where = {}; if (filter.where == null) filter.where = {};
filter.where.nature = model; filter.where.nature = model;
} }
// do we need some filtration? // do we need some filtration?
if (filter.where) { if (filter.where) {
res = res ? res.filter(applyFilter(filter)) : res; res = res ? res.filter(applyFilter(filter)) : res;
} }
// do we need some sorting? // do we need some sorting?
if (filter.order) { if (filter.order) {
var props = instance[model].properties; var props = instance[model].properties;
var allNumeric = true; var allNumeric = true;
var orders = filter.order; var orders = filter.order;
var reverse = false; var reverse = false;
if (typeof filter.order === "string") { if (typeof filter.order === "string") {
orders = [filter.order]; orders = [filter.order];
} }
orders.forEach(function (key, i) { orders.forEach(function (key, i) {
var m = key.match(/\s+(A|DE)SC$/i); var m = key.match(/\s+(A|DE)SC$/i);
if (m) { if (m) {
key = key.replace(/\s+(A|DE)SC/i, ''); key = key.replace(/\s+(A|DE)SC/i, '');
if (m[1] === 'DE') reverse = true; 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));
} }
if (reverse) res = res.reverse(); orders[i] = key;
} if (props[key].type.name !== 'Number') {
return res; 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 * Connection/Disconnection
*/ */
exports.initialize = function(dataSource, callback) { exports.initialize = function (dataSource, callback) {
if (!cradle) return; if (!cradle) return;
// when using cradle if we dont wait for the dataSource to be connected, the models fails to load correctly. // when using cradle if we dont wait for the dataSource to be connected, the models fails to load correctly.
dataSource.waitForConnect = true; dataSource.waitForConnect = true;
if (!dataSource.settings.url) { if (!dataSource.settings.url) {
var host = dataSource.settings.host || 'localhost'; var host = dataSource.settings.host || 'localhost';
var port = dataSource.settings.port || '5984'; var port = dataSource.settings.port || '5984';
var options = dataSource.settings.options || { var options = dataSource.settings.options || {
cache: true, cache: true,
raw: false raw: false
}; };
if (dataSource.settings.username) { if (dataSource.settings.username) {
options.auth = {}; options.auth = {};
options.auth.username = dataSource.settings.username; options.auth.username = dataSource.settings.username;
if (dataSource.settings.password) { if (dataSource.settings.password) {
options.auth.password = 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;
} }
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.settings.host = host;
dataSource.client, dataSource.settings.port = port;
errorHandler(callback, function() { dataSource.settings.database = database;
dataSource.connector = new CradleAdapter(dataSource.client); dataSource.settings.options = options;
process.nextTick(callback); }
})); 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 * Write methods
*/ */
CradleAdapter.prototype.define = function(descr) { CradleAdapter.prototype.define = function (descr) {
this._models[descr.model.modelName] = descr; this._models[descr.model.modelName] = descr;
}; };
CradleAdapter.prototype.create = function(model, data, callback) { CradleAdapter.prototype.create = function (model, data, callback) {
this.client.save( this.client.save(
stringify(data.id), stringify(data.id),
naturalize(data, model), naturalize(data, model),
errorHandler(callback, function(res, cb) { errorHandler(callback, function (res, cb) {
cb(res.id); cb(res.id);
}) })
); );
}; };
CradleAdapter.prototype.save = function(model, data, callback) { CradleAdapter.prototype.save = function (model, data, callback) {
this.client.save( this.client.save(
stringify(data.id), stringify(data.id),
naturalize(data, model), naturalize(data, model),
errorHandler(callback) errorHandler(callback)
) )
}; };
CradleAdapter.prototype.updateAttributes = function(model, id, data, callback) { CradleAdapter.prototype.updateAttributes = function (model, id, data, callback) {
this.client.merge( this.client.merge(
stringify(id), stringify(id),
data, data,
errorHandler(callback, function(doc, cb) { errorHandler(callback, function (doc, cb) {
cb(idealize(doc)); cb(idealize(doc));
}) })
); );
}; };
CradleAdapter.prototype.updateOrCreate = function(model, data, callback) { CradleAdapter.prototype.updateOrCreate = function (model, data, callback) {
this.client.get( this.client.get(
stringify(data.id), stringify(data.id),
function (err, doc) { function (err, doc) {
if(err) { if (err) {
this.create(model, data, callback); this.create(model, data, callback);
} else { } else {
this.updateAttributes(model, data.id, data, callback); this.updateAttributes(model, data.id, data, callback);
} }
}.bind(this) }.bind(this)
) )
}; };
/** /**
* Read methods * Read methods
*/ */
CradleAdapter.prototype.exists = function(model, id, callback) { CradleAdapter.prototype.exists = function (model, id, callback) {
this.client.get( this.client.get(
stringify(id), stringify(id),
errorHandler(callback, function(doc, cb) { errorHandler(callback, function (doc, cb) {
cb(!!doc); cb(!!doc);
}) })
); );
}; };
CradleAdapter.prototype.find = function(model, id, callback) { CradleAdapter.prototype.find = function (model, id, callback) {
this.client.get( this.client.get(
stringify(id), stringify(id),
errorHandler(callback, function(doc, cb) { errorHandler(callback, function (doc, cb) {
cb(idealize(doc)); cb(idealize(doc));
}) })
); );
}; };
CradleAdapter.prototype.count = function(model, callback, where) { CradleAdapter.prototype.count = function (model, callback, where) {
this.models( this.models(
model, model,
{where: where}, {where: where},
callback, callback,
function(docs, cb) { function (docs, cb) {
cb(docs.length); 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;
} }
);
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) { CradleAdapter.prototype.models = function (model, filter, callback, func) {
this.models( var limit = 200;
model, var skip = 0;
filter, if (filter != null) {
callback 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 * Detroy methods
*/ */
CradleAdapter.prototype.destroy = function(model, id, callback) { CradleAdapter.prototype.destroy = function (model, id, callback) {
this.client.remove( this.client.remove(
stringify(id), stringify(id),
function (err, doc) { function (err, doc) {
callback(err); callback(err);
} }
); );
}; };
CradleAdapter.prototype.destroyAll = function(model, callback) { CradleAdapter.prototype.destroyAll = function (model, callback) {
this.models( this.models(
model, model,
null, null,
callback, callback,
function(docs, cb) { function (docs, cb) {
var docIds = docs.map(function(doc) { var docIds = docs.map(function (doc) {
return doc.id; return doc.id;
}); });
this.client.get(docIds, function(err, res) { this.client.get(docIds, function (err, res) {
if(err) cb(err); if (err) cb(err);
var funcs = res.map(function(doc) { var funcs = res.map(function (doc) {
return this.client.remove.bind(this.client); return this.client.remove.bind(this.client);
}.bind(this)); }.bind(this));
var args = res.map(function(doc) { var args = res.map(function (doc) {
return [doc._id, doc._rev]; return [doc._id, doc._rev];
}); });
synchronize(funcs, args, cb); synchronize(funcs, args, cb);
}.bind(this)); }.bind(this));
}.bind(this) }.bind(this)
); );
}; };

View File

@ -1,188 +1,190 @@
exports.initialize = function initializeSchema(dataSource, callback) { exports.initialize = function initializeSchema(dataSource, callback) {
dataSource.connector = new WebService(); dataSource.connector = new WebService();
process.nextTick(callback); process.nextTick(callback);
}; };
function WebService() { function WebService() {
this._models = {}; this._models = {};
this.cache = {}; this.cache = {};
this.ids = {}; this.ids = {};
} }
WebService.prototype.installPostProcessor = function installPostProcessor(descr) { WebService.prototype.installPostProcessor = function installPostProcessor(descr) {
var dates = []; var dates = [];
Object.keys(descr.properties).forEach(function(column) { Object.keys(descr.properties).forEach(function (column) {
if (descr.properties[column].type.name === 'Date') { if (descr.properties[column].type.name === 'Date') {
dates.push(column); dates.push(column);
} }
}); });
var postProcessor = function(model) { var postProcessor = function (model) {
var max = dates.length; var max = dates.length;
for (var i = 0; i < max; i++) { for (var i = 0; i < max; i++) {
var column = dates[i]; var column = dates[i];
if (model[column]) { if (model[column]) {
model[column] = new Date(model[column]); model[column] = new Date(model[column]);
} }
}; }
}; ;
};
descr.postProcessor = postProcessor; descr.postProcessor = postProcessor;
}; };
WebService.prototype.preProcess = function preProcess(data) { WebService.prototype.preProcess = function preProcess(data) {
var result = {}; var result = {};
Object.keys(data).forEach(function(key) { Object.keys(data).forEach(function (key) {
if (data[key] != null) { if (data[key] != null) {
result[key] = data[key]; result[key] = data[key];
} }
}) })
return result; return result;
}; };
WebService.prototype.postProcess = function postProcess(model, data) { WebService.prototype.postProcess = function postProcess(model, data) {
var postProcessor = this._models[model].postProcessor; var postProcessor = this._models[model].postProcessor;
if (postProcessor && data) { if (postProcessor && data) {
postProcessor(data); postProcessor(data);
} }
}; };
WebService.prototype.postProcessMultiple = function postProcessMultiple(model, data) { WebService.prototype.postProcessMultiple = function postProcessMultiple(model, data) {
var postProcessor = this._models[model].postProcessor; var postProcessor = this._models[model].postProcessor;
if (postProcessor) { if (postProcessor) {
var max = data.length; var max = data.length;
for (var i = 0; i < max; i++) { for (var i = 0; i < max; i++) {
if (data[i]) { if (data[i]) {
postProcessor(data[i]); postProcessor(data[i]);
} }
};
} }
;
}
}; };
WebService.prototype.define = function defineModel(descr) { WebService.prototype.define = function defineModel(descr) {
var m = descr.model.modelName; var m = descr.model.modelName;
this.installPostProcessor(descr); this.installPostProcessor(descr);
this._models[m] = descr; this._models[m] = descr;
}; };
WebService.prototype.getResourceUrl = function getResourceUrl(model) { WebService.prototype.getResourceUrl = function getResourceUrl(model) {
var url = this._models[model].settings.restPath; var url = this._models[model].settings.restPath;
if (!url) throw new Error('Resource url (restPath) for ' + model + ' is not defined'); if (!url) throw new Error('Resource url (restPath) for ' + model + ' is not defined');
return url; return url;
}; };
WebService.prototype.getBlankReq = function () { WebService.prototype.getBlankReq = function () {
if (!this.csrfToken) { if (!this.csrfToken) {
this.csrfToken = $('meta[name=csrf-token]').attr('content'); this.csrfToken = $('meta[name=csrf-token]').attr('content');
this.csrfParam = $('meta[name=csrf-param]').attr('content'); this.csrfParam = $('meta[name=csrf-param]').attr('content');
} }
var req = {}; var req = {};
req[this.csrfParam] = this.csrfToken; req[this.csrfParam] = this.csrfToken;
return req; return req;
} }
WebService.prototype.create = function create(model, data, callback) { WebService.prototype.create = function create(model, data, callback) {
var req = this.getBlankReq(); var req = this.getBlankReq();
req[model] = this.preProcess(data); req[model] = this.preProcess(data);
$.post(this.getResourceUrl(model) + '.json', req, function (res) { $.post(this.getResourceUrl(model) + '.json', req, function (res) {
if (res.code === 200) { if (res.code === 200) {
callback(null, res.data.id); callback(null, res.data.id);
} else { } else {
callback(res.error); callback(res.error);
} }
}, 'json'); }, 'json');
// this.cache[model][id] = data; // this.cache[model][id] = data;
}; };
WebService.prototype.updateOrCreate = function (model, data, callback) { WebService.prototype.updateOrCreate = function (model, data, callback) {
var mem = this; var mem = this;
this.exists(model, data.id, function (err, exists) { this.exists(model, data.id, function (err, exists) {
if (exists) { if (exists) {
mem.save(model, data, callback); mem.save(model, data, callback);
} else { } else {
mem.create(model, data, function (err, id) { mem.create(model, data, function (err, id) {
data.id = id; data.id = id;
callback(err, data); callback(err, data);
}); });
} }
}); });
}; };
WebService.prototype.save = function save(model, data, callback) { WebService.prototype.save = function save(model, data, callback) {
var _this = this; var _this = this;
var req = this.getBlankReq(); var req = this.getBlankReq();
req._method = 'PUT'; req._method = 'PUT';
req[model] = this.preProcess(data); req[model] = this.preProcess(data);
$.post(this.getResourceUrl(model) + '/' + data.id + '.json', req, function (res) { $.post(this.getResourceUrl(model) + '/' + data.id + '.json', req, function (res) {
if (res.code === 200) { if (res.code === 200) {
_this.postProcess(model, res.data); _this.postProcess(model, res.data);
callback(null, res.data); callback(null, res.data);
} else { } else {
callback(res.error); callback(res.error);
} }
}, 'json'); }, 'json');
}; };
WebService.prototype.exists = function exists(model, id, callback) { WebService.prototype.exists = function exists(model, id, callback) {
$.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) { $.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) {
if (res.code === 200) { if (res.code === 200) {
callback(null, true); callback(null, true);
} else if (res.code === 404) { } else if (res.code === 404) {
callback(null, false); callback(null, false);
} else { } else {
callback(res.error); callback(res.error);
} }
}); });
}; };
WebService.prototype.find = function find(model, id, callback) { WebService.prototype.find = function find(model, id, callback) {
var _this = this; var _this = this;
$.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) { $.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) {
if (res.code === 200) { if (res.code === 200) {
_this.postProcess(model, res.data); _this.postProcess(model, res.data);
callback(null, res.data); callback(null, res.data);
} else { } else {
callback(res.error); callback(res.error);
} }
}); });
}; };
WebService.prototype.destroy = function destroy(model, id, callback) { WebService.prototype.destroy = function destroy(model, id, callback) {
var _this = this; var _this = this;
var req = this.getBlankReq(); var req = this.getBlankReq();
req._method = 'DELETE'; req._method = 'DELETE';
$.post(this.getResourceUrl(model) + '/' + id + '.json', req, function (res) { $.post(this.getResourceUrl(model) + '/' + id + '.json', req, function (res) {
if (res.code === 200) { if (res.code === 200) {
//delete _this.cache[model][id]; //delete _this.cache[model][id];
callback(null, res.data); callback(null, res.data);
} else { } else {
callback(res.error); callback(res.error);
} }
}, 'json'); }, 'json');
}; };
WebService.prototype.all = function all(model, filter, callback) { WebService.prototype.all = function all(model, filter, callback) {
var _this = this; var _this = this;
$.getJSON(this.getResourceUrl(model) + '.json?query=' + encodeURIComponent(JSON.stringify(filter)), function (res) { $.getJSON(this.getResourceUrl(model) + '.json?query=' + encodeURIComponent(JSON.stringify(filter)), function (res) {
if (res.code === 200) { if (res.code === 200) {
_this.postProcessMultiple(model, res.data); _this.postProcessMultiple(model, res.data);
callback(null, res.data); callback(null, res.data);
} else { } else {
callback(res.error); callback(res.error);
} }
}); });
}; };
WebService.prototype.destroyAll = function destroyAll(model, callback) { 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) { 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) { WebService.prototype.updateAttributes = function (model, id, data, callback) {
data.id = id; data.id = id;
this.save(model, data, callback); this.save(model, data, callback);
}; };

View File

@ -10,317 +10,317 @@ var utils = require('../utils');
* @param {Function} [callback] The callback function * @param {Function} [callback] The callback function
*/ */
exports.initialize = function initializeDataSource(dataSource, callback) { exports.initialize = function initializeDataSource(dataSource, callback) {
dataSource.connector = new Memory(); dataSource.connector = new Memory();
dataSource.connector.connect(callback); dataSource.connector.connect(callback);
}; };
exports.Memory = Memory; exports.Memory = Memory;
function Memory(m) { function Memory(m) {
if (m) { if (m) {
this.isTransaction = true; this.isTransaction = true;
this.cache = m.cache; this.cache = m.cache;
this.ids = m.ids; this.ids = m.ids;
this.constructor.super_.call(this, 'memory'); this.constructor.super_.call(this, 'memory');
this._models = m._models; this._models = m._models;
} else { } else {
this.isTransaction = false; this.isTransaction = false;
this.cache = {}; this.cache = {};
this.ids = {}; this.ids = {};
this.constructor.super_.call(this, 'memory'); this.constructor.super_.call(this, 'memory');
} }
} }
util.inherits(Memory, Connector); util.inherits(Memory, Connector);
Memory.prototype.connect = function(callback) { Memory.prototype.connect = function (callback) {
if (this.isTransaction) { if (this.isTransaction) {
this.onTransactionExec = callback; this.onTransactionExec = callback;
} else { } else {
process.nextTick(callback); process.nextTick(callback);
} }
}; };
Memory.prototype.define = function defineModel(definition) { Memory.prototype.define = function defineModel(definition) {
this.constructor.super_.prototype.define.apply(this, [].slice.call(arguments)); this.constructor.super_.prototype.define.apply(this, [].slice.call(arguments));
var m = definition.model.modelName; var m = definition.model.modelName;
this.cache[m] = {}; this.cache[m] = {};
this.ids[m] = 1; this.ids[m] = 1;
}; };
Memory.prototype.create = function create(model, data, callback) { Memory.prototype.create = function create(model, data, callback) {
// FIXME: [rfeng] We need to generate unique ids based on the id type // FIXME: [rfeng] We need to generate unique ids based on the id type
// FIXME: [rfeng] We don't support composite ids yet // FIXME: [rfeng] We don't support composite ids yet
var currentId = this.ids[model]; var currentId = this.ids[model];
if(currentId === undefined) { if (currentId === undefined) {
// First time // First time
this.ids[model] = 1; this.ids[model] = 1;
currentId = 1; currentId = 1;
} }
var id = this.getIdValue(model, data) || currentId; var id = this.getIdValue(model, data) || currentId;
if(id > currentId) { if (id > currentId) {
// If the id is passed in and the value is greater than the current id // If the id is passed in and the value is greater than the current id
currentId = id; currentId = id;
} }
this.ids[model] = Number(currentId) + 1; this.ids[model] = Number(currentId) + 1;
var props = this._models[model].properties; var props = this._models[model].properties;
var idName = this.idName(model); var idName = this.idName(model);
id = (props[idName] && props[idName].type && props[idName].type(id)) || id; id = (props[idName] && props[idName].type && props[idName].type(id)) || id;
this.setIdValue(model, data, id); this.setIdValue(model, data, id);
this.cache[model][id] = JSON.stringify(data); this.cache[model][id] = JSON.stringify(data);
process.nextTick(function() { process.nextTick(function () {
callback(null, id); callback(null, id);
}); });
}; };
Memory.prototype.updateOrCreate = function (model, data, callback) { Memory.prototype.updateOrCreate = function (model, data, callback) {
var self = this; var self = this;
this.exists(model, self.getIdValue(model, data), function (err, exists) { this.exists(model, self.getIdValue(model, data), function (err, exists) {
if (exists) { if (exists) {
self.save(model, data, callback); self.save(model, data, callback);
} else { } else {
self.create(model, data, function (err, id) { self.create(model, data, function (err, id) {
self.setIdValue(model, data, id); self.setIdValue(model, data, id);
callback(err, data); callback(err, data);
}); });
} }
}); });
}; };
Memory.prototype.save = function save(model, data, callback) { Memory.prototype.save = function save(model, data, callback) {
this.cache[model][this.getIdValue(model, data)] = JSON.stringify(data); this.cache[model][this.getIdValue(model, data)] = JSON.stringify(data);
process.nextTick(function () { process.nextTick(function () {
callback(null, data); callback(null, data);
}); });
}; };
Memory.prototype.exists = function exists(model, id, callback) { Memory.prototype.exists = function exists(model, id, callback) {
process.nextTick(function () { process.nextTick(function () {
callback(null, this.cache[model] && this.cache[model].hasOwnProperty(id)); callback(null, this.cache[model] && this.cache[model].hasOwnProperty(id));
}.bind(this)); }.bind(this));
}; };
Memory.prototype.find = function find(model, id, callback) { Memory.prototype.find = function find(model, id, callback) {
var self = this; var self = this;
process.nextTick(function () { process.nextTick(function () {
callback(null, id in this.cache[model] && this.fromDb(model, this.cache[model][id])); callback(null, id in this.cache[model] && this.fromDb(model, this.cache[model][id]));
}.bind(this)); }.bind(this));
}; };
Memory.prototype.destroy = function destroy(model, id, callback) { Memory.prototype.destroy = function destroy(model, id, callback) {
delete this.cache[model][id]; delete this.cache[model][id];
process.nextTick(callback); process.nextTick(callback);
}; };
Memory.prototype.fromDb = function(model, data) { Memory.prototype.fromDb = function (model, data) {
if (!data) return null; if (!data) return null;
data = JSON.parse(data); data = JSON.parse(data);
var props = this._models[model].properties; var props = this._models[model].properties;
for(var key in data) { for (var key in data) {
var val = data[key]; var val = data[key];
if (val === undefined || val === null) { if (val === undefined || val === null) {
continue; 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;
} }
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) { Memory.prototype.all = function all(model, filter, callback) {
var self = this; var self = this;
var nodes = Object.keys(this.cache[model]).map(function (key) { var nodes = Object.keys(this.cache[model]).map(function (key) {
return this.fromDb(model, this.cache[model][key]); return this.fromDb(model, this.cache[model][key]);
}.bind(this)); }.bind(this));
if (filter) { if (filter) {
// do we need some sorting? // do we need some sorting?
if (filter.order) { if (filter.order) {
var orders = filter.order; var orders = filter.order;
if (typeof filter.order === "string") { if (typeof filter.order === "string") {
orders = [filter.order]; orders = [filter.order];
} }
orders.forEach(function (key, i) { orders.forEach(function (key, i) {
var reverse = 1; var reverse = 1;
var m = key.match(/\s+(A|DE)SC$/i); var m = key.match(/\s+(A|DE)SC$/i);
if (m) { if (m) {
key = key.replace(/\s+(A|DE)SC/i, ''); key = key.replace(/\s+(A|DE)SC/i, '');
if (m[1].toLowerCase() === 'de') reverse = -1; if (m[1].toLowerCase() === 'de') reverse = -1;
}
orders[i] = {"key": key, "reverse": reverse};
});
nodes = nodes.sort(sorting.bind(orders));
} }
orders[i] = {"key": key, "reverse": reverse};
var nearFilter = geo.nearFilter(filter.where); });
nodes = nodes.sort(sorting.bind(orders));
// 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 () { var nearFilter = geo.nearFilter(filter.where);
if (filter && filter.include) {
self._models[model].model.include(nodes, filter.include, callback);
} else {
callback(null, nodes);
}
});
function sorting(a, b) { // geo sorting
for (var i=0, l=this.length; i<l; i++) { if (nearFilter) {
if (a[this[i].key] > b[this[i].key]) { nodes = geo.filter(nodes, nearFilter);
return 1*this[i].reverse;
} else if (a[this[i].key] < b[this[i].key]) {
return -1*this[i].reverse;
}
}
return 0;
} }
// 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) { function applyFilter(filter) {
if (typeof filter.where === 'function') { if (typeof filter.where === 'function') {
return filter.where; return filter.where;
} }
var keys = Object.keys(filter.where); var keys = Object.keys(filter.where);
return function (obj) { return function (obj) {
var pass = true; var pass = true;
keys.forEach(function (key) { keys.forEach(function (key) {
if (!test(filter.where[key], obj && obj[key])) { if (!test(filter.where[key], obj && obj[key])) {
pass = false; pass = false;
} }
}); });
return pass; return pass;
} }
function test(example, value) { function test(example, value) {
if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { if (typeof value === 'string' && example && example.constructor.name === 'RegExp') {
return value.match(example); 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;
} }
if (typeof example === 'undefined') return undefined; return false;
if (typeof value === 'undefined') return undefined; }
if (typeof example === 'object') {
// ignore geo near filter
if(example.near) return true;
if (example.inq) { if (isNum(example.gt) && example.gt < value) return true;
if (!value) return false; if (isNum(example.gte) && example.gte <= value) return true;
for (var i = 0; i < example.inq.length; i += 1) { if (isNum(example.lt) && example.lt > value) return true;
if (example.inq[i] == value) return true; if (isNum(example.lte) && example.lte >= 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);
} }
// not strict equality
return (example !== null ? example.toString() : example) == (value !== null ? value.toString() : value);
}
function isNum(n) { function isNum(n) {
return typeof n === 'number'; return typeof n === 'number';
} }
} }
Memory.prototype.destroyAll = function destroyAll(model, where, callback) { Memory.prototype.destroyAll = function destroyAll(model, where, callback) {
if(!callback && 'function' === typeof where) { if (!callback && 'function' === typeof where) {
callback = where; callback = where;
where = undefined; 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]; }.bind(this));
var filter = null; if (!where) {
if (where) { this.cache[model] = {};
filter = applyFilter({where: where}); }
} process.nextTick(callback);
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);
}; };
Memory.prototype.count = function count(model, callback, where) { Memory.prototype.count = function count(model, callback, where) {
var cache = this.cache[model]; var cache = this.cache[model];
var data = Object.keys(cache); var data = Object.keys(cache);
if (where) { if (where) {
var filter = {where: where}; var filter = {where: where};
data = data.map(function (id) { data = data.map(function (id) {
return this.fromDb(model, cache[id]); return this.fromDb(model, cache[id]);
}.bind(this)); }.bind(this));
data = data.filter(applyFilter(filter)); data = data.filter(applyFilter(filter));
} }
process.nextTick(function () { process.nextTick(function () {
callback(null, data.length); callback(null, data.length);
}); });
}; };
Memory.prototype.updateAttributes = function updateAttributes(model, id, data, cb) { Memory.prototype.updateAttributes = function updateAttributes(model, id, data, cb) {
if(!id) { if (!id) {
var err = new Error('You must provide an id when updating attributes!'); var err = new Error('You must provide an id when updating attributes!');
if(cb) { if (cb) {
return cb(err); 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);
} else { } 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 () { Memory.prototype.transaction = function () {
return new Memory(this); return new Memory(this);
}; };
Memory.prototype.exec = function(callback) { Memory.prototype.exec = function (callback) {
this.onTransactionExec(); this.onTransactionExec();
setTimeout(callback, 50); setTimeout(callback, 50);
}; };
Memory.prototype.buildNearFilter = function (filter) { Memory.prototype.buildNearFilter = function (filter) {
@ -328,9 +328,9 @@ Memory.prototype.buildNearFilter = function (filter) {
} }
function merge(base, update) { function merge(base, update) {
if (!base) return update; if (!base) return update;
Object.keys(update).forEach(function (key) { Object.keys(update).forEach(function (key) {
base[key] = update[key]; base[key] = update[key];
}); });
return base; return base;
} }

View File

@ -6,367 +6,368 @@ var safeRequire = require('../utils').safeRequire;
var neo4j = safeRequire('neo4j'); var neo4j = safeRequire('neo4j');
exports.initialize = function initializeSchema(dataSource, callback) { exports.initialize = function initializeSchema(dataSource, callback) {
dataSource.client = new neo4j.GraphDatabase(dataSource.settings.url); dataSource.client = new neo4j.GraphDatabase(dataSource.settings.url);
dataSource.connector = new Neo4j(dataSource.client); dataSource.connector = new Neo4j(dataSource.client);
process.nextTick(callback); process.nextTick(callback);
}; };
function Neo4j(client) { function Neo4j(client) {
this._models = {}; this._models = {};
this.client = client; this.client = client;
this.cache = {}; this.cache = {};
} }
Neo4j.prototype.define = function defineModel(descr) { Neo4j.prototype.define = function defineModel(descr) {
this.mixClassMethods(descr.model, descr.properties); this.mixClassMethods(descr.model, descr.properties);
this.mixInstanceMethods(descr.model.prototype, descr.properties); this.mixInstanceMethods(descr.model.prototype, descr.properties);
this._models[descr.model.modelName] = descr; this._models[descr.model.modelName] = descr;
}; };
Neo4j.prototype.createIndexHelper = function (cls, indexName) { Neo4j.prototype.createIndexHelper = function (cls, indexName) {
var db = this.client; var db = this.client;
var method = 'findBy' + indexName[0].toUpperCase() + indexName.substr(1); var method = 'findBy' + indexName[0].toUpperCase() + indexName.substr(1);
cls[method] = function (value, cb) { cls[method] = function (value, cb) {
db.getIndexedNode(cls.modelName, indexName, value, function (err, node) { db.getIndexedNode(cls.modelName, indexName, value, function (err, node) {
if (err) return cb(err); if (err) return cb(err);
if (node) { if (node) {
node.data.id = node.id; node.data.id = node.id;
cb(null, new cls(node.data)); cb(null, new cls(node.data));
} else { } else {
cb(null, null); cb(null, null);
} }
}); });
}; };
}; };
Neo4j.prototype.mixClassMethods = function mixClassMethods(cls, properties) { Neo4j.prototype.mixClassMethods = function mixClassMethods(cls, properties) {
var neo = this; var neo = this;
Object.keys(properties).forEach(function (name) { Object.keys(properties).forEach(function (name) {
if (properties[name].index) { if (properties[name].index) {
neo.createIndexHelper(cls, name); 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.createRelationshipTo = function createRelationshipTo(id1, id2, type, data, cb) {
cls[name] = function cypherQuery(params, cb) { var fromNode, toNode;
if (typeof params === 'function') { neo.node(id1, function (err, node) {
cb = params; if (err && cb) return cb(err);
params = []; if (err && !cb) throw err;
} else if (params.constructor.name !== 'Array') { fromNode = node;
params = [params]; ok();
} });
neo.node(id2, function (err, node) {
var i = 0; if (err && cb) return cb(err);
var q = queryStr.replace(/\?/g, function () { if (err && !cb) throw err;
return params[i++]; toNode = node;
}); ok();
});
neo.client.query(function (err, result) { function ok() {
if (err) return cb(err, []); if (fromNode && toNode) {
cb(null, result.map(rowHandler)); fromNode.createRelationshipTo(toNode, type, cleanup(data), cb);
}, 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);
} }
};
// only create relationship if it is not exists cls.createRelationshipFrom = function createRelationshipFrom(id1, id2, type, data, cb) {
cls.ensureRelationshipTo = function (id1, id2, type, data, cb) { cls.createRelationshipTo(id2, id1, type, data, cb);
cls.relationshipExists(id1, id2, type, 'outgoing', function (err, exists) { }
if (err && cb) return cb(err);
if (err && !cb) throw err; // only create relationship if it is not exists
if (exists) return cb && cb(null); cls.ensureRelationshipTo = function (id1, id2, type, data, cb) {
cls.createRelationshipTo(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) { Neo4j.prototype.mixInstanceMethods = function mixInstanceMethods(proto) {
var neo = this; var neo = this;
/** /**
* @param obj - Object or id of object to check relation with * @param obj - Object or id of object to check relation with
* @param type - type of relation * @param type - type of relation
* @param cb - callback (err, rel || false) * @param cb - callback (err, rel || false)
*/ */
proto.isInRelationWith = function isInRelationWith(obj, type, direction, cb) { proto.isInRelationWith = function isInRelationWith(obj, type, direction, cb) {
this.constructor.relationshipExists(this.id, obj.id || obj, type, 'all', cb); this.constructor.relationshipExists(this.id, obj.id || obj, type, 'all', cb);
}; };
}; };
Neo4j.prototype.node = function find(id, callback) { Neo4j.prototype.node = function find(id, callback) {
if (this.cache[id]) { if (this.cache[id]) {
callback(null, this.cache[id]); callback(null, this.cache[id]);
} else { } else {
this.client.getNodeById(id, function (err, node) { this.client.getNodeById(id, function (err, node) {
if (node) { if (node) {
this.cache[id] = node; this.cache[id] = node;
} }
callback(err, node); callback(err, node);
}.bind(this)); }.bind(this));
} }
}; };
Neo4j.prototype.create = function create(model, data, callback) { Neo4j.prototype.create = function create(model, data, callback) {
data.nodeType = model; data.nodeType = model;
var node = this.client.createNode(); var node = this.client.createNode();
node.data = cleanup(data); node.data = cleanup(data);
node.data.nodeType = model; node.data.nodeType = model;
node.save(function (err) { node.save(function (err) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
this.cache[node.id] = node; this.cache[node.id] = node;
node.index(model, 'id', node.id, function (err) { node.index(model, 'id', node.id, function (err) {
if (err) return callback(err); if (err) return callback(err);
this.updateIndexes(model, node, function (err) { this.updateIndexes(model, node, function (err) {
if (err) return callback(err); if (err) return callback(err);
callback(null, node.id); callback(null, node.id);
}); });
}.bind(this));
}.bind(this)); }.bind(this));
}.bind(this));
}; };
Neo4j.prototype.updateIndexes = function updateIndexes(model, node, cb) { Neo4j.prototype.updateIndexes = function updateIndexes(model, node, cb) {
var props = this._models[model].properties; var props = this._models[model].properties;
var wait = 1; var wait = 1;
Object.keys(props).forEach(function (key) { Object.keys(props).forEach(function (key) {
if (props[key].index && node.data[key]) { if (props[key].index && node.data[key]) {
wait += 1; wait += 1;
node.index(model, key, node.data[key], done); node.index(model, key, node.data[key], done);
}
});
done();
var error = false;
function done(err) {
error = error || err;
if (--wait === 0) {
cb(error);
}
} }
});
done();
var error = false;
function done(err) {
error = error || err;
if (--wait === 0) {
cb(error);
}
}
}; };
Neo4j.prototype.save = function save(model, data, callback) { Neo4j.prototype.save = function save(model, data, callback) {
var self = this; var self = this;
this.node(data.id, function (err, node) { this.node(data.id, function (err, node) {
//delete id property since that's redundant and we use the node.id //delete id property since that's redundant and we use the node.id
delete data.id; delete data.id;
if (err) return callback(err); if (err) return callback(err);
node.data = cleanup(data); node.data = cleanup(data);
node.save(function (err) { node.save(function (err) {
if (err) return callback(err); if (err) return callback(err);
self.updateIndexes(model, node, function (err) { self.updateIndexes(model, node, function (err) {
if (err) return console.log(err); if (err) return console.log(err);
//map node id to the id property being sent back //map node id to the id property being sent back
node.data.id = node.id; node.data.id = node.id;
callback(null, node.data); callback(null, node.data);
}); });
});
}); });
});
}; };
Neo4j.prototype.exists = function exists(model, id, callback) { Neo4j.prototype.exists = function exists(model, id, callback) {
delete this.cache[id]; delete this.cache[id];
this.node(id, callback); this.node(id, callback);
}; };
Neo4j.prototype.find = function find(model, id, callback) { Neo4j.prototype.find = function find(model, id, callback) {
delete this.cache[id]; delete this.cache[id];
this.node(id, function (err, node) { this.node(id, function (err, node) {
if (node && node.data) { if (node && node.data) {
node.data.id = id; node.data.id = id;
} }
callback(err, this.readFromDb(model, node && node.data)); callback(err, this.readFromDb(model, node && node.data));
}.bind(this)); }.bind(this));
}; };
Neo4j.prototype.readFromDb = function readFromDb(model, data) { Neo4j.prototype.readFromDb = function readFromDb(model, data) {
if (!data) return data; if (!data) return data;
var res = {}; var res = {};
var props = this._models[model].properties; var props = this._models[model].properties;
Object.keys(data).forEach(function (key) { Object.keys(data).forEach(function (key) {
if (props[key] && props[key].type.name === 'Date') { if (props[key] && props[key].type.name === 'Date') {
res[key] = new Date(data[key]); res[key] = new Date(data[key]);
} else { } else {
res[key] = data[key]; res[key] = data[key];
} }
}); });
return res; return res;
}; };
Neo4j.prototype.destroy = function destroy(model, id, callback) { Neo4j.prototype.destroy = function destroy(model, id, callback) {
var force = true; var force = true;
this.node(id, function (err, node) { this.node(id, function (err, node) {
if (err) return callback(err); if (err) return callback(err);
node.delete(function (err) { node.delete(function (err) {
if (err) return callback(err); if (err) return callback(err);
delete this.cache[id]; delete this.cache[id];
}.bind(this), force); }.bind(this), force);
}); });
}; };
Neo4j.prototype.all = function all(model, filter, callback) { Neo4j.prototype.all = function all(model, filter, callback) {
this.client.queryNodeIndex(model, 'id:*', function (err, nodes) { this.client.queryNodeIndex(model, 'id:*', function (err, nodes) {
if (nodes) { if (nodes) {
nodes = nodes.map(function (obj) { nodes = nodes.map(function (obj) {
obj.data.id = obj.id; obj.data.id = obj.id;
return this.readFromDb(model, obj.data); return this.readFromDb(model, obj.data);
}.bind(this)); }.bind(this));
} }
if (filter) { if (filter) {
nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes; nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
if (filter.order) { if (filter.order) {
var key = filter.order.split(' ')[0]; var key = filter.order.split(' ')[0];
var dir = filter.order.split(' ')[1]; var dir = filter.order.split(' ')[1];
nodes = nodes.sort(function (a, b) { nodes = nodes.sort(function (a, b) {
return a[key] > b[key]; return a[key] > b[key];
}); });
if (dir === 'DESC') nodes = nodes.reverse(); if (dir === 'DESC') nodes = nodes.reverse();
} }
} }
callback(err, nodes); callback(err, nodes);
}.bind(this)); }.bind(this));
}; };
Neo4j.prototype.allNodes = function all(model, callback) { Neo4j.prototype.allNodes = function all(model, callback) {
this.client.queryNodeIndex(model, 'id:*', function (err, nodes) { this.client.queryNodeIndex(model, 'id:*', function (err, nodes) {
callback(err, nodes); callback(err, nodes);
}); });
}; };
function applyFilter(filter) { function applyFilter(filter) {
if (typeof filter.where === 'function') { if (typeof filter.where === 'function') {
return filter.where; return filter.where;
} }
var keys = Object.keys(filter.where || {}); var keys = Object.keys(filter.where || {});
return function (obj) { return function (obj) {
var pass = true; var pass = true;
keys.forEach(function (key) { keys.forEach(function (key) {
if (!test(filter.where[key], obj[key])) { if (!test(filter.where[key], obj[key])) {
pass = false; pass = false;
} }
}); });
return pass; return pass;
} }
function test(example, value) { function test(example, value) {
if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { if (typeof value === 'string' && example && example.constructor.name === 'RegExp') {
return value.match(example); 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;
} }
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) { Neo4j.prototype.destroyAll = function destroyAll(model, callback) {
var wait, error = null; var wait, error = null;
this.allNodes(model, function (err, collection) { this.allNodes(model, function (err, collection) {
if (err) return callback(err); if (err) return callback(err);
wait = collection.length; wait = collection.length;
collection && collection.forEach && collection.forEach(function (node) { collection && collection.forEach && collection.forEach(function (node) {
node.delete(done, true); node.delete(done, true);
});
}); });
});
function done(err) { function done(err) {
error = error || err; error = error || err;
if (--wait === 0) { if (--wait === 0) {
callback(error); callback(error);
}
} }
}
}; };
Neo4j.prototype.count = function count(model, callback, conds) { Neo4j.prototype.count = function count(model, callback, conds) {
this.all(model, {where: conds}, function (err, collection) { this.all(model, {where: conds}, function (err, collection) {
callback(err, collection ? collection.length : 0); callback(err, collection ? collection.length : 0);
}); });
}; };
Neo4j.prototype.updateAttributes = function updateAttributes(model, id, data, cb) { Neo4j.prototype.updateAttributes = function updateAttributes(model, id, data, cb) {
data.id = id; data.id = id;
this.node(id, function (err, node) { this.node(id, function (err, node) {
this.save(model, merge(node.data, data), cb); this.save(model, merge(node.data, data), cb);
}.bind(this)); }.bind(this));
}; };
function cleanup(data) { function cleanup(data) {
if (!data) return null; if (!data) return null;
var res = {}; var res = {};
Object.keys(data).forEach(function (key) { Object.keys(data).forEach(function (key) {
var v = data[key]; var v = data[key];
if (v === null) { if (v === null) {
// skip // skip
// console.log('skip null', key); // console.log('skip null', key);
} else if (v && v.constructor.name === 'Array' && v.length === 0) { } else if (v && v.constructor.name === 'Array' && v.length === 0) {
// skip // skip
// console.log('skip blank array', key); // console.log('skip blank array', key);
} else if (typeof v !== 'undefined') { } else if (typeof v !== 'undefined') {
res[key] = v; res[key] = v;
} }
}); });
return res; return res;
} }
function merge(base, update) { function merge(base, update) {
Object.keys(update).forEach(function (key) { Object.keys(update).forEach(function (key) {
base[key] = update[key]; base[key] = update[key];
}); });
return base; return base;
} }

View File

@ -7,104 +7,104 @@ var uuid = require('node-uuid');
var riak = safeRequire('riak-js'); var riak = safeRequire('riak-js');
exports.initialize = function initializeSchema(dataSource, callback) { exports.initialize = function initializeSchema(dataSource, callback) {
dataSource.client = riak.getClient({ dataSource.client = riak.getClient({
host: dataSource.settings.host || '127.0.0.1', host: dataSource.settings.host || '127.0.0.1',
port: dataSource.settings.port || 8091 port: dataSource.settings.port || 8091
}); });
dataSource.connector = new Riak(dataSource.client); dataSource.connector = new Riak(dataSource.client);
}; };
function Riak(client) { function Riak(client) {
this._models = {}; this._models = {};
this.client = client; this.client = client;
} }
Riak.prototype.define = function (descr) { Riak.prototype.define = function (descr) {
this._models[descr.model.modelName] = descr; this._models[descr.model.modelName] = descr;
}; };
Riak.prototype.save = function (model, data, callback) { 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) { Riak.prototype.create = function (model, data, callback) {
data.id = uuid(); data.id = uuid();
this.save(model, data, function (err) { this.save(model, data, function (err) {
if (callback) { if (callback) {
callback(err, data.id); callback(err, data.id);
} }
}); });
}; };
Riak.prototype.exists = function (model, id, callback) { Riak.prototype.exists = function (model, id, callback) {
this.client.exists(model, id, function (err, exists, meta) { this.client.exists(model, id, function (err, exists, meta) {
if (callback) { if (callback) {
callback(err, exists); callback(err, exists);
} }
}); });
}; };
Riak.prototype.find = function find(model, id, callback) { Riak.prototype.find = function find(model, id, callback) {
this.client.get(model, id, function (err, data, meta) { this.client.get(model, id, function (err, data, meta) {
if (data && data.id) { if (data && data.id) {
data.id = id; data.id = id;
} else { } else {
data = null; data = null;
} }
if (typeof callback === 'function') callback(err, data); if (typeof callback === 'function') callback(err, data);
}); });
}; };
Riak.prototype.destroy = function destroy(model, id, callback) { Riak.prototype.destroy = function destroy(model, id, callback) {
this.client.remove(model, id, function (err) { this.client.remove(model, id, function (err) {
callback(err); callback(err);
}); });
}; };
Riak.prototype.all = function all(model, filter, callback) { Riak.prototype.all = function all(model, filter, callback) {
var opts = {}; var opts = {};
if (filter && filter.where) opts.where = filter.where; if (filter && filter.where) opts.where = filter.where;
this.client.getAll(model, function (err, result, meta) { this.client.getAll(model, function (err, result, meta) {
if (err) return callback(err, []); if (err) return callback(err, []);
/// return callback(err, result.map(function (x) { return {id: x}; })); /// return callback(err, result.map(function (x) { return {id: x}; }));
result = (result || []).map(function (row) { result = (result || []).map(function (row) {
var record = row.data; var record = row.data;
record.id = row.meta.key; record.id = row.meta.key;
console.log(record); console.log(record);
return record; return record;
}); });
return callback(err, result); return callback(err, result);
}.bind(this)); }.bind(this));
}; };
Riak.prototype.destroyAll = function destroyAll(model, callback) { Riak.prototype.destroyAll = function destroyAll(model, callback) {
var self = this; var self = this;
this.all(model, {}, function (err, recs) { this.all(model, {}, function (err, recs) {
if (err) callback(err); if (err) callback(err);
removeOne(); removeOne();
function removeOne(error) { function removeOne(error) {
err = err || error; err = err || error;
var rec = recs.pop(); var rec = recs.pop();
if (!rec) return callback(err && err.statusCode != '404' ? err : null); if (!rec) return callback(err && err.statusCode != '404' ? err : null);
console.log(rec.id); console.log(rec.id);
self.client.remove(model, rec.id, removeOne); self.client.remove(model, rec.id, removeOne);
} }
}); });
}; };
Riak.prototype.count = function count(model, callback) { Riak.prototype.count = function count(model, callback) {
this.client.keys(model + ':*', function (err, keys) { this.client.keys(model + ':*', function (err, keys) {
callback(err, err ? null : keys.length); callback(err, err ? null : keys.length);
}); });
}; };
Riak.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { Riak.prototype.updateAttributes = function updateAttrs(model, id, data, cb) {
data.id = id; data.id = id;
this.save(model, data, cb); this.save(model, data, cb);
}; };

1120
lib/dao.js

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -11,11 +11,11 @@ var assert = require('assert');
exports.nearFilter = function nearFilter(where) { exports.nearFilter = function nearFilter(where) {
var result = false; var result = false;
if(where && typeof where === 'object') { if (where && typeof where === 'object') {
Object.keys(where).forEach(function (key) { Object.keys(where).forEach(function (key) {
var ex = where[key]; var ex = where[key];
if(ex && ex.near) { if (ex && ex.near) {
result = { result = {
near: ex.near, near: ex.near,
maxDistance: ex.maxDistance, maxDistance: ex.maxDistance,
@ -45,18 +45,18 @@ exports.filter = function (arr, filter) {
var loc = obj[key]; var loc = obj[key];
// filter out objects without locations // filter out objects without locations
if(!loc) return; if (!loc) return;
if(!(loc instanceof GeoPoint)) { if (!(loc instanceof GeoPoint)) {
loc = GeoPoint(loc); loc = GeoPoint(loc);
} }
if(typeof loc.lat !== 'number') return; if (typeof loc.lat !== 'number') return;
if(typeof loc.lng !== 'number') return; if (typeof loc.lng !== 'number') return;
var d = GeoPoint.distanceBetween(origin, loc); var d = GeoPoint.distanceBetween(origin, loc);
if(max && d > max) { if (max && d > max) {
// dont add // dont add
} else { } else {
distances[obj.id] = d; distances[obj.id] = d;
@ -68,11 +68,11 @@ exports.filter = function (arr, filter) {
var a = objB[key]; var a = objB[key];
var b = objB[key]; var b = objB[key];
if(a && b) { if (a && b) {
var da = distances[objA.id]; var da = distances[objA.id];
var db = distances[objB.id]; var db = distances[objB.id];
if(db === da) return 0; if (db === da) return 0;
return da > db ? 1 : -1; return da > db ? 1 : -1;
} else { } else {
return 0; return 0;
@ -87,15 +87,15 @@ exports.filter = function (arr, filter) {
exports.GeoPoint = GeoPoint; exports.GeoPoint = GeoPoint;
function GeoPoint(data) { function GeoPoint(data) {
if(!(this instanceof GeoPoint)) { if (!(this instanceof GeoPoint)) {
return new GeoPoint(data); return new GeoPoint(data);
} }
if(typeof data === 'string') { if (typeof data === 'string') {
data = data.split(/,\s*/); data = data.split(/,\s*/);
assert(data.length === 2, 'must provide a string "lng,lat" creating a GeoPoint with a string'); 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 = { data = {
lng: Number(data[0]), lng: Number(data[0]),
lat: Number(data[1]) lat: Number(data[1])
@ -122,10 +122,10 @@ function GeoPoint(data) {
*/ */
GeoPoint.distanceBetween = function distanceBetween(a, b, options) { GeoPoint.distanceBetween = function distanceBetween(a, b, options) {
if(!(a instanceof GeoPoint)) { if (!(a instanceof GeoPoint)) {
a = GeoPoint(a); a = GeoPoint(a);
} }
if(!(b instanceof GeoPoint)) { if (!(b instanceof GeoPoint)) {
b = GeoPoint(b); b = GeoPoint(b);
} }
@ -162,7 +162,7 @@ GeoPoint.prototype.toString = function () {
var PI = 3.1415926535897932384626433832795; var PI = 3.1415926535897932384626433832795;
// factor to convert decimal degrees to radians // factor to convert decimal degrees to radians
var DEG2RAD = 0.01745329252; var DEG2RAD = 0.01745329252;
// factor to convert decimal degrees to radians // factor to convert decimal degrees to radians
var RAD2DEG = 57.29577951308; var RAD2DEG = 57.29577951308;
@ -184,12 +184,12 @@ function geoDistance(x1, y1, x2, y2, options) {
x2 = x2 * DEG2RAD; x2 = x2 * DEG2RAD;
y2 = y2 * DEG2RAD; y2 = y2 * DEG2RAD;
var a = Math.pow(Math.sin(( y2-y1 ) / 2.0 ), 2); var a = Math.pow(Math.sin(( y2 - y1 ) / 2.0), 2);
var b = Math.pow(Math.sin(( x2-x1 ) / 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 c = Math.sqrt(a + Math.cos(y2) * Math.cos(y1) * b);
var type = (options && options.type) || 'miles'; var type = (options && options.type) || 'miles';
return 2 * Math.asin( c ) * EARTH_RADIUS[type]; return 2 * Math.asin(c) * EARTH_RADIUS[type];
} }

View File

@ -27,39 +27,41 @@ Hookable.afterDestroy = null;
// TODO: Evaluate https://github.com/bnoguchi/hooks-js/ // TODO: Evaluate https://github.com/bnoguchi/hooks-js/
Hookable.prototype.trigger = function trigger(actionName, work, data) { Hookable.prototype.trigger = function trigger(actionName, work, data) {
var capitalizedName = capitalize(actionName); var capitalizedName = capitalize(actionName);
var beforeHook = this.constructor["before" + capitalizedName] || this.constructor["pre" + capitalizedName]; var beforeHook = this.constructor["before" + capitalizedName]
var afterHook = this.constructor["after" + capitalizedName] || this.constructor["post" + capitalizedName]; || this.constructor["pre" + capitalizedName];
if (actionName === 'validate') { var afterHook = this.constructor["after" + capitalizedName]
beforeHook = beforeHook || this.constructor.beforeValidation; || this.constructor["post" + capitalizedName];
afterHook = afterHook || this.constructor.afterValidation; if (actionName === 'validate') {
} beforeHook = beforeHook || this.constructor.beforeValidation;
var inst = this; afterHook = afterHook || this.constructor.afterValidation;
}
var inst = this;
// we only call "before" hook when we have actual action (work) to perform // we only call "before" hook when we have actual action (work) to perform
if (work) { if (work) {
if (beforeHook) { if (beforeHook) {
// before hook should be called on instance with one param: callback // before hook should be called on instance with one param: callback
beforeHook.call(inst, function () { beforeHook.call(inst, function () {
// actual action also have one param: callback // actual action also have one param: callback
work.call(inst, next); work.call(inst, next);
}, data); }, data);
} else {
work.call(inst, next);
}
} else { } else {
next(); work.call(inst, next);
} }
} else {
next();
}
function next(done) { function next(done) {
if (afterHook) { if (afterHook) {
afterHook.call(inst, done); afterHook.call(inst, done);
} else if (done) { } else if (done) {
done.call(this); done.call(this);
}
} }
}
}; };
function capitalize(string) { function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
} }

View File

@ -26,135 +26,137 @@ function Inclusion() {
* *
*/ */
Inclusion.include = function (objects, include, cb) { Inclusion.include = function (objects, include, cb) {
var self = this; var self = this;
if ( if (
(include.constructor.name == 'Array' && include.length == 0) || (include.constructor.name == 'Array' && include.length == 0) ||
(include.constructor.name == 'Object' && Object.keys(include).length == 0) (include.constructor.name == 'Object' && Object.keys(include).length == 0)
) { ) {
cb(null, objects); cb(null, objects);
return; 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 = {}; if (!keyVals[relation.keyFrom]) {
var objsByKeys = {}; 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; if (keyVals[relation.keyFrom].length > 0) {
for (var i = 0; i < include.length; i++) { // deep clone is necessary since inq seems to change the processed array
var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys); var keysToBeProcessed = {};
if (callback !== null) { var inValues = [];
nbCallbacks++; for (var j = 0; j < keyVals[relation.keyFrom].length; j++) {
callback(function() { keysToBeProcessed[keyVals[relation.keyFrom][j]] = true;
nbCallbacks--; if (keyVals[relation.keyFrom][j] !== 'null'
if (nbCallbacks == 0) { && keyVals[relation.keyFrom][j] !== 'undefined') {
cb(null, objects); 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 { } else {
cb(null, objects); 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) { return null;
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;
}
} }

View File

@ -1,60 +1,60 @@
module.exports = function getIntrospector(ModelBuilder) { module.exports = function getIntrospector(ModelBuilder) {
function introspectType(value) { function introspectType(value) {
// Unknown type, using Any // Unknown type, using Any
if (value === null || value === undefined) { if (value === null || value === undefined) {
return ModelBuilder.Any; 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;
} }
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;
} }

View File

@ -5,24 +5,24 @@ var util = require('util');
* @param baseClass * @param baseClass
*/ */
exports.inherits = function (newClass, baseClass, options) { exports.inherits = function (newClass, baseClass, options) {
util.inherits(newClass, baseClass); util.inherits(newClass, baseClass);
options = options || { options = options || {
staticProperties: true, staticProperties: true,
override: false override: false
}; };
if (options.staticProperties) { if (options.staticProperties) {
Object.keys(baseClass).forEach(function (classProp) { Object.keys(baseClass).forEach(function (classProp) {
if (classProp !== 'super_' && (!newClass.hasOwnProperty(classProp) || options.override)) { if (classProp !== 'super_' && (!newClass.hasOwnProperty(classProp)
var pd = Object.getOwnPropertyDescriptor(baseClass, classProp); || options.override)) {
Object.defineProperty(newClass, classProp, pd); var pd = Object.getOwnPropertyDescriptor(baseClass, classProp);
} Object.defineProperty(newClass, classProp, pd);
}); }
} });
}
}; };
/** /**
* Mix in the a class into the new class * Mix in the a class into the new class
* @param newClass The target class to receive the mixin * @param newClass The target class to receive the mixin
@ -30,76 +30,78 @@ exports.inherits = function (newClass, baseClass, options) {
* @param options * @param options
*/ */
exports.mixin = function (newClass, mixinClass, options) { exports.mixin = function (newClass, mixinClass, options) {
if (Array.isArray(newClass._mixins)) { if (Array.isArray(newClass._mixins)) {
if(newClass._mixins.indexOf(mixinClass) !== -1) { if (newClass._mixins.indexOf(mixinClass) !== -1) {
return; 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); Object.defineProperty(newClass, classProp, pd);
} else { }
newClass._mixins = [mixinClass]; });
} }
options = options || { if (options.instanceProperties) {
staticProperties: true, if (mixinClass.prototype) {
instanceProperties: true, var instanceProxies = [];
override: false, Object.keys(mixinClass.prototype).forEach(function (instanceProp) {
proxyFunctions: false if (!newClass.prototype.hasOwnProperty(instanceProp) || options.override) {
}; var pd = Object.getOwnPropertyDescriptor(mixinClass.prototype, instanceProp);
if (options.proxyFunctions && pd.writable && typeof pd.value === 'function') {
if(options.staticProperties === undefined) { pd.value = exports.proxy(pd.value, instanceProxies);
options.staticProperties = true; }
} Object.defineProperty(newClass.prototype, instanceProp, pd);
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);
}
});
} }
});
} }
}
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 // Make sure same methods referenced by different properties have the same proxy
// For example, deleteById is an alias of removeById // For example, deleteById is an alias of removeById
proxies = proxies || []; proxies = proxies || [];
for(var i = 0; i<proxies.length; i++) { for (var i = 0; i < proxies.length; i++) {
if(proxies[i]._delegate === fn) { if (proxies[i]._delegate === fn) {
return proxies[i]; return proxies[i];
} }
} }
var f = function() { var f = function () {
return fn.apply(this, arguments); return fn.apply(this, arguments);
}; };
f._delegate = fn; f._delegate = fn;
proxies.push(f); proxies.push(f);
Object.keys(fn).forEach(function(x) { Object.keys(fn).forEach(function (x) {
f[x] = fn[x]; f[x] = fn[x];
}); });
return f; return f;

View File

@ -1,4 +1,3 @@
module.exports = List; module.exports = List;
/** /**
@ -10,229 +9,229 @@ module.exports = List;
* @constructor * @constructor
*/ */
function List(data, type, parent) { function List(data, type, parent) {
var list = this; var list = this;
if (!(list instanceof List)) { if (!(list instanceof List)) {
return new List(data, type, parent); return new List(data, type, parent);
}
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) {
throw new Error('could not create List from JSON string: ', data);
} }
}
if(typeof data === 'string') { if (data && data instanceof List) data = data.items;
try {
data = JSON.parse(data); Object.defineProperty(list, 'parent', {
} catch(e) { writable: false,
throw new Error('could not create List from JSON string: ', data); enumerable: false,
} configurable: false,
value: parent
});
Object.defineProperty(list, 'nextid', {
writable: true,
enumerable: false,
value: 1
});
data = list.items = data || [];
var Item = list.ItemType = ListItem;
if (typeof type === 'object' && type.constructor.name === 'Array') {
Item = list.ItemType = type[0] || ListItem;
}
data.forEach(function (item, i) {
data[i] = Item(item, list);
Object.defineProperty(list, data[i].id, {
writable: true,
enumerable: false,
configurable: true,
value: data[i]
});
if (list.nextid <= data[i].id) {
list.nextid = data[i].id + 1;
} }
});
if (data && data instanceof List) data = data.items; Object.defineProperty(list, 'length', {
enumerable: false,
Object.defineProperty(list, 'parent', { configurable: true,
writable: false, get: function () {
enumerable: false, return list.items.length;
configurable: false,
value: parent
});
Object.defineProperty(list, 'nextid', {
writable: true,
enumerable: false,
value: 1
});
data = list.items = data || [];
var Item = list.ItemType = ListItem;
if (typeof type === 'object' && type.constructor.name === 'Array') {
Item = list.ItemType = type[0] || ListItem;
} }
});
data.forEach(function(item, i) { return list;
data[i] = Item(item, list);
Object.defineProperty(list, data[i].id, {
writable: true,
enumerable: false,
configurable: true,
value: data[i]
});
if (list.nextid <= data[i].id) {
list.nextid = data[i].id + 1;
}
});
Object.defineProperty(list, 'length', {
enumerable: false,
configurable: true,
get: function() {
return list.items.length;
}
});
return list;
} }
var _; var _;
try { try {
var underscore = 'underscore'; var underscore = 'underscore';
_ = require(underscore); _ = require(underscore);
} catch (e) { } catch (e) {
_ = false; _ = false;
} }
if (_) { if (_) {
var _import = [ var _import = [
// collection methods // collection methods
'each', 'each',
'map', 'map',
'reduce', 'reduce',
'reduceRight', 'reduceRight',
'find', 'find',
'filter', 'filter',
'reject', 'reject',
'all', 'all',
'any', 'any',
'include', 'include',
'invoke', 'invoke',
'pluck', 'pluck',
'max', 'max',
'min', 'min',
'sortBy', 'sortBy',
'groupBy', 'groupBy',
'sortedIndex', 'sortedIndex',
'shuffle', 'shuffle',
'toArray', 'toArray',
'size', 'size',
// array methods // array methods
'first', 'first',
'initial', 'initial',
'last', 'last',
'rest', 'rest',
'compact', 'compact',
'flatten', 'flatten',
'without', 'without',
'union', 'union',
'intersection', 'intersection',
'difference', 'difference',
'uniq', 'uniq',
'zip', 'zip',
'indexOf', 'indexOf',
'lastIndexOf', 'lastIndexOf',
'range' 'range'
]; ];
_import.forEach(function(name) { _import.forEach(function (name) {
List.prototype[name] = function() { List.prototype[name] = function () {
var args = [].slice.call(arguments); var args = [].slice.call(arguments);
args.unshift(this.items); args.unshift(this.items);
return _[name].apply(_, args); return _[name].apply(_, args);
}; };
}); });
} }
['slice', 'forEach', 'filter', 'reduce', 'map'].forEach(function (method) { ['slice', 'forEach', 'filter', 'reduce', 'map'].forEach(function (method) {
var slice = [].slice; var slice = [].slice;
List.prototype[method] = function () { List.prototype[method] = function () {
return Array.prototype[method].apply(this.items, slice.call(arguments)); return Array.prototype[method].apply(this.items, slice.call(arguments));
}; };
}); });
List.prototype.find = function(pattern, field) { List.prototype.find = function (pattern, field) {
if (field) { if (field) {
var res; var res;
this.items.forEach(function(o) { this.items.forEach(function (o) {
if (o[field] == pattern) res = o; if (o[field] == pattern) res = o;
}); });
return res; return res;
} else {
return this.items[this.items.indexOf(pattern)];
}
};
List.prototype.toObject = function (onlySchema) {
var items = [];
this.items.forEach(function (item) {
if (item.toObject) {
items.push(item.toObject(onlySchema));
} else { } else {
return this.items[this.items.indexOf(pattern)]; items.push(item);
} }
});
return items;
}; };
List.prototype.toObject = function(onlySchema) { List.prototype.toJSON = function () {
var items = []; return this.toObject(true);
this.items.forEach(function(item) {
if(item.toObject) {
items.push(item.toObject(onlySchema));
} else {
items.push(item);
}
});
return items;
}; };
List.prototype.toJSON = function() { List.prototype.toString = function () {
return this.toObject(true); return JSON.stringify(this.items);
}; };
List.prototype.toString = function() { List.prototype.autoincrement = function () {
return JSON.stringify(this.items); return this.nextid++;
}; };
List.prototype.autoincrement = function() { List.prototype.push = function (obj) {
return this.nextid++; var item = new ListItem(obj, this);
this.items.push(item);
return item;
}; };
List.prototype.push = function(obj) { List.prototype.remove = function (obj) {
var item = new ListItem(obj, this); var id = obj.id ? obj.id : obj;
this.items.push(item); var found = false;
return item; this.items.forEach(function (o, i) {
}; if (id && o.id == id) {
found = i;
List.prototype.remove = function(obj) { if (o.id !== id) {
var id = obj.id ? obj.id : obj; console.log('WARNING! Type of id not matched');
var found = false; }
this.items.forEach(function(o, i) {
if (id && o.id == id) {
found = i;
if (o.id !== id) {
console.log('WARNING! Type of id not matched');
}
}
});
if (found !== false) {
delete this[id];
this.items.splice(found, 1);
} }
});
if (found !== false) {
delete this[id];
this.items.splice(found, 1);
}
}; };
List.prototype.sort = function(cb) { List.prototype.sort = function (cb) {
return this.items.sort(cb); return this.items.sort(cb);
}; };
List.prototype.map = function(cb) { List.prototype.map = function (cb) {
if (typeof cb === 'function') return this.items.map(cb); if (typeof cb === 'function') return this.items.map(cb);
if (typeof cb === 'string') return this.items.map(function(el) { if (typeof cb === 'string') return this.items.map(function (el) {
if (typeof el[cb] === 'function') return el[cb](); if (typeof el[cb] === 'function') return el[cb]();
if (el.hasOwnProperty(cb)) return el[cb]; if (el.hasOwnProperty(cb)) return el[cb];
}); });
}; };
function ListItem(data, parent) { function ListItem(data, parent) {
if(!(this instanceof ListItem)) { if (!(this instanceof ListItem)) {
return new ListItem(data, parent); return new ListItem(data, parent);
} }
if (typeof data === 'object') { if (typeof data === 'object') {
for (var i in data) this[i] = data[i]; for (var i in data) this[i] = data[i];
} else { } else {
this.id = data; this.id = data;
} }
Object.defineProperty(this, 'parent', { Object.defineProperty(this, 'parent', {
writable: false, writable: false,
enumerable: false, enumerable: false,
configurable: true, configurable: true,
value: parent value: parent
}); });
if (!this.id) { if (!this.id) {
this.id = parent.autoincrement(); this.id = parent.autoincrement();
} }
if (parent.ItemType) { if (parent.ItemType) {
this.__proto__ = parent.ItemType.prototype; this.__proto__ = parent.ItemType.prototype;
if (parent.ItemType !== ListItem) { if (parent.ItemType !== ListItem) {
parent.ItemType.apply(this); parent.ItemType.apply(this);
}
} }
}
this.save = function(c) { this.save = function (c) {
parent.parent.save(c); parent.parent.save(c);
}; };
} }

View File

@ -32,15 +32,15 @@ var slice = Array.prototype.slice;
* @constructor * @constructor
*/ */
function ModelBuilder() { function ModelBuilder() {
// create blank models pool // create blank models pool
/** /**
* @property {Object} models Model constructors * @property {Object} models Model constructors
*/ */
this.models = {}; this.models = {};
/** /**
* @property {Object} definitions Definitions of the models * @property {Object} definitions Definitions of the models
*/ */
this.definitions = {}; this.definitions = {};
} }
// Inherit from EventEmitter // Inherit from EventEmitter
@ -50,10 +50,10 @@ util.inherits(ModelBuilder, EventEmitter);
ModelBuilder.defaultInstance = new ModelBuilder(); ModelBuilder.defaultInstance = new ModelBuilder();
function isModelClass(cls) { function isModelClass(cls) {
if(!cls) { if (!cls) {
return false; return false;
} }
return cls.prototype instanceof DefaultModelBaseClass; return cls.prototype instanceof DefaultModelBaseClass;
} }
/** /**
@ -63,15 +63,15 @@ function isModelClass(cls) {
* given name if a model doesn't exist * given name if a model doesn't exist
* @returns {*} The model class * @returns {*} The model class
*/ */
ModelBuilder.prototype.getModel = function(name, forceCreate) { ModelBuilder.prototype.getModel = function (name, forceCreate) {
var model = this.models[name]; var model = this.models[name];
if(!model && forceCreate) { if (!model && forceCreate) {
model = this.define(name, {}, {unresolved: true}); model = this.define(name, {}, {unresolved: true});
} }
return model; return model;
}; };
ModelBuilder.prototype.getModelDefinition = function(name) { ModelBuilder.prototype.getModelDefinition = function (name) {
return this.definitions[name]; return this.definitions[name];
}; };
@ -107,316 +107,316 @@ ModelBuilder.prototype.getModelDefinition = function(name) {
* ``` * ```
*/ */
ModelBuilder.prototype.define = function defineClass(className, properties, settings, parent) { ModelBuilder.prototype.define = function defineClass(className, properties, settings, parent) {
var modelBuilder = this; var modelBuilder = this;
var args = slice.call(arguments); var args = slice.call(arguments);
var pluralName = (settings && settings.plural) || var pluralName = (settings && settings.plural) ||
inflection.pluralize(className); inflection.pluralize(className);
if (!className) { if (!className) {
throw new Error('Class name required'); throw new Error('Class name required');
}
if (args.length === 1) {
properties = {};
args.push(properties);
}
if (args.length === 2) {
settings = {};
args.push(settings);
}
properties = properties || {};
settings = settings || {};
// Set the strict mode to be false by default
if (settings.strict === undefined || settings.strict === null) {
settings.strict = false;
}
// Set up the base model class
var ModelBaseClass = parent || DefaultModelBaseClass;
var baseClass = settings.base || settings['super'];
if (baseClass) {
if (isModelClass(baseClass)) {
ModelBaseClass = baseClass;
} else {
ModelBaseClass = this.models[baseClass];
assert(ModelBaseClass, 'Base model is not found: ' + baseClass);
} }
if (args.length === 1) { }
properties = {};
args.push(properties); // Check if there is a unresolved model with the same name
var ModelClass = this.models[className];
// Create the ModelClass if it doesn't exist or it's resolved (override)
// TODO: [rfeng] We need to decide what names to use for built-in models such as User.
if (!ModelClass || !ModelClass.settings.unresolved) {
// every class can receive hash of data as optional param
ModelClass = function ModelConstructor(data, dataSource) {
if (!(this instanceof ModelConstructor)) {
return new ModelConstructor(data, dataSource);
}
if (ModelClass.settings.unresolved) {
throw new Error('Model ' + ModelClass.modelName + ' is not defined.');
}
ModelBaseClass.apply(this, arguments);
if (dataSource) {
hiddenProperty(this, '__dataSource', dataSource);
}
};
// mix in EventEmitter (don't inherit from)
var events = new EventEmitter();
for (var f in EventEmitter.prototype) {
if (typeof EventEmitter.prototype[f] === 'function') {
ModelClass[f] = EventEmitter.prototype[f].bind(events);
}
} }
if (args.length === 2) { hiddenProperty(ModelClass, 'modelName', className);
settings = {}; }
args.push(settings);
util.inherits(ModelClass, ModelBaseClass);
// store class in model pool
this.models[className] = ModelClass;
// Return the unresolved model
if (settings.unresolved) {
ModelClass.settings = {unresolved: true};
return ModelClass;
}
// 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];
} }
}
properties = properties || {}; // Load and inject the model classes
settings = settings || {}; 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;
});
}
// Set the strict mode to be false by default ModelClass.getter = {};
if(settings.strict === undefined || settings.strict === null) { ModelClass.setter = {};
settings.strict = false;
}
// Set up the base model class var modelDefinition = new ModelDefinition(this, className, properties, settings);
var ModelBaseClass = parent || DefaultModelBaseClass;
var baseClass = settings.base || settings['super'];
if(baseClass) {
if(isModelClass(baseClass)) {
ModelBaseClass = baseClass;
} else {
ModelBaseClass = this.models[baseClass];
assert(ModelBaseClass, 'Base model is not found: ' + baseClass);
}
}
// Check if there is a unresolved model with the same name this.definitions[className] = modelDefinition;
var ModelClass = this.models[className];
// Create the ModelClass if it doesn't exist or it's resolved (override) // expose properties on the ModelClass
// TODO: [rfeng] We need to decide what names to use for built-in models such as User. ModelClass.definition = modelDefinition;
if(!ModelClass || !ModelClass.settings.unresolved) { // keep a pointer to settings as models can use it for configuration
// every class can receive hash of data as optional param ModelClass.settings = modelDefinition.settings;
ModelClass = function ModelConstructor(data, dataSource) {
if(!(this instanceof ModelConstructor)) {
return new ModelConstructor(data, dataSource);
}
if(ModelClass.settings.unresolved) {
throw new Error('Model ' + ModelClass.modelName + ' is not defined.');
}
ModelBaseClass.apply(this, arguments);
if(dataSource) {
hiddenProperty(this, '__dataSource', dataSource);
}
};
// mix in EventEmitter (don't inherit from)
var events = new EventEmitter();
for (var f in EventEmitter.prototype) {
if (typeof EventEmitter.prototype[f] === 'function') {
ModelClass[f] = EventEmitter.prototype[f].bind(events);
}
}
hiddenProperty(ModelClass, 'modelName', className);
}
util.inherits(ModelClass, ModelBaseClass); var idInjection = settings.idInjection;
if (idInjection !== false) {
// Default to true if undefined
idInjection = true;
}
// store class in model pool var idNames = modelDefinition.idNames();
this.models[className] = ModelClass; if (idNames.length > 0) {
// id already exists
idInjection = false;
}
// Return the unresolved model // Add the id property
if(settings.unresolved) { if (idInjection) {
ModelClass.settings = {unresolved: true}; // Set up the id property
return ModelClass; ModelClass.definition.defineProperty('id', { type: Number, id: 1, generated: true });
} }
// Add metadata to the ModelClass idNames = modelDefinition.idNames(); // Reload it after rebuild
hiddenProperty(ModelClass, 'modelBuilder', modelBuilder); // Create a virtual property 'id'
hiddenProperty(ModelClass, 'dataSource', modelBuilder); // Keep for back-compatibility if (idNames.length === 1) {
hiddenProperty(ModelClass, 'pluralModelName', pluralName); var idProp = idNames[0];
hiddenProperty(ModelClass, 'relations', {}); if (idProp !== 'id') {
hiddenProperty(ModelClass, 'http', { path: '/' + pluralName }); Object.defineProperty(ModelClass.prototype, 'id', {
get: function () {
// inherit ModelBaseClass static methods var idProp = ModelClass.definition.idNames[0];
for (var i in ModelBaseClass) { return this.__data[idProp];
// We need to skip properties that are already in the subclass, for example, the event emitter methods },
if(i !== '_mixins' && !(i in ModelClass)) { configurable: true,
ModelClass[i] = ModelBaseClass[i]; enumerable: false
}
}
// 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;
}); });
} }
} else {
ModelClass.getter = {}; // Now the id property is an object that consists of multiple keys
ModelClass.setter = {}; Object.defineProperty(ModelClass.prototype, 'id', {
get: function () {
var modelDefinition = new ModelDefinition(this, className, properties, settings); var compositeId = {};
var idNames = ModelClass.definition.idNames();
this.definitions[className] = modelDefinition; for (var p in idNames) {
compositeId[p] = this.__data[p];
// 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 { return compositeId;
// Now the id property is an object that consists of multiple keys },
Object.defineProperty(ModelClass.prototype, 'id', { configurable: true,
get: function () { enumerable: false
var compositeId = {}; });
var idNames = ModelClass.definition.idNames(); }
for (var p in idNames) {
compositeId[p] = this.__data[p]; // A function to loop through the properties
} ModelClass.forEachProperty = function (cb) {
return compositeId; Object.keys(ModelClass.definition.properties).forEach(cb);
}, };
configurable: true,
enumerable: false // 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 // Merging the properties
ModelClass.forEachProperty = function (cb) { Object.keys(properties).forEach(function (key) {
Object.keys(ModelClass.definition.properties).forEach(cb); 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 // Merge the settings
ModelClass.attachTo = function (dataSource) { subclassSettings = mergeSettings(settings, subclassSettings);
dataSource.attach(this);
};
// A function to extend the model /*
ModelClass.extend = function (className, subclassProperties, subclassSettings) { Object.keys(settings).forEach(function (key) {
var properties = ModelClass.definition.properties; if(subclassSettings[key] === undefined) {
var settings = ModelClass.definition.settings; subclassSettings[key] = settings[key];
}
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
*/ */
ModelClass.registerProperty = function (propertyName) {
var properties = modelDefinition.build(); // Define the subclass
var prop = properties[propertyName]; var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass);
var DataType = prop.type;
if(!DataType) { // Calling the setup function
throw new Error('Invalid type for property ' + propertyName); 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; set: function (value) {
} else if (DataType.name === 'Date') { if (ModelClass.setter[propertyName]) {
var OrigDate = Date; ModelClass.setter[propertyName].call(this, value); // Try setter first
DataType = function Date(arg) { } else {
return new OrigDate(arg); if (!this.__data) {
}; this.__data = {};
} else if(typeof DataType === 'string') { }
DataType = modelBuilder.resolveType(DataType); 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
});
if(prop.required) { // <propertyName>$was --> __dataWas.<propertyName>
var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; Object.defineProperty(ModelClass.prototype, propertyName + '$was', {
ModelClass.validatesPresenceOf(propertyName, requiredOptions); 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, { ModelClass.forEachProperty(ModelClass.registerProperty);
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
});
// <propertyName>$was --> __dataWas.<propertyName> ModelClass.emit('defined', ModelClass);
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? return ModelClass;
// 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;
}; };
@ -428,8 +428,8 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
* @param {Object} propertyDefinition - property settings * @param {Object} propertyDefinition - property settings
*/ */
ModelBuilder.prototype.defineProperty = function (model, propertyName, propertyDefinition) { ModelBuilder.prototype.defineProperty = function (model, propertyName, propertyDefinition) {
this.definitions[model].defineProperty(propertyName, propertyDefinition); this.definitions[model].defineProperty(propertyName, propertyDefinition);
this.models[model].registerProperty(propertyName); this.models[model].registerProperty(propertyName);
}; };
/** /**
@ -456,69 +456,67 @@ ModelBuilder.prototype.defineProperty = function (model, propertyName, propertyD
* }); * });
*/ */
ModelBuilder.prototype.extendModel = function (model, props) { ModelBuilder.prototype.extendModel = function (model, props) {
var t = this; var t = this;
Object.keys(props).forEach(function (propName) { Object.keys(props).forEach(function (propName) {
var definition = props[propName]; var definition = props[propName];
t.defineProperty(model, propName, definition); t.defineProperty(model, propName, definition);
}); });
}; };
ModelBuilder.prototype.copyModel = function copyModel(Master) { ModelBuilder.prototype.copyModel = function copyModel(Master) {
var modelBuilder = this; var modelBuilder = this;
var className = Master.modelName; var className = Master.modelName;
var md = Master.modelBuilder.definitions[className]; var md = Master.modelBuilder.definitions[className];
var Slave = function SlaveModel() { var Slave = function SlaveModel() {
Master.apply(this, [].slice.call(arguments)); 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); return Slave;
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;
}; };
/*! /*!
* Define hidden property * Define hidden property
*/ */
function hiddenProperty(where, property, value) { function hiddenProperty(where, property, value) {
Object.defineProperty(where, property, { Object.defineProperty(where, property, {
writable: true, writable: true,
enumerable: false, enumerable: false,
configurable: true, configurable: true,
value: value value: value
}); });
} }
/** /**
* Get the schema name * Get the schema name
*/ */
ModelBuilder.prototype.getSchemaName = function (name) { ModelBuilder.prototype.getSchemaName = function (name) {
if (name) { if (name) {
return name; return name;
} }
if (typeof this._nameCount !== 'number') { if (typeof this._nameCount !== 'number') {
this._nameCount = 0; this._nameCount = 0;
} else { } else {
this._nameCount++; this._nameCount++;
} }
return 'AnonymousModel_' + 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 * @param {String} type The type string, such as 'number', 'Number', 'boolean', or 'String'. It's case insensitive
* @returns {Function} if the type is resolved * @returns {Function} if the type is resolved
*/ */
ModelBuilder.prototype.resolveType = function(type) { ModelBuilder.prototype.resolveType = function (type) {
if (!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; 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 * @returns {Object} A map of model constructors keyed by model name
*/ */
ModelBuilder.prototype.buildModels = function (schemas) { ModelBuilder.prototype.buildModels = function (schemas) {
var models = {}; var models = {};
// Normalize the schemas to be an array of the schema objects {name: <name>, properties: {}, options: {}} // Normalize the schemas to be an array of the schema objects {name: <name>, properties: {}, options: {}}
if (!Array.isArray(schemas)) { if (!Array.isArray(schemas)) {
if (schemas.properties && schemas.name) { if (schemas.properties && schemas.name) {
// Only one item // Only one item
schemas = [schemas]; schemas = [schemas];
} else { } else {
// Anonymous schema // Anonymous schema
schemas = [ schemas = [
{ {
name: this.getSchemaName(), name: this.getSchemaName(),
properties: schemas, properties: schemas,
options: {anonymous: true} options: {anonymous: true}
}
];
} }
];
} }
}
var relations = []; var relations = [];
for (var s in schemas) { for (var s in schemas) {
var name = this.getSchemaName(schemas[s].name); var name = this.getSchemaName(schemas[s].name);
schemas[s].name = name; schemas[s].name = name;
var model = this.define(schemas[s].name, schemas[s].properties, schemas[s].options); var model = this.define(schemas[s].name, schemas[s].properties, schemas[s].options);
models[name] = model; models[name] = model;
relations = relations.concat(model.definition.relations); relations = relations.concat(model.definition.relations);
} }
// Connect the models based on the relations // Connect the models based on the relations
for (var i = 0; i < relations.length; i++) { for (var i = 0; i < relations.length; i++) {
var relation = relations[i]; var relation = relations[i];
var sourceModel = models[relation.source]; var sourceModel = models[relation.source];
var targetModel = models[relation.target]; var targetModel = models[relation.target];
if (sourceModel && targetModel) { if (sourceModel && targetModel) {
if(typeof sourceModel[relation.type] === 'function') { if (typeof sourceModel[relation.type] === 'function') {
sourceModel[relation.type](targetModel, {as: relation.as}); 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 * @param [Object} options The options
* @returns {} * @returns {}
*/ */
ModelBuilder.prototype.buildModelFromInstance = function(name, json, options) { ModelBuilder.prototype.buildModelFromInstance = function (name, json, options) {
// Introspect the JSON document to generate a schema // Introspect the JSON document to generate a schema
var schema = introspect(json); var schema = introspect(json);
// Create a model for the generated schema // Create a model for the generated schema
return this.define(name, schema, options); return this.define(name, schema, options);
}; };

View File

@ -21,27 +21,27 @@ module.exports = ModelDefinition;
* *
*/ */
function ModelDefinition(modelBuilder, name, properties, settings) { function ModelDefinition(modelBuilder, name, properties, settings) {
if (!(this instanceof ModelDefinition)) { if (!(this instanceof ModelDefinition)) {
// Allow to call ModelDefinition without new // Allow to call ModelDefinition without new
return new ModelDefinition(modelBuilder, name, properties, settings); return new ModelDefinition(modelBuilder, name, properties, settings);
} }
this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance; this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance;
assert(name, 'name is missing'); assert(name, 'name is missing');
if (arguments.length === 2 && typeof name === 'object') { if (arguments.length === 2 && typeof name === 'object') {
var schema = name; var schema = name;
this.name = schema.name; this.name = schema.name;
this.rawProperties = schema.properties || {}; // Keep the raw property definitions this.rawProperties = schema.properties || {}; // Keep the raw property definitions
this.settings = schema.settings || {}; this.settings = schema.settings || {};
} else { } else {
assert(typeof name === 'string', 'name must be a string'); assert(typeof name === 'string', 'name must be a string');
this.name = name; this.name = name;
this.rawProperties = properties || {}; // Keep the raw property definitions this.rawProperties = properties || {}; // Keep the raw property definitions
this.settings = settings || {}; this.settings = settings || {};
} }
this.relations = []; this.relations = [];
this.properties = null; this.properties = null;
this.build(); this.build();
} }
util.inherits(ModelDefinition, EventEmitter); util.inherits(ModelDefinition, EventEmitter);
@ -49,18 +49,17 @@ util.inherits(ModelDefinition, EventEmitter);
// Set up types // Set up types
require('./types')(ModelDefinition); require('./types')(ModelDefinition);
/** /**
* Return table name for specified `modelName` * Return table name for specified `modelName`
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb' * @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
*/ */
ModelDefinition.prototype.tableName = function (connectorType) { ModelDefinition.prototype.tableName = function (connectorType) {
var settings = this.settings; var settings = this.settings;
if(settings[connectorType]) { if (settings[connectorType]) {
return settings[connectorType].table || settings[connectorType].tableName || this.name; return settings[connectorType].table || settings[connectorType].tableName || this.name;
} else { } else {
return this.name; return this.name;
} }
}; };
/** /**
@ -70,16 +69,16 @@ ModelDefinition.prototype.tableName = function (connectorType) {
* @returns {String} columnName * @returns {String} columnName
*/ */
ModelDefinition.prototype.columnName = function (connectorType, propertyName) { ModelDefinition.prototype.columnName = function (connectorType, propertyName) {
if(!propertyName) { if (!propertyName) {
return propertyName; return propertyName;
} }
this.build(); this.build();
var property = this.properties[propertyName]; var property = this.properties[propertyName];
if(property && property[connectorType]) { if (property && property[connectorType]) {
return property[connectorType].column || property[connectorType].columnName || propertyName; return property[connectorType].column || property[connectorType].columnName || propertyName;
} else { } else {
return propertyName; return propertyName;
} }
}; };
/** /**
@ -89,16 +88,16 @@ ModelDefinition.prototype.columnName = function (connectorType, propertyName) {
* @returns {Object} column metadata * @returns {Object} column metadata
*/ */
ModelDefinition.prototype.columnMetadata = function (connectorType, propertyName) { ModelDefinition.prototype.columnMetadata = function (connectorType, propertyName) {
if(!propertyName) { if (!propertyName) {
return propertyName; return propertyName;
} }
this.build(); this.build();
var property = this.properties[propertyName]; var property = this.properties[propertyName];
if(property && property[connectorType]) { if (property && property[connectorType]) {
return property[connectorType]; return property[connectorType];
} else { } else {
return null; return null;
} }
}; };
/** /**
@ -107,17 +106,17 @@ ModelDefinition.prototype.columnMetadata = function (connectorType, propertyName
* @returns {String[]} column names * @returns {String[]} column names
*/ */
ModelDefinition.prototype.columnNames = function (connectorType) { ModelDefinition.prototype.columnNames = function (connectorType) {
this.build(); this.build();
var props = this.properties; var props = this.properties;
var cols = []; var cols = [];
for(var p in props) { for (var p in props) {
if(props[p][connectorType]) { if (props[p][connectorType]) {
cols.push(property[connectorType].column || props[p][connectorType].columnName || p); cols.push(property[connectorType].column || props[p][connectorType].columnName || p);
} else { } else {
cols.push(p); cols.push(p);
}
} }
return cols; }
return cols;
}; };
/** /**
@ -125,27 +124,27 @@ ModelDefinition.prototype.columnNames = function (connectorType) {
* @returns {Object[]} property name/index for IDs * @returns {Object[]} property name/index for IDs
*/ */
ModelDefinition.prototype.ids = function () { ModelDefinition.prototype.ids = function () {
if(this._ids) { if (this._ids) {
return 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 = []; if (typeof id !== 'number') {
this.build(); id = 1;
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});
} }
ids.sort(function (a, b) { ids.push({name: key, id: id});
return a.key - b.key; }
}); ids.sort(function (a, b) {
this._ids = ids; return a.key - b.key;
return ids; });
this._ids = ids;
return ids;
}; };
/** /**
@ -153,20 +152,21 @@ ModelDefinition.prototype.ids = function () {
* @param {String} modelName The model name * @param {String} modelName The model name
* @returns {String} columnName for ID * @returns {String} columnName for ID
*/ */
ModelDefinition.prototype.idColumnName = function(connectorType) { ModelDefinition.prototype.idColumnName = function (connectorType) {
return this.columnName(connectorType, this.idName()); return this.columnName(connectorType, this.idName());
}; };
/** /**
* Find the ID property name * Find the ID property name
* @returns {String} property name for ID * @returns {String} property name for ID
*/ */
ModelDefinition.prototype.idName = function() { ModelDefinition.prototype.idName = function () {
var id = this.ids()[0]; var id = this.ids()[0];
if(this.properties.id && this.properties.id.id) { if (this.properties.id && this.properties.id.id) {
return 'id'; return 'id';
} else {} } else {
return id && id.name; }
return id && id.name;
}; };
/** /**
@ -174,11 +174,11 @@ ModelDefinition.prototype.idName = function() {
* @returns {String[]} property names for IDs * @returns {String[]} property names for IDs
*/ */
ModelDefinition.prototype.idNames = function () { ModelDefinition.prototype.idNames = function () {
var ids = this.ids(); var ids = this.ids();
var names = ids.map(function (id) { var names = ids.map(function (id) {
return id.name; return id.name;
}); });
return names; return names;
}; };
/** /**
@ -186,19 +186,19 @@ ModelDefinition.prototype.idNames = function () {
* @returns {{}} * @returns {{}}
*/ */
ModelDefinition.prototype.indexes = function () { ModelDefinition.prototype.indexes = function () {
this.build(); this.build();
var indexes = {}; var indexes = {};
if (this.settings.indexes) { if (this.settings.indexes) {
for (var i in this.settings.indexes) { for (var i in this.settings.indexes) {
indexes[i] = this.settings.indexes[i]; indexes[i] = this.settings.indexes[i];
}
} }
for (var p in this.properties) { }
if (this.properties[p].index) { for (var p in this.properties) {
indexes[p + '_index'] = this.properties[p].index; 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 * @param {Boolean} force Forcing rebuild
*/ */
ModelDefinition.prototype.build = function (forceRebuild) { ModelDefinition.prototype.build = function (forceRebuild) {
if(forceRebuild) { if (forceRebuild) {
this.properties = null; this.properties = null;
this.relations = []; this.relations = [];
this._ids = null; this._ids = null;
} }
if (this.properties) { 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; 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 * @param {Object} propertyDefinition The property definition
*/ */
ModelDefinition.prototype.defineProperty = function (propertyName, propertyDefinition) { ModelDefinition.prototype.defineProperty = function (propertyName, propertyDefinition) {
this.rawProperties[propertyName] = propertyDefinition; this.rawProperties[propertyName] = propertyDefinition;
this.build(true); this.build(true);
}; };
function isModelClass(cls) { function isModelClass(cls) {
if(!cls) { if (!cls) {
return false; return false;
} }
return cls.prototype instanceof ModelBaseClass; return cls.prototype instanceof ModelBaseClass;
} }
ModelDefinition.prototype.toJSON = function(forceRebuild) { ModelDefinition.prototype.toJSON = function (forceRebuild) {
if(forceRebuild) { if (forceRebuild) {
this.json = null; this.json = null;
} }
if(this.json) { 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; 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;
}; };

View File

@ -28,19 +28,19 @@ var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text'];
* @param {Object} data - initial object data * @param {Object} data - initial object data
*/ */
function ModelBaseClass(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 // 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) { function clone(data) {
/* /*
if(!(data instanceof ModelBaseClass)) { if(!(data instanceof ModelBaseClass)) {
if(data && (Array.isArray(data) || 'object' === typeof data)) { if(data && (Array.isArray(data) || 'object' === typeof data)) {
return traverse(data).clone(); return traverse(data).clone();
} }
} }
*/ */
return data; return data;
} }
/** /**
* Initialize properties * Initialize properties
@ -49,117 +49,117 @@ function clone(data) {
* @private * @private
*/ */
ModelBaseClass.prototype._initProperties = function (data, applySetters) { ModelBaseClass.prototype._initProperties = function (data, applySetters) {
var self = this; var self = this;
var ctor = this.constructor; var ctor = this.constructor;
var properties = ctor.definition.build(); var properties = ctor.definition.build();
data = data || {}; data = data || {};
Object.defineProperty(this, '__cachedRelations', { Object.defineProperty(this, '__cachedRelations', {
writable: true, writable: true,
enumerable: false, enumerable: false,
configurable: true, configurable: true,
value: {} value: {}
}); });
Object.defineProperty(this, '__data', { Object.defineProperty(this, '__data', {
writable: true, writable: true,
enumerable: false, enumerable: false,
configurable: true, configurable: true,
value: {} value: {}
}); });
Object.defineProperty(this, '__dataWas', { Object.defineProperty(this, '__dataWas', {
writable: true, writable: true,
enumerable: false, enumerable: false,
configurable: true, configurable: true,
value: {} value: {}
}); });
if (data['__cachedRelations']) { if (data['__cachedRelations']) {
this.__cachedRelations = 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) { ctor.forEachProperty(function (propertyName) {
if (i in properties) {
this.__data[i] = this.__dataWas[i] = clone(data[i]); var type = properties[propertyName].type;
} else if (i in ctor.relations) {
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo]; if (BASE_TYPES.indexOf(type.name) === -1) {
this.__cachedRelations[i] = data[i]; if (typeof self.__data[propertyName] !== 'object' && self.__data[propertyName]) {
} else { try {
if(strict === false) { self.__data[propertyName] = JSON.parse(self.__data[propertyName] + '');
this.__data[i] = this.__dataWas[i] = clone(data[i]); } catch (e) {
} else if(strict === 'throw') { self.__data[propertyName] = String(self.__data[propertyName]);
throw new Error('Unknown property: ' + i);
}
} }
}
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)) { function getDefault(propertyName) {
self[propertyName] = self.__data[propertyName] || data[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 this.trigger('initialize');
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');
} }
/** /**
@ -167,24 +167,24 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
* @param {Object} params - various property configuration * @param {Object} params - various property configuration
*/ */
ModelBaseClass.defineProperty = function (prop, params) { ModelBaseClass.defineProperty = function (prop, params) {
this.dataSource.defineProperty(this.modelName, prop, params); this.dataSource.defineProperty(this.modelName, prop, params);
}; };
ModelBaseClass.getPropertyType = function (propName) { ModelBaseClass.getPropertyType = function (propName) {
var prop = this.definition.properties[propName]; var prop = this.definition.properties[propName];
if(!prop) { if (!prop) {
// The property is not part of the definition // The property is not part of the definition
return null; return null;
} }
if (!prop.type) { if (!prop.type) {
throw new Error('Type not defined for property ' + this.modelName + '.' + propName); throw new Error('Type not defined for property ' + this.modelName + '.' + propName);
// return null; // return null;
} }
return prop.type.name; return prop.type.name;
}; };
ModelBaseClass.prototype.getPropertyType = function (propName) { 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 * @override default toString method
*/ */
ModelBaseClass.toString = function () { 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) * @returns {Object} - canonical object representation (no getters and setters)
*/ */
ModelBaseClass.prototype.toObject = function (onlySchema) { ModelBaseClass.prototype.toObject = function (onlySchema) {
var data = {}; var data = {};
var self = this; var self = this;
var schemaLess = this.constructor.definition.settings.strict === false || !onlySchema; var schemaLess = this.constructor.definition.settings.strict === false || !onlySchema;
this.constructor.forEachProperty(function (propertyName) { this.constructor.forEachProperty(function (propertyName) {
if (self[propertyName] instanceof List) { if (self[propertyName] instanceof List) {
data[propertyName] = self[propertyName].toObject(!schemaLess); data[propertyName] = self[propertyName].toObject(!schemaLess);
} else if (self.__data.hasOwnProperty(propertyName)) { } else if (self.__data.hasOwnProperty(propertyName)) {
if(self[propertyName] !== undefined && self[propertyName]!== null && self[propertyName].toObject) { if (self[propertyName] !== undefined && self[propertyName] !== null && self[propertyName].toObject) {
data[propertyName] = self[propertyName].toObject(!schemaLess); data[propertyName] = self[propertyName].toObject(!schemaLess);
} else { } else {
data[propertyName] = self[propertyName]; data[propertyName] = self[propertyName];
} }
} else { } else {
data[propertyName] = null; 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;
}
}
}
} }
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) { // ModelBaseClass.prototype.hasOwnProperty = function (prop) {
@ -244,13 +244,13 @@ ModelBaseClass.prototype.toObject = function (onlySchema) {
// }; // };
ModelBaseClass.prototype.toJSON = function () { ModelBaseClass.prototype.toJSON = function () {
return this.toObject(); return this.toObject();
}; };
ModelBaseClass.prototype.fromObject = function (obj) { ModelBaseClass.prototype.fromObject = function (obj) {
for(var key in obj) { for (var key in obj) {
this[key] = obj[key]; this[key] = obj[key];
} }
}; };
/** /**
@ -260,7 +260,7 @@ ModelBaseClass.prototype.fromObject = function (obj) {
* @return Boolean * @return Boolean
*/ */
ModelBaseClass.prototype.propertyChanged = function propertyChanged(propertyName) { 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 * initial state
*/ */
ModelBaseClass.prototype.reset = function () { ModelBaseClass.prototype.reset = function () {
var obj = this; var obj = this;
for(var k in obj) { for (var k in obj) {
if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) { if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) {
delete obj[k]; delete obj[k];
}
if (obj.propertyChanged(k)) {
obj[k] = obj[k + '$was'];
}
} }
if (obj.propertyChanged(k)) {
obj[k] = obj[k + '$was'];
}
}
}; };
ModelBaseClass.prototype.inspect = function () { 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) { ModelBaseClass.mixin = function (anotherClass, options) {
return jutil.mixin(this, anotherClass, options); return jutil.mixin(this, anotherClass, options);
}; };
ModelBaseClass.prototype.getDataSource = function () { ModelBaseClass.prototype.getDataSource = function () {

View File

@ -11,11 +11,11 @@ function Relation() {
} }
Relation.relationNameFor = function relationNameFor(foreignKey) { Relation.relationNameFor = function relationNameFor(foreignKey) {
for (var rel in this.relations) { for (var rel in this.relations) {
if (this.relations[rel].type === 'belongsTo' && this.relations[rel].keyFrom === foreignKey) { if (this.relations[rel].type === 'belongsTo' && this.relations[rel].keyFrom === foreignKey) {
return rel; return rel;
}
} }
}
}; };
/** /**
@ -26,131 +26,132 @@ Relation.relationNameFor = function relationNameFor(foreignKey) {
* @example `User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});` * @example `User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});`
*/ */
Relation.hasMany = function hasMany(anotherClass, params) { Relation.hasMany = function hasMany(anotherClass, params) {
var thisClassName = this.modelName; var thisClassName = this.modelName;
params = params || {}; params = params || {};
if (typeof anotherClass === 'string') { if (typeof anotherClass === 'string') {
params.as = anotherClass; params.as = anotherClass;
if (params.model) { if (params.model) {
anotherClass = params.model; anotherClass = params.model;
} else { } else {
var anotherClassName = i8n.singularize(anotherClass).toLowerCase(); var anotherClassName = i8n.singularize(anotherClass).toLowerCase();
for(var name in this.dataSource.modelBuilder.models) { for (var name in this.dataSource.modelBuilder.models) {
if (name.toLowerCase() === anotherClassName) { if (name.toLowerCase() === anotherClassName) {
anotherClass = this.dataSource.modelBuilder.models[name]; 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] = { this.relations[methodName] = {
type: 'hasMany', type: 'hasMany',
keyFrom: idName, keyFrom: idName,
keyTo: fk, keyTo: fk,
modelTo: anotherClass, modelTo: anotherClass,
multiple: true multiple: true
}; };
// each instance of this class should have method named // each instance of this class should have method named
// pluralize(anotherClass.modelName) // pluralize(anotherClass.modelName)
// which is actually just anotherClass.find({where: {thisModelNameId: this[idName]}}, cb); // which is actually just anotherClass.find({where: {thisModelNameId: this[idName]}}, cb);
var scopeMethods = { var scopeMethods = {
findById: find, findById: find,
destroy: destroy destroy: destroy
}; };
if (params.through) { if (params.through) {
var fk2 = i8n.camelize(anotherClass.modelName + '_id', true); var fk2 = i8n.camelize(anotherClass.modelName + '_id', true);
scopeMethods.create = function create(data, done) { scopeMethods.create = function create(data, done) {
if (typeof data !== 'object') { if (typeof data !== 'object') {
done = data; done = data;
data = {}; data = {};
} }
if ('function' !== typeof done) { if ('function' !== typeof done) {
done = function() {}; done = function () {
} };
var self = this; }
anotherClass.create(data, function(err, ac) { var self = this;
if (err) return done(err, ac); anotherClass.create(data, function (err, ac) {
var d = {}; if (err) return done(err, ac);
d[params.through.relationNameFor(fk)] = self; var d = {};
d[params.through.relationNameFor(fk2)] = ac; d[params.through.relationNameFor(fk)] = self;
params.through.create(d, function(e) { d[params.through.relationNameFor(fk2)] = ac;
if (e) { params.through.create(d, function (e) {
ac.destroy(function() { if (e) {
done(e); ac.destroy(function () {
}); done(e);
} else {
done(err, ac);
}
});
}); });
}; } else {
scopeMethods.add = function(acInst, done) { done(err, ac);
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'));
}
}); });
});
};
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. * This optional parameter default value is false, so the related object will be loaded from cache if available.
*/ */
Relation.belongsTo = function (anotherClass, params) { Relation.belongsTo = function (anotherClass, params) {
params = params || {}; params = params || {};
if ('string' === typeof anotherClass) { if ('string' === typeof anotherClass) {
params.as = anotherClass; params.as = anotherClass;
if (params.model) { if (params.model) {
anotherClass = params.model; anotherClass = params.model;
} else { } else {
var anotherClassName = anotherClass.toLowerCase(); var anotherClassName = anotherClass.toLowerCase();
for(var name in this.dataSource.modelBuilder.models) { for (var name in this.dataSource.modelBuilder.models) {
if (name.toLowerCase() === anotherClassName) { if (name.toLowerCase() === anotherClassName) {
anotherClass = this.dataSource.modelBuilder.models[name]; anotherClass = this.dataSource.modelBuilder.models[name];
}
}
} }
}
} }
}
var idName = this.dataSource.idName(anotherClass.modelName) || 'id'; var idName = this.dataSource.idName(anotherClass.modelName) || 'id';
var methodName = params.as || i8n.camelize(anotherClass.modelName, true); var methodName = params.as || i8n.camelize(anotherClass.modelName, true);
var fk = params.foreignKey || methodName + 'Id'; var fk = params.foreignKey || methodName + 'Id';
this.relations[methodName] = { this.relations[methodName] = {
type: 'belongsTo', type: 'belongsTo',
keyFrom: fk, keyFrom: fk,
keyTo: idName, keyTo: idName,
modelTo: anotherClass, modelTo: anotherClass,
multiple: false multiple: false
}; };
this.dataSource.defineForeignKey(this.modelName, fk, anotherClass.modelName); this.dataSource.defineForeignKey(this.modelName, fk, anotherClass.modelName);
this.prototype['__finders__'] = this.prototype['__finders__'] || {}; this.prototype['__finders__'] = this.prototype['__finders__'] || {};
this.prototype['__finders__'][methodName] = function (id, cb) { this.prototype['__finders__'][methodName] = function (id, cb) {
if (id === null) { if (id === null) {
cb(null, null); cb(null, null);
return; return;
} }
anotherClass.findById(id, function (err,inst) { anotherClass.findById(id, function (err, inst) {
if (err) return cb(err); if (err) return cb(err);
if (!inst) return cb(null, null); if (!inst) return cb(null, null);
if (inst[idName] === this[fk]) { if (inst[idName] === this[fk]) {
cb(null, inst); cb(null, inst);
} else { } else {
cb(new Error('Permission denied')); cb(new Error('Permission denied'));
} }
}.bind(this)); }.bind(this));
}; };
this.prototype[methodName] = function (refresh, p) { this.prototype[methodName] = function (refresh, p) {
if (arguments.length === 1) { if (arguments.length === 1) {
p = refresh; p = refresh;
refresh = false; refresh = false;
} else if (arguments.length > 2) { } else if (arguments.length > 2) {
throw new Error('Method can\'t be called with more than two arguments'); throw new Error('Method can\'t be called with more than two arguments');
} }
var self = this; var self = this;
var cachedValue; var cachedValue;
if (!refresh && this.__cachedRelations && (typeof this.__cachedRelations[methodName] !== 'undefined')) { if (!refresh && this.__cachedRelations && (typeof this.__cachedRelations[methodName] !== 'undefined')) {
cachedValue = this.__cachedRelations[methodName]; cachedValue = this.__cachedRelations[methodName];
} }
if (p instanceof ModelBaseClass) { // acts as setter if (p instanceof ModelBaseClass) { // acts as setter
this[fk] = p[idName]; this[fk] = p[idName];
this.__cachedRelations[methodName] = p; this.__cachedRelations[methodName] = p;
} else if (typeof p === 'function') { // acts as async getter } else if (typeof p === 'function') { // acts as async getter
if (typeof cachedValue === 'undefined') { if (typeof cachedValue === 'undefined') {
this.__finders__[methodName].apply(self, [this[fk], function(err, inst) { this.__finders__[methodName].apply(self, [this[fk], function (err, inst) {
if (!err) { if (!err) {
self.__cachedRelations[methodName] = inst; self.__cachedRelations[methodName] = inst;
} }
p(err, inst); p(err, inst);
}]); }]);
return this[fk]; return this[fk];
} else { } else {
p(null, cachedValue); p(null, cachedValue);
return cachedValue; return cachedValue;
} }
} else if (typeof p === 'undefined') { // acts as sync getter } else if (typeof p === 'undefined') { // acts as sync getter
return this[fk]; return this[fk];
} else { // setter } else { // setter
this[fk] = p; this[fk] = p;
delete this.__cachedRelations[methodName]; delete this.__cachedRelations[methodName];
} }
}; };
}; };
@ -268,40 +269,40 @@ Relation.belongsTo = function (anotherClass, params) {
* Post.hasAndBelongsToMany('tags'); creates connection model 'PostTag' * Post.hasAndBelongsToMany('tags'); creates connection model 'PostTag'
*/ */
Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params) { Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params) {
params = params || {}; params = params || {};
var models = this.dataSource.modelBuilder.models; var models = this.dataSource.modelBuilder.models;
if ('string' === typeof anotherClass) { if ('string' === typeof anotherClass) {
params.as = anotherClass; params.as = anotherClass;
if (params.model) { if (params.model) {
anotherClass = params.model; anotherClass = params.model;
} else { } else {
anotherClass = lookupModel(i8n.singularize(anotherClass)) || anotherClass = lookupModel(i8n.singularize(anotherClass)) ||
anotherClass; anotherClass;
}
if (typeof anotherClass === 'string') {
throw new Error('Could not find "' + anotherClass + '" relation for ' + this.modelName);
}
} }
if (typeof anotherClass === 'string') {
if (!params.through) { throw new Error('Could not find "' + anotherClass + '" relation for ' + this.modelName);
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);
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) { this.hasMany(anotherClass, {as: params.as, through: params.through});
var lookupClassName = modelName.toLowerCase();
for (var name in models) { function lookupModel(modelName) {
if (name.toLowerCase() === lookupClassName) { var lookupClassName = modelName.toLowerCase();
return models[name]; for (var name in models) {
} if (name.toLowerCase() === lookupClassName) {
} return models[name];
}
} }
}
}; };

View File

@ -5,192 +5,192 @@ exports.defineScope = defineScope;
function defineScope(cls, targetClass, name, params, methods) { function defineScope(cls, targetClass, name, params, methods) {
// collect meta info about scope // collect meta info about scope
if (!cls._scopeMeta) { if (!cls._scopeMeta) {
cls._scopeMeta = {}; cls._scopeMeta = {};
} }
// only makes sence to add scope in meta if base and target classes // only makes sence to add scope in meta if base and target classes
// are same // are same
if (cls === targetClass) { if (cls === targetClass) {
cls._scopeMeta[name] = params; cls._scopeMeta[name] = params;
} else { } else {
if (!targetClass._scopeMeta) { if (!targetClass._scopeMeta) {
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 if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) {
Object.defineProperty(cls, name, { var self = this;
enumerable: false, var params = mergeParams(actualCond, caller._scope);
configurable: true, return targetClass.find(params, function (err, data) {
/** if (!err && saveOnCache) {
* This defines a property for the scope. For example, user.accounts or if (!self.__cachedRelations) {
* User.vips. Please note the cls can be the model class or prototype of self.__cachedRelations = {};
* the model class. }
* self.__cachedRelations[name] = data;
* 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);
} }
cb(err, data);
});
} else {
cb(null, this.__cachedRelations[name]);
}
};
f._scope = typeof params === 'function' ? params.call(this) : params;
// define sub-scopes f.build = build;
Object.keys(targetClass._scopeMeta).forEach(function (name) { f.create = create;
Object.defineProperty(f, name, { f.destroyAll = destroyAll;
enumerable: false, for (var i in methods) {
get: function () { f[i] = methods[i].bind(this);
mergeParams(f._scope, targetClass._scopeMeta[name]); }
return f;
} // define sub-scopes
}); Object.keys(targetClass._scopeMeta).forEach(function (name) {
}.bind(this)); Object.defineProperty(f, name, {
enumerable: false,
get: function () {
mergeParams(f._scope, targetClass._scopeMeta[name]);
return f; 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) { // overwrite order
base = base || {}; if (update.order) {
if (update.where) { base.order = update.order;
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;
} }
return base;
}
} }
/** /**
@ -200,12 +200,12 @@ function defineScope(cls, targetClass, name, params, methods) {
* @returns {Object} `base` * @returns {Object} `base`
*/ */
function merge(base, update) { function merge(base, update) {
base = base || {}; base = base || {};
if (update) { if (update) {
Object.keys(update).forEach(function (key) { Object.keys(update).forEach(function (key) {
base[key] = update[key]; base[key] = update[key];
}); });
} }
return base; return base;
} }

View File

@ -8,7 +8,7 @@ module.exports = BaseSQL;
* @constructor * @constructor
*/ */
function BaseSQL() { function BaseSQL() {
Connector.apply(this, [].slice.call(arguments)); Connector.apply(this, [].slice.call(arguments));
} }
util.inherits(BaseSQL, Connector); util.inherits(BaseSQL, Connector);
@ -20,33 +20,32 @@ util.inherits(BaseSQL, Connector);
BaseSQL.prototype.relational = true; BaseSQL.prototype.relational = true;
BaseSQL.prototype.query = function () { 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) { 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) { BaseSQL.prototype.queryOne = function (sql, callback) {
return this.query(sql, function (err, data) { return this.query(sql, function (err, data) {
if (err) return callback(err); if (err) return callback(err);
callback(err, data[0]); callback(err, data[0]);
}); });
}; };
/** /**
* Get the table name for a given model * Get the table name for a given model
* @param {String} model The model name * @param {String} model The model name
* @returns {String} The table name * @returns {String} The table name
*/ */
BaseSQL.prototype.table = function (model) { BaseSQL.prototype.table = function (model) {
var name = this.getDataSource(model).tableName(model); var name = this.getDataSource(model).tableName(model);
var dbName = this.dbName; var dbName = this.dbName;
if(typeof dbName === 'function') { if (typeof dbName === 'function') {
name = dbName(name); name = dbName(name);
} }
return name; return name;
}; };
/** /**
@ -56,12 +55,12 @@ BaseSQL.prototype.table = function (model) {
* @returns {String} The column name * @returns {String} The column name
*/ */
BaseSQL.prototype.column = function (model, property) { BaseSQL.prototype.column = function (model, property) {
var name = this.getDataSource(model).columnName(model, property); var name = this.getDataSource(model).columnName(model, property);
var dbName = this.dbName; var dbName = this.dbName;
if(typeof dbName === 'function') { if (typeof dbName === 'function') {
name = dbName(name); name = dbName(name);
} }
return name; return name;
}; };
/** /**
@ -71,7 +70,7 @@ BaseSQL.prototype.column = function (model, property) {
* @returns {Object} The column metadata * @returns {Object} The column metadata
*/ */
BaseSQL.prototype.columnMetadata = function (model, property) { 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 * @returns {String} The property name for a given column
*/ */
BaseSQL.prototype.propertyName = function (model, column) { BaseSQL.prototype.propertyName = function (model, column) {
var props = this._models[model].properties; var props = this._models[model].properties;
for(var p in props) { for (var p in props) {
if(this.column(model, p) === column) { if (this.column(model, p) === column) {
return p; return p;
}
} }
return null; }
return null;
}; };
/** /**
@ -96,12 +95,13 @@ BaseSQL.prototype.propertyName = function (model, column) {
* @returns {String} The column name * @returns {String} The column name
*/ */
BaseSQL.prototype.idColumn = function (model) { BaseSQL.prototype.idColumn = function (model) {
var name = this.getDataSource(model).idColumnName(model);; var name = this.getDataSource(model).idColumnName(model);
var dbName = this.dbName; ;
if(typeof dbName === 'function') { var dbName = this.dbName;
name = dbName(name); if (typeof dbName === 'function') {
} name = dbName(name);
return name; }
return name;
}; };
/** /**
@ -110,7 +110,7 @@ BaseSQL.prototype.idColumn = function (model) {
* @returns {String} the escaped id column name * @returns {String} the escaped id column name
*/ */
BaseSQL.prototype.idColumnEscaped = function (model) { 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 * @param {String} name The name
*/ */
BaseSQL.prototype.escapeName = function (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 * @returns {String} the escaped table name
*/ */
BaseSQL.prototype.tableEscaped = function (model) { 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 * @returns {String} The escaped column name
*/ */
BaseSQL.prototype.columnEscaped = function (model, property) { 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 * @param {Function} callback The callback function
*/ */
BaseSQL.prototype.save = function (model, data, callback) { BaseSQL.prototype.save = function (model, data, callback) {
var sql = 'UPDATE ' + this.tableEscaped(model) + ' SET ' var sql = 'UPDATE ' + this.tableEscaped(model) + ' SET '
+ this.toFields(model, data) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + Number(data.id); + this.toFields(model, data) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + Number(data.id);
this.query(sql, function (err) { this.query(sql, function (err) {
callback(err); callback(err);
}); });
}; };
/** /**
* Check if a model instance exists for the given id value * Check if a model instance exists for the given id value
* @param {String} model The model name * @param {String} model The model name
@ -163,13 +162,13 @@ BaseSQL.prototype.save = function (model, data, callback) {
* @param {Function} callback The callback function * @param {Function} callback The callback function
*/ */
BaseSQL.prototype.exists = function (model, id, callback) { BaseSQL.prototype.exists = function (model, id, callback) {
var sql = 'SELECT 1 FROM ' + var sql = 'SELECT 1 FROM ' +
this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + Number(id) + ' LIMIT 1'; this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + Number(id) + ' LIMIT 1';
this.query(sql, function (err, data) { this.query(sql, function (err, data) {
if (err) return callback(err); if (err) return callback(err);
callback(null, data.length === 1); callback(null, data.length === 1);
}); });
}; };
/** /**
@ -179,17 +178,17 @@ BaseSQL.prototype.exists = function (model, id, callback) {
* @param {Function} callback The callback function * @param {Function} callback The callback function
*/ */
BaseSQL.prototype.find = function find(model, id, callback) { BaseSQL.prototype.find = function find(model, id, callback) {
var sql = 'SELECT * FROM ' + var sql = 'SELECT * FROM ' +
this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id + ' LIMIT 1'; this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id + ' LIMIT 1';
this.query(sql, function (err, data) { this.query(sql, function (err, data) {
if (data && data.length === 1) { if (data && data.length === 1) {
data[0].id = id; data[0].id = id;
} else { } else {
data = [null]; data = [null];
} }
callback(err, this.fromDatabase(model, data[0])); callback(err, this.fromDatabase(model, data[0]));
}.bind(this)); }.bind(this));
}; };
/** /**
@ -199,12 +198,12 @@ BaseSQL.prototype.find = function find(model, id, callback) {
* @param {Function} callback The callback function * @param {Function} callback The callback function
*/ */
BaseSQL.prototype.delete = BaseSQL.prototype.destroy = function destroy(model, id, callback) { BaseSQL.prototype.delete = BaseSQL.prototype.destroy = function destroy(model, id, callback) {
var sql = 'DELETE FROM ' + var sql = 'DELETE FROM ' +
this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id; this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id;
this.command(sql, function (err) { this.command(sql, function (err) {
callback(err); callback(err);
}); });
}; };
/** /**
@ -214,12 +213,12 @@ BaseSQL.prototype.delete = BaseSQL.prototype.destroy = function destroy(model, i
* @param {Function} callback The callback function * @param {Function} callback The callback function
*/ */
BaseSQL.prototype.deleteAll = BaseSQL.prototype.destroyAll = function destroyAll(model, callback) { BaseSQL.prototype.deleteAll = BaseSQL.prototype.destroyAll = function destroyAll(model, callback) {
this.command('DELETE FROM ' + this.tableEscaped(model), function (err) { this.command('DELETE FROM ' + this.tableEscaped(model), function (err) {
if (err) { if (err) {
return callback(err, []); return callback(err, []);
} }
callback(err); callback(err);
}.bind(this)); }.bind(this));
}; };
/** /**
@ -230,27 +229,27 @@ BaseSQL.prototype.deleteAll = BaseSQL.prototype.destroyAll = function destroyAll
* @param {Object} where The where clause * @param {Object} where The where clause
*/ */
BaseSQL.prototype.count = function count(model, callback, where) { BaseSQL.prototype.count = function count(model, callback, where) {
var self = this; var self = this;
var props = this._models[model].properties; var props = this._models[model].properties;
this.queryOne('SELECT count(*) as cnt FROM ' + this.queryOne('SELECT count(*) as cnt FROM ' +
this.tableEscaped(model) + ' ' + buildWhere(where), function (err, res) { this.tableEscaped(model) + ' ' + buildWhere(where), function (err, res) {
if (err) return callback(err); if (err) return callback(err);
callback(err, res && res.cnt); 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]));
}
}); });
return cs.length ? ' WHERE ' + cs.join(' AND ') : '';
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 ') : '';
}
}; };
/** /**
@ -261,15 +260,15 @@ BaseSQL.prototype.count = function count(model, callback, where) {
* @param {Function} cb The callback function * @param {Function} cb The callback function
*/ */
BaseSQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { BaseSQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) {
data.id = id; data.id = id;
this.save(model, data, cb); this.save(model, data, cb);
}; };
/** /**
* Disconnect from the connector * Disconnect from the connector
*/ */
BaseSQL.prototype.disconnect = function disconnect() { 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 * @param {Function} [cb] The callback function
*/ */
BaseSQL.prototype.automigrate = function (models, cb) { BaseSQL.prototype.automigrate = function (models, cb) {
var self = this; var self = this;
var wait = 0; var wait = 0;
if ((!cb) && ('function' === typeof models)) { if ((!cb) && ('function' === typeof models)) {
cb = models; cb = models;
models = undefined; models = undefined;
} }
// First argument is a model name // First argument is a model name
if ('string' === typeof models) { if ('string' === typeof models) {
models = [models]; models = [models];
} }
models = models || Object.keys(this._models); models = models || Object.keys(this._models);
models.forEach(function (model) { models.forEach(function (model) {
if (model in self._models) { if (model in self._models) {
wait++; wait++;
self.dropTable(model, function () { self.dropTable(model, function () {
// console.log('drop', model); // console.log('drop', model);
self.createTable(model, function (err) { self.createTable(model, function (err) {
// console.log('create', model); // console.log('create', model);
if (err) console.log(err); if (err) console.log(err);
done(); done();
}); });
}); });
}
});
if (wait === 0) cb();
function done() {
if (--wait === 0 && cb) {
cb();
}
} }
});
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 * @param {Function} [cb] The callback function
*/ */
BaseSQL.prototype.dropTable = function (model, cb) { 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) { BaseSQL.prototype.createTable = function (model, cb) {
this.command('CREATE TABLE ' + this.tableEscaped(model) + this.command('CREATE TABLE ' + this.tableEscaped(model) +
' (\n ' + this.propertiesSQL(model) + '\n)', cb); ' (\n ' + this.propertiesSQL(model) + '\n)', cb);
}; };

View File

@ -1,61 +1,61 @@
module.exports = function (Types) { module.exports = function (Types) {
var List = require('./list.js'); var List = require('./list.js');
var GeoPoint = require('./geo').GeoPoint; var GeoPoint = require('./geo').GeoPoint;
/** /**
* Schema types * Schema types
*/ */
Types.Text = function Text(value) { Types.Text = function Text(value) {
if (!(this instanceof Text)) { if (!(this instanceof Text)) {
return value; return value;
} }
this.value = value; this.value = value;
}; // Text type }; // Text type
Types.Text.prototype.toObject = Types.Text.prototype.toJSON = function () { Types.Text.prototype.toObject = Types.Text.prototype.toJSON = function () {
return this.value; return this.value;
}; };
Types.JSON = function JSON(value) { Types.JSON = function JSON(value) {
if (!(this instanceof JSON)) { if (!(this instanceof JSON)) {
return value; return value;
} }
this.value = value; this.value = value;
}; // JSON Object }; // JSON Object
Types.JSON.prototype.toObject = Types.JSON.prototype.toJSON = function () { Types.JSON.prototype.toObject = Types.JSON.prototype.toJSON = function () {
return this.value; return this.value;
}; };
Types.Any = function Any(value) { Types.Any = function Any(value) {
if (!(this instanceof Any)) { if (!(this instanceof Any)) {
return value; return value;
} }
this.value = value; this.value = value;
}; // Any Type }; // Any Type
Types.Any.prototype.toObject = Types.Any.prototype.toJSON = function () { Types.Any.prototype.toObject = Types.Any.prototype.toJSON = function () {
return this.value; return this.value;
}; };
Types.schemaTypes = {}; Types.schemaTypes = {};
Types.registerType = function (type, names) { Types.registerType = function (type, names) {
names = names || []; names = names || [];
names = names.concat([type.name]); names = names.concat([type.name]);
for (var n = 0; n < names.length; n++) { for (var n = 0; n < names.length; n++) {
this.schemaTypes[names[n].toLowerCase()] = type; this.schemaTypes[names[n].toLowerCase()] = type;
} }
}; };
Types.registerType(Types.Text); Types.registerType(Types.Text);
Types.registerType(Types.JSON); Types.registerType(Types.JSON);
Types.registerType(Types.Any); Types.registerType(Types.Any);
Types.registerType(String); Types.registerType(String);
Types.registerType(Number); Types.registerType(Number);
Types.registerType(Boolean); Types.registerType(Boolean);
Types.registerType(Date); Types.registerType(Date);
Types.registerType(Buffer, ['Binary']); Types.registerType(Buffer, ['Binary']);
Types.registerType(Array); Types.registerType(Array);
Types.registerType(GeoPoint); Types.registerType(GeoPoint);
Types.registerType(Object); Types.registerType(Object);
} };

View File

@ -8,54 +8,56 @@ exports.mergeSettings = mergeSettings;
var traverse = require('traverse'); var traverse = require('traverse');
function safeRequire(module) { function safeRequire(module) {
try { try {
return require(module); return require(module);
} catch (e) { } catch (e) {
console.log('Run "npm install loopback-datasource-juggler ' + module + '" command to use loopback-datasource-juggler using ' + module + ' database engine'); console.log('Run "npm install loopback-datasource-juggler ' + module
process.exit(1); + '" command to use loopback-datasource-juggler using ' + module
} + ' database engine');
process.exit(1);
}
} }
function fieldsToArray(fields, properties) { function fieldsToArray(fields, properties) {
if(!fields) return; if (!fields) return;
// include all properties by default // include all properties by default
var result = properties; var result = properties;
if(typeof fields === 'string') { if (typeof fields === 'string') {
return [fields]; 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) { return result;
// 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;
} }
function selectFields(fields) { function selectFields(fields) {
@ -79,24 +81,25 @@ function selectFields(fields) {
* @returns {exports.map|*} * @returns {exports.map|*}
*/ */
function removeUndefined(query) { function removeUndefined(query) {
if (typeof query !== 'object' || query === null) { if (typeof query !== 'object' || query === null) {
return query; 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)) { if (!Array.isArray(x) && (typeof x === 'object' && x !== null
// This object is not a plain object && x.constructor !== Object)) {
this.update(x, true); // Stop navigating into this object // This object is not a plain object
return x; this.update(x, true); // Stop navigating into this object
} return x;
}
return x; return x;
}); });
} }
var url = require('url'); var url = require('url');
@ -108,25 +111,25 @@ var qs = require('qs');
* @returns {Object} The settings object * @returns {Object} The settings object
*/ */
function parseSettings(urlStr) { function parseSettings(urlStr) {
if(!urlStr) { if (!urlStr) {
return {}; 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]; // <username>:<password>
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 = {}; return 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]; // <username>:<password>
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;
} }
/** /**

View File

@ -203,121 +203,121 @@ Validatable.validatesUniquenessOf = getConfigurator('uniqueness', {async: true})
* Presence validator * Presence validator
*/ */
function validatePresence(attr, conf, err) { function validatePresence(attr, conf, err) {
if (blank(this[attr])) { if (blank(this[attr])) {
err(); err();
} }
} }
/** /**
* Length validator * Length validator
*/ */
function validateLength(attr, conf, err) { 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; var len = this[attr].length;
if (conf.min && len < conf.min) { if (conf.min && len < conf.min) {
err('min'); err('min');
} }
if (conf.max && len > conf.max) { if (conf.max && len > conf.max) {
err('max'); err('max');
} }
if (conf.is && len !== conf.is) { if (conf.is && len !== conf.is) {
err('is'); err('is');
} }
} }
/** /**
* Numericality validator * Numericality validator
*/ */
function validateNumericality(attr, conf, err) { 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') { if (typeof this[attr] !== 'number') {
return err('number'); return err('number');
} }
if (conf.int && this[attr] !== Math.round(this[attr])) { if (conf.int && this[attr] !== Math.round(this[attr])) {
return err('int'); return err('int');
} }
} }
/** /**
* Inclusion validator * Inclusion validator
*/ */
function validateInclusion(attr, conf, err) { 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])) { if (!~conf.in.indexOf(this[attr])) {
err() err()
} }
} }
/** /**
* Exclusion validator * Exclusion validator
*/ */
function validateExclusion(attr, conf, err) { 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])) { if (~conf.in.indexOf(this[attr])) {
err() err()
} }
} }
/** /**
* Format validator * Format validator
*/ */
function validateFormat(attr, conf, err) { 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 (typeof this[attr] === 'string') {
if (!this[attr].match(conf['with'])) { if (!this[attr].match(conf['with'])) {
err(); err();
}
} else {
err();
} }
} else {
err();
}
} }
/** /**
* Custom validator * Custom validator
*/ */
function validateCustom(attr, conf, err, done) { function validateCustom(attr, conf, err, done) {
conf.customValidator.call(this, err, done); conf.customValidator.call(this, err, done);
} }
/** /**
* Uniqueness validator * Uniqueness validator
*/ */
function validateUniqueness(attr, conf, err, done) { function validateUniqueness(attr, conf, err, done) {
var cond = {where: {}}; var cond = {where: {}};
cond.where[attr] = this[attr]; cond.where[attr] = this[attr];
this.constructor.find(cond, function (error, found) { this.constructor.find(cond, function (error, found) {
if (error) { if (error) {
return err(); return err();
} }
if (found.length > 1) { if (found.length > 1) {
err(); err();
} else if (found.length === 1 && (!this.id || !found[0].id || found[0].id.toString() != this.id.toString())) { } else if (found.length === 1 && (!this.id || !found[0].id || found[0].id.toString() != this.id.toString())) {
err(); err();
} }
done(); done();
}.bind(this)); }.bind(this));
} }
var validators = { var validators = {
presence: validatePresence, presence: validatePresence,
length: validateLength, length: validateLength,
numericality: validateNumericality, numericality: validateNumericality,
inclusion: validateInclusion, inclusion: validateInclusion,
exclusion: validateExclusion, exclusion: validateExclusion,
format: validateFormat, format: validateFormat,
custom: validateCustom, custom: validateCustom,
uniqueness: validateUniqueness uniqueness: validateUniqueness
}; };
function getConfigurator(name, opts) { function getConfigurator(name, opts) {
return function () { return function () {
configure(this, name, arguments, opts); configure(this, name, arguments, opts);
}; };
} }
/** /**
@ -341,193 +341,193 @@ function getConfigurator(name, opts) {
* ``` * ```
*/ */
Validatable.prototype.isValid = function (callback, data) { 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 // exit with success when no errors
if (!this.constructor._validations) { if (!this.constructor._validations) {
cleanErrors(this); cleanErrors(this);
if (callback) { if (callback) {
this.trigger('validate', function (validationsDone) { this.trigger('validate', function (validationsDone) {
validationsDone.call(inst, function() { validationsDone.call(inst, function () {
callback(valid); callback(valid);
}); });
}); });
}
return 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) { if (!async) {
var inst = this, validationsDone.call(inst, function () {
asyncFail = false; if (valid) cleanErrors(inst);
if (callback) {
this.constructor._validations.forEach(function (v) { callback(valid);
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);
}
});
} }
});
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) { function cleanErrors(inst) {
Object.defineProperty(inst, 'errors', { Object.defineProperty(inst, 'errors', {
enumerable: false, enumerable: false,
configurable: true, configurable: true,
value: false value: false
}); });
} }
function validationFailed(inst, v, cb) { function validationFailed(inst, v, cb) {
var attr = v[0]; var attr = v[0];
var conf = v[1]; var conf = v[1];
var opts = v[2] || {}; 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) // here we should check skip validation conditions (if, unless)
// that can be specified in conf // that can be specified in conf
if (skipValidation(inst, conf, 'if')) return false; if (skipValidation(inst, conf, 'if')) return false;
if (skipValidation(inst, conf, 'unless')) return false; if (skipValidation(inst, conf, 'unless')) return false;
var fail = false; var fail = false;
var validator = validators[conf.validation]; var validator = validators[conf.validation];
var validatorArguments = []; var validatorArguments = [];
validatorArguments.push(attr); validatorArguments.push(attr);
validatorArguments.push(conf); validatorArguments.push(conf);
validatorArguments.push(function onerror(kind) { validatorArguments.push(function onerror(kind) {
var message, code = conf.validation; var message, code = conf.validation;
if (conf.message) { if (conf.message) {
message = 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);
});
} }
validator.apply(inst, validatorArguments); if (!message && defaultMessages[conf.validation]) {
return fail; 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) { function skipValidation(inst, conf, kind) {
var doValidate = true; var doValidate = true;
if (typeof conf[kind] === 'function') { if (typeof conf[kind] === 'function') {
doValidate = conf[kind].call(inst); doValidate = conf[kind].call(inst);
if (kind === 'unless') doValidate = !doValidate; if (kind === 'unless') doValidate = !doValidate;
} else if (typeof conf[kind] === 'string') { } else if (typeof conf[kind] === 'string') {
if (typeof inst[conf[kind]] === 'function') { if (typeof inst[conf[kind]] === 'function') {
doValidate = inst[conf[kind]].call(inst); doValidate = inst[conf[kind]].call(inst);
if (kind === 'unless') doValidate = !doValidate; if (kind === 'unless') doValidate = !doValidate;
} else if (inst.__data.hasOwnProperty(conf[kind])) { } else if (inst.__data.hasOwnProperty(conf[kind])) {
doValidate = inst[conf[kind]]; doValidate = inst[conf[kind]];
if (kind === 'unless') doValidate = !doValidate; if (kind === 'unless') doValidate = !doValidate;
} else { } else {
doValidate = kind === 'if'; doValidate = kind === 'if';
}
} }
return !doValidate; }
return !doValidate;
} }
var defaultMessages = { var defaultMessages = {
presence: 'can\'t be blank', presence: 'can\'t be blank',
length: { length: {
min: 'too short', min: 'too short',
max: 'too long', max: 'too long',
is: 'length is wrong' is: 'length is wrong'
}, },
common: { common: {
blank: 'is blank', blank: 'is blank',
'null': 'is null' 'null': 'is null'
}, },
numericality: { numericality: {
'int': 'is not an integer', 'int': 'is not an integer',
'number': 'is not a number' 'number': 'is not a number'
}, },
inclusion: 'is not included in the list', inclusion: 'is not included in the list',
exclusion: 'is reserved', exclusion: 'is reserved',
uniqueness: 'is not unique' uniqueness: 'is not unique'
}; };
function nullCheck(attr, conf, err) { function nullCheck(attr, conf, err) {
var isNull = this[attr] === null || !(attr in this); var isNull = this[attr] === null || !(attr in this);
if (isNull) { if (isNull) {
if (!conf.allowNull) { if (!conf.allowNull) {
err('null'); err('null');
}
return true;
} else {
if (blank(this[attr])) {
if (!conf.allowBlank) {
err('blank');
}
return true;
}
} }
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 * @returns {Boolean} whether `v` blank or not
*/ */
function blank(v) { function blank(v) {
if (typeof v === 'undefined') return true; if (typeof v === 'undefined') return true;
if (v instanceof Array && v.length === 0) return true; if (v instanceof Array && v.length === 0) return true;
if (v === null) return true; if (v === null) return true;
if (typeof v == 'string' && v === '') return true; if (typeof v == 'string' && v === '') return true;
return false; return false;
} }
function configure(cls, validation, args, opts) { function configure(cls, validation, args, opts) {
if (!cls._validations) { if (!cls._validations) {
Object.defineProperty(cls, '_validations', { Object.defineProperty(cls, '_validations', {
writable: true, writable: true,
configurable: true, configurable: true,
enumerable: false, enumerable: false,
value: [] 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]);
}); });
}
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() { function Errors() {
Object.defineProperty(this, 'codes', { Object.defineProperty(this, 'codes', {
enumerable: false, enumerable: false,
configurable: true, configurable: true,
value: {} value: {}
}); });
} }
Errors.prototype.add = function (field, message, code) { Errors.prototype.add = function (field, message, code) {
code = code || 'invalid'; code = code || 'invalid';
if (!this[field]) { if (!this[field]) {
this[field] = []; this[field] = [];
this.codes[field] = []; this.codes[field] = [];
} }
this[field].push(message); this[field].push(message);
this.codes[field].push(code); this.codes[field].push(code);
}; };
function ErrorCodes(messages) { function ErrorCodes(messages) {
var c = this; var c = this;
Object.keys(messages).forEach(function(field) { Object.keys(messages).forEach(function (field) {
c[field] = messages[field].codes; c[field] = messages[field].codes;
}); });
} }
function ValidationError(obj) { function ValidationError(obj) {
if (!(this instanceof ValidationError)) return new ValidationError(obj); if (!(this instanceof ValidationError)) return new ValidationError(obj);
this.name = 'ValidationError'; this.name = 'ValidationError';
this.message = 'The Model instance is not valid. ' + this.message = 'The Model instance is not valid. ' +
'See `details` property of the error object for more info.'; 'See `details` property of the error object for more info.';
this.statusCode = 422; this.statusCode = 422;
this.details = { this.details = {
context: obj && obj.constructor && obj.constructor.modelName, context: obj && obj.constructor && obj.constructor.modelName,
codes: obj.errors && obj.errors.codes, codes: obj.errors && obj.errors.codes,
messages: obj.errors messages: obj.errors
}; };
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
} }
util.inherits(ValidationError, Error); util.inherits(ValidationError, Error);

View File

@ -2,347 +2,345 @@
var should = require('./init.js'); var should = require('./init.js');
var db, User; var db, User;
describe('basic-querying', function() { describe('basic-querying', function () {
before(function(done) { before(function (done) {
db = getSchema(); 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);
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) { describe('findById', function () {
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();
});
});
});
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) {
it('should query collection', function(done) { should.not.exist(err);
User.find(function(err, users) { should.exist(u.id);
should.exists(users); User.findById(u.id, function (err, u) {
should.not.exists(err); should.exist(u);
users.should.have.lengthOf(6); should.not.exist(err);
done(); u.should.be.an.instanceOf(User);
}); done();
}); });
});
});
it('should query limited collection', function(done) { });
User.find({limit: 3}, function(err, users) {
should.exists(users); describe('find', function () {
should.not.exists(err);
users.should.have.lengthOf(3); before(seed);
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();
});
});
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();
});
});
it('should query offset collection with limit', function(done) { it('should query collection desc sorted by numeric field', function (done) {
User.find({skip: 1, limit: 4}, function(err, users) { User.find({order: 'order DESC'}, function (err, users) {
should.exists(users); should.exists(users);
should.not.exists(err); should.not.exists(err);
users.should.have.lengthOf(4); users.forEach(function (u, i) {
done(); u.order.should.eql(users.length - i);
});
}); });
done();
});
});
it('should query filtered collection', function(done) { it('should query collection sorted by string field', function (done) {
User.find({where: {role: 'lead'}}, function(err, users) { User.find({order: 'name'}, function (err, users) {
should.exists(users); should.exists(users);
should.not.exists(err); should.not.exists(err);
users.should.have.lengthOf(2); 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(); 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);
});
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();
}
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']); users.forEach(function (user) {
sample({name: false}).expect(['id', 'email', 'role', 'order']); var obj = user.toObject();
sample({name: false, id: true}).expect(['id']);
sample({id: true}).expect(['id']);
sample('id').expect(['id']);
sample(['id']).expect(['id']);
sample(['email']).expect(['email']);
});
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) { before(seed);
User.count(function(err, n) {
should.not.exist(err);
should.exist(n);
n.should.equal(6);
done();
});
});
it('should query filtered count', function(done) { it('should query total count', function (done) {
User.count({role: 'lead'}, function(err, n) { User.count(function (err, n) {
should.not.exist(err); should.not.exist(err);
should.exist(n); should.exist(n);
n.should.equal(2); n.should.equal(6);
done(); 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) { before(seed);
User.all({order: 'id'}, function(err, users) {
User.findOne(function(e, u) { it('should find first record (default sort by id)', function (done) {
should.not.exist(e); User.all({order: 'id'}, function (err, users) {
should.exist(u); User.findOne(function (e, u) {
u.id.toString().should.equal(users[0].id.toString()); should.not.exist(e);
done(); 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() { it('should find first record', function (done) {
User.findOne({order: 'order'}, function (e, u) {
before(seed); should.not.exist(e);
should.exist(u);
it('should check whether record exist', function(done) { u.order.should.equal(1);
User.findOne(function(e, u) { u.name.should.equal('Paul McCartney');
User.exists(u.id, function(err, exists) { done();
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() { it('should find last record', function (done) {
User.findOne({order: 'order DESC'}, function (e, u) {
before(seed); should.not.exist(e);
should.exist(u);
it('should only delete instances that satisfy the where condition', function(done) { u.order.should.equal(6);
User.destroyAll({name: 'John Lennon'}, function() { u.name.should.equal('Ringo Starr');
User.find({where: {name: 'John Lennon'}}, function(err, data) { done();
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 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) { function seed(done) {
var count = 0; var count = 0;
var beatles = [ var beatles = [
{ {
name: 'John Lennon', name: 'John Lennon',
email: 'john@b3atl3s.co.uk', email: 'john@b3atl3s.co.uk',
role: 'lead', role: 'lead',
order: 2 order: 2
}, { },
name: 'Paul McCartney', {
email: 'paul@b3atl3s.co.uk', name: 'Paul McCartney',
role: 'lead', email: 'paul@b3atl3s.co.uk',
order: 1 role: 'lead',
}, order: 1
{name: 'George Harrison', order: 5}, },
{name: 'Ringo Starr', order: 6}, {name: 'George Harrison', order: 5},
{name: 'Pete Best', order: 4}, {name: 'Ringo Starr', order: 6},
{name: 'Stuart Sutcliffe', order: 3} {name: 'Pete Best', order: 4},
]; {name: 'Stuart Sutcliffe', order: 3}
User.destroyAll(function() { ];
beatles.forEach(function(beatle) { User.destroyAll(function () {
User.create(beatle, ok); beatles.forEach(function (beatle) {
}); User.create(beatle, ok);
}); });
});
function ok() { function ok() {
if (++count === beatles.length) { if (++count === beatles.length) {
done(); done();
}
} }
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -3,64 +3,64 @@ var should = require('./init.js');
var db, Model; var db, Model;
describe('datatypes', function() { describe('datatypes', function () {
before(function(done){ before(function (done) {
db = getSchema(); db = getSchema();
Model = db.define('Model', { Model = db.define('Model', {
str: String, str: String,
date: Date, date: Date,
num: Number, num: Number,
bool: Boolean, bool: Boolean,
list: {type: []}, list: {type: []},
}); });
db.automigrate(function() { db.automigrate(function () {
Model.destroyAll(done); 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) { function testFind(next) {
var d = new Date, id; 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({ function testAll() {
str: 'hello', date: d, num: '3', bool: 1, list: ['test'] Model.findOne(function (err, m) {
}, function(err, m) { should.not.exist(err);
should.not.exist(err); should.exist(m);
should.exist(m && m.id); m.str.should.be.a('string');
m.str.should.be.a('string'); m.num.should.be.a('number');
m.num.should.be.a('number'); m.bool.should.be.a('boolean');
m.bool.should.be.a('boolean'); m.date.should.be.an.instanceOf(Date);
id = m.id; m.date.toString().should.equal(d.toString(), 'Time must match');
testFind(testAll); 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();
});
}
});
}); });

View File

@ -3,36 +3,36 @@ var should = require('./init.js');
var db = getSchema(); var db = getSchema();
describe('defaults', function() { describe('defaults', function () {
var Server; var Server;
before(function() { before(function () {
Server = db.define('Server', { Server = db.define('Server', {
host: String, host: String,
port: {type: Number, default: 80} port: {type: Number, default: 80}
});
}); });
});
it('should apply defaults on new', function() { it('should apply defaults on new', function () {
var s = new Server; var s = new Server;
s.port.should.equal(80); s.port.should.equal(80);
}); });
it('should apply defaults on create', function(done) { it('should apply defaults on create', function (done) {
Server.create(function(err, s) { Server.create(function (err, s) {
s.port.should.equal(80); s.port.should.equal(80);
done(); done();
});
}); });
});
it('should apply defaults on read', function(done) { it('should apply defaults on read', function (done) {
db.defineProperty('Server', 'host', { db.defineProperty('Server', 'host', {
type: String, type: String,
default: 'localhost' default: 'localhost'
});
Server.all(function (err, servers) {
(new String('localhost')).should.equal(servers[0].host);
done();
});
}); });
Server.all(function (err, servers) {
(new String('localhost')).should.equal(servers[0].host);
done();
});
});
}); });

View File

@ -2,391 +2,423 @@
var should = require('./init.js'); var should = require('./init.js');
var j = require('../'), var j = require('../'),
Schema = j.Schema, Schema = j.Schema,
AbstractClass = j.AbstractClass, AbstractClass = j.AbstractClass,
Hookable = j.Hookable, Hookable = j.Hookable,
db, User; db, User;
describe('hooks', function() { describe('hooks', function () {
before(function(done) { before(function (done) {
db = getSchema(); db = getSchema();
User = db.define('User', { User = db.define('User', {
email: {type: String, index: true}, email: {type: String, index: true},
name: String, name: String,
password: String, password: String,
state: String state: String
});
db.automigrate(done);
}); });
describe('initialize', function() { db.automigrate(done);
});
afterEach(function() { describe('initialize', 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();
});
});
afterEach(function () {
User.afterInitialize = null;
}); });
describe('create', function() { it('should be triggered on new', function (done) {
User.afterInitialize = function () {
afterEach(removeHooks('Create')); done();
};
it('should be triggered on create', function(done) { new User;
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();
});
});
}); });
describe('save', function() { it('should be triggered on create', function (done) {
afterEach(removeHooks('Save')); var user;
User.afterInitialize = function () {
it('should be triggered on create', function(done) { if (this.name === 'Nickolay') {
addHooks('Save', done); this.name += ' Rozental';
User.create(); }
}); };
User.create({name: 'Nickolay'}, function (err, u) {
it('should be triggered on new+save', function(done) { u.id.should.be.ok;
addHooks('Save', done); u.name.should.equal('Nickolay Rozental');
(new User).save(); done();
}); });
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() { describe('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() { afterEach(removeHooks('Create'));
User.beforeUpdate = function(next) {
should.fail('This should not be called');
next();
};
(new User).save();
});
it('should be triggered on updateAttributes', function(done) { it('should be triggered on create', function (done) {
User.create(function (err, user) { addHooks('Create', done);
addHooks('Update', done); User.create();
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() { it('should not be triggered on new', function () {
User.beforeCreate = function (next) {
afterEach(removeHooks('Destroy')); should.fail('This should not be called');
next();
it('should be triggered on destroy', function(done) { };
var hook = 'not called'; var u = new User;
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() { it('should be triggered on new+save', function (done) {
var life = [], user; addHooks('Create', done);
before(function(done) { (new User).save();
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('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) { function addHooks(name, done) {
var called = false, random = String(Math.floor(Math.random() * 1000)); var called = false, random = String(Math.floor(Math.random() * 1000));
User['before' + name] = function(next, data) { User['before' + name] = function (next, data) {
called = true; called = true;
data.email = random; data.email = random;
next(); next();
}; };
User['after' + name] = function(next) { User['after' + name] = function (next) {
(new Boolean(called)).should.equal(true); (new Boolean(called)).should.equal(true);
this.email.should.equal(random); this.email.should.equal(random);
done(); done();
}; };
} }
function removeHooks(name) { function removeHooks(name) {
return function() { return function () {
User['after' + name] = null; User['after' + name] = null;
User['before' + name] = null; User['before' + name] = null;
}; };
} }

View File

@ -4,208 +4,209 @@ var should = require('./init.js');
var db, User, Post, Passport, City, Street, Building; var db, User, Post, Passport, City, Street, Building;
var nbSchemaRequests = 0; var nbSchemaRequests = 0;
describe('include', function() { describe('include', function () {
before(setup); before(setup);
it('should fetch belongsTo relation', function(done) { it('should fetch belongsTo relation', function (done) {
Passport.all({include: 'owner'}, function (err, passports) { Passport.all({include: 'owner'}, function (err, passports) {
passports.length.should.be.ok; passports.length.should.be.ok;
passports.forEach(function(p) { passports.forEach(function (p) {
p.__cachedRelations.should.have.property('owner'); p.__cachedRelations.should.have.property('owner');
var owner = p.__cachedRelations.owner; var owner = p.__cachedRelations.owner;
if (!p.ownerId) { if (!p.ownerId) {
should.not.exist(owner); should.not.exist(owner);
} else { } else {
should.exist(owner); should.exist(owner);
owner.id.should.equal(p.ownerId); owner.id.should.equal(p.ownerId);
} }
}); });
done(); done();
});
}); });
});
it('should fetch hasMany relation', function(done) { it('should fetch hasMany relation', function (done) {
User.all({include: 'posts'}, function (err, users) { User.all({include: 'posts'}, function (err, users) {
should.not.exist(err); should.not.exist(err);
should.exist(users); should.exist(users);
users.length.should.be.ok; users.length.should.be.ok;
users.forEach(function(u) { users.forEach(function (u) {
u.__cachedRelations.should.have.property('posts'); u.__cachedRelations.should.have.property('posts');
u.__cachedRelations.posts.forEach(function(p) { u.__cachedRelations.posts.forEach(function (p) {
p.userId.should.equal(u.id); p.userId.should.equal(u.id);
});
});
done();
}); });
});
done();
}); });
});
it('should fetch Passport - Owner - Posts', function(done) { it('should fetch Passport - Owner - Posts', function (done) {
Passport.all({include: {owner: 'posts'}}, function(err, passports) { Passport.all({include: {owner: 'posts'}}, function (err, passports) {
should.not.exist(err); should.not.exist(err);
should.exist(passports); should.exist(passports);
passports.length.should.be.ok; passports.length.should.be.ok;
passports.forEach(function(p) { passports.forEach(function (p) {
p.__cachedRelations.should.have.property('owner'); p.__cachedRelations.should.have.property('owner');
var user = p.__cachedRelations.owner; var user = p.__cachedRelations.owner;
if (!p.ownerId) { if (!p.ownerId) {
should.not.exist(user); should.not.exist(user);
} else { } else {
should.exist(user); should.exist(user);
user.id.should.equal(p.ownerId); user.id.should.equal(p.ownerId);
user.__cachedRelations.should.have.property('posts'); user.__cachedRelations.should.have.property('posts');
user.__cachedRelations.posts.forEach(function(pp) { user.__cachedRelations.posts.forEach(function (pp) {
pp.userId.should.equal(user.id); pp.userId.should.equal(user.id);
}); });
} }
}); });
done(); done();
});
}); });
});
it('should fetch Passports - User - Posts - User', function(done) { it('should fetch Passports - User - Posts - User', function (done) {
Passport.all({ Passport.all({
include: {owner: {posts: 'author'}} include: {owner: {posts: 'author'}}
}, function(err, passports) { }, function (err, passports) {
should.not.exist(err); should.not.exist(err);
should.exist(passports); should.exist(passports);
passports.length.should.be.ok; passports.length.should.be.ok;
passports.forEach(function(p) { passports.forEach(function (p) {
p.__cachedRelations.should.have.property('owner'); p.__cachedRelations.should.have.property('owner');
var user = p.__cachedRelations.owner; var user = p.__cachedRelations.owner;
if (!p.ownerId) { if (!p.ownerId) {
should.not.exist(user); should.not.exist(user);
} else { } else {
should.exist(user); should.exist(user);
user.id.should.equal(p.ownerId); user.id.should.equal(p.ownerId);
user.__cachedRelations.should.have.property('posts'); user.__cachedRelations.should.have.property('posts');
user.__cachedRelations.posts.forEach(function(pp) { user.__cachedRelations.posts.forEach(function (pp) {
pp.userId.should.equal(user.id); pp.userId.should.equal(user.id);
pp.__cachedRelations.should.have.property('author'); pp.__cachedRelations.should.have.property('author');
var author = pp.__cachedRelations.author; var author = pp.__cachedRelations.author;
author.id.should.equal(user.id); author.id.should.equal(user.id);
}); });
} }
}); });
done(); done();
});
}); });
});
it('should fetch User - Posts AND Passports', function(done) { it('should fetch User - Posts AND Passports', function (done) {
User.all({include: ['posts', 'passports']}, function(err, users) { User.all({include: ['posts', 'passports']}, function (err, users) {
should.not.exist(err); should.not.exist(err);
should.exist(users); should.exist(users);
users.length.should.be.ok; users.length.should.be.ok;
users.forEach(function(user) { users.forEach(function (user) {
user.__cachedRelations.should.have.property('posts'); user.__cachedRelations.should.have.property('posts');
user.__cachedRelations.should.have.property('passports'); user.__cachedRelations.should.have.property('passports');
user.__cachedRelations.posts.forEach(function(p) { user.__cachedRelations.posts.forEach(function (p) {
p.userId.should.equal(user.id); p.userId.should.equal(user.id);
});
user.__cachedRelations.passports.forEach(function(pp) {
pp.ownerId.should.equal(user.id);
});
});
done();
}); });
user.__cachedRelations.passports.forEach(function (pp) {
pp.ownerId.should.equal(user.id);
});
});
done();
}); });
});
}); });
function setup(done) { function setup(done) {
db = getSchema(); db = getSchema();
City = db.define('City'); City = db.define('City');
Street = db.define('Street'); Street = db.define('Street');
Building = db.define('Building'); Building = db.define('Building');
User = db.define('User', { User = db.define('User', {
name: String, name: String,
age: Number age: Number
}); });
Passport = db.define('Passport', { Passport = db.define('Passport', {
number: String number: String
}); });
Post = db.define('Post', { Post = db.define('Post', {
title: String title: String
}); });
Passport.belongsTo('owner', {model: User}); Passport.belongsTo('owner', {model: User});
User.hasMany('passports', {foreignKey: 'ownerId'}); User.hasMany('passports', {foreignKey: 'ownerId'});
User.hasMany('posts', {foreignKey: 'userId'}); User.hasMany('posts', {foreignKey: 'userId'});
Post.belongsTo('author', {model: User, foreignKey: 'userId'}); Post.belongsTo('author', {model: User, foreignKey: 'userId'});
db.automigrate(function() { db.automigrate(function () {
var createdUsers = []; var createdUsers = [];
var createdPassports = []; var createdPassports = [];
var createdPosts = []; var createdPosts = [];
createUsers(); createUsers();
function createUsers() { function createUsers() {
clearAndCreate( clearAndCreate(
User, User,
[ [
{name: 'User A', age: 21}, {name: 'User A', age: 21},
{name: 'User B', age: 22}, {name: 'User B', age: 22},
{name: 'User C', age: 23}, {name: 'User C', age: 23},
{name: 'User D', age: 24}, {name: 'User D', age: 24},
{name: 'User E', age: 25} {name: 'User E', age: 25}
], ],
function(items) { function (items) {
createdUsers = items; createdUsers = items;
createPassports(); createPassports();
}
);
} }
);
}
function createPassports() { function createPassports() {
clearAndCreate( clearAndCreate(
Passport, Passport,
[ [
{number: '1', ownerId: createdUsers[0].id}, {number: '1', ownerId: createdUsers[0].id},
{number: '2', ownerId: createdUsers[1].id}, {number: '2', ownerId: createdUsers[1].id},
{number: '3'} {number: '3'}
], ],
function(items) { function (items) {
createdPassports = items; createdPassports = items;
createPosts(); createPosts();
}
);
} }
);
}
function createPosts() { function createPosts() {
clearAndCreate( clearAndCreate(
Post, Post,
[ [
{title: 'Post A', userId: createdUsers[0].id}, {title: 'Post A', userId: createdUsers[0].id},
{title: 'Post B', userId: createdUsers[0].id}, {title: 'Post B', userId: createdUsers[0].id},
{title: 'Post C', userId: createdUsers[0].id}, {title: 'Post C', userId: createdUsers[0].id},
{title: 'Post D', userId: createdUsers[1].id}, {title: 'Post D', userId: createdUsers[1].id},
{title: 'Post E'} {title: 'Post E'}
], ],
function(items) { function (items) {
createdPosts = items; createdPosts = items;
done(); done();
}
);
} }
);
}
}); });
} }
function clearAndCreate(model, data, callback) { function clearAndCreate(model, data, callback) {
var createdItems = []; var createdItems = [];
model.destroyAll(function () { model.destroyAll(function () {
nextItem(null, null); nextItem(null, null);
}); });
var itemIndex = 0; var itemIndex = 0;
function nextItem(err, lastItem) {
if (lastItem !== null) { function nextItem(err, lastItem) {
createdItems.push(lastItem); if (lastItem !== null) {
} createdItems.push(lastItem);
if (itemIndex >= data.length) {
callback(createdItems);
return;
}
model.create(data[itemIndex], nextItem);
itemIndex++;
} }
if (itemIndex >= data.length) {
callback(createdItems);
return;
}
model.create(data[itemIndex], nextItem);
itemIndex++;
}
} }

View File

@ -1,21 +1,21 @@
module.exports = require('should'); module.exports = require('should');
/* /*
if (!process.env.TRAVIS) { if (!process.env.TRAVIS) {
if (typeof __cov === 'undefined') { if (typeof __cov === 'undefined') {
process.on('exit', function () { process.on('exit', function () {
require('semicov').report(); require('semicov').report();
}); });
} }
require('semicov').init('lib'); require('semicov').init('lib');
} }
*/ */
var Schema = require('../').Schema; var Schema = require('../').Schema;
if (!('getSchema' in global)) { if (!('getSchema' in global)) {
global.getSchema = function() { global.getSchema = function () {
return new Schema('memory'); return new Schema('memory');
}; };
} }

View File

@ -3,99 +3,99 @@ var ModelBuilder = require('../lib/model-builder').ModelBuilder;
var introspectType = require('../lib/introspection')(ModelBuilder); var introspectType = require('../lib/introspection')(ModelBuilder);
var traverse = require('traverse'); 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() { it('should handle simple types', function () {
assert.equal(introspectType('123'), 'string'); assert.equal(introspectType('123'), 'string');
assert.equal(introspectType(true), 'boolean'); assert.equal(introspectType(true), 'boolean');
assert.equal(introspectType(false), 'boolean'); assert.equal(introspectType(false), 'boolean');
assert.equal(introspectType(12), 'number'); assert.equal(introspectType(12), 'number');
assert.equal(introspectType(new Date()), 'date'); assert.equal(introspectType(new Date()), 'date');
}); });
it('should handle array types', function() { it('should handle array types', function () {
var type = introspectType(['123']); var type = introspectType(['123']);
assert.deepEqual(type, ['string'], 'type should be ["string"]'); assert.deepEqual(type, ['string'], 'type should be ["string"]');
type = introspectType([1]); type = introspectType([1]);
assert.deepEqual(type, ['number'], 'type should be ["number"]'); assert.deepEqual(type, ['number'], 'type should be ["number"]');
// Stop at first known type // Stop at first known type
type = introspectType([1, '123']); type = introspectType([1, '123']);
assert.deepEqual(type, ['number'], 'type should be ["number"]'); assert.deepEqual(type, ['number'], 'type should be ["number"]');
type = introspectType([null, '123']); type = introspectType([null, '123']);
assert.deepEqual(type, ['string'], 'type should be ["string"]'); assert.deepEqual(type, ['string'], 'type should be ["string"]');
type = introspectType([]); type = introspectType([]);
assert.equal(type, 'array'); assert.equal(type, 'array');
}); });
it('should return Any for null or undefined', function() { it('should return Any for null or undefined', function () {
assert.equal(introspectType(null), ModelBuilder.Any); assert.equal(introspectType(null), ModelBuilder.Any);
assert.equal(introspectType(undefined), ModelBuilder.Any); assert.equal(introspectType(undefined), ModelBuilder.Any);
}); });
it('should return a schema for object', function() { it('should return a schema for object', function () {
var json = {a: 'str', b: 0, c: true}; var json = {a: 'str', b: 0, c: true};
var type = introspectType(json); var type = introspectType(json);
assert.equal(type.a, 'string'); assert.equal(type.a, 'string');
assert.equal(type.b, 'number'); assert.equal(type.b, 'number');
assert.equal(type.c, 'boolean'); assert.equal(type.c, 'boolean');
}); });
it('should handle nesting objects', function() { it('should handle nesting objects', function () {
var json = {a: 'str', b: 0, c: true, d: {x: 10, y: 5}}; var json = {a: 'str', b: 0, c: true, d: {x: 10, y: 5}};
var type = introspectType(json); var type = introspectType(json);
assert.equal(type.a, 'string'); assert.equal(type.a, 'string');
assert.equal(type.b, 'number'); assert.equal(type.b, 'number');
assert.equal(type.c, 'boolean'); assert.equal(type.c, 'boolean');
assert.equal(type.d.x, 'number'); assert.equal(type.d.x, 'number');
assert.equal(type.d.y, 'number'); assert.equal(type.d.y, 'number');
}); });
it('should handle nesting arrays', function() { it('should handle nesting arrays', function () {
var json = {a: 'str', b: 0, c: true, d: [1, 2]}; var json = {a: 'str', b: 0, c: true, d: [1, 2]};
var type = introspectType(json); var type = introspectType(json);
assert.equal(type.a, 'string'); assert.equal(type.a, 'string');
assert.equal(type.b, 'number'); assert.equal(type.b, 'number');
assert.equal(type.c, 'boolean'); assert.equal(type.c, 'boolean');
assert.deepEqual(type.d, ['number']); 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 = { var json = {
name: 'Joe', name: 'Joe',
age: 30, age: 30,
birthday: new Date(), birthday: new Date(),
vip: true, vip: true,
address: { address: {
street: '1 Main St', street: '1 Main St',
city: 'San Jose', city: 'San Jose',
state: 'CA', state: 'CA',
zipcode: '95131', zipcode: '95131',
country: 'US' country: 'US'
}, },
friends: ['John', 'Mary'], friends: ['John', 'Mary'],
emails: [ emails: [
{label: 'work', id: 'x@sample.com'}, {label: 'work', id: 'x@sample.com'},
{label: 'home', id: 'x@home.com'} {label: 'home', id: 'x@home.com'}
], ],
tags: [] tags: []
}; };
var copy = traverse(json).clone(); var copy = traverse(json).clone();
var schema = introspectType(json); var schema = introspectType(json);
var builder = new ModelBuilder(); var builder = new ModelBuilder();
var Model = builder.define('MyModel', schema, {idInjection: false}); var Model = builder.define('MyModel', schema, {idInjection: false});
// FIXME: [rfeng] The constructor mutates the arguments // FIXME: [rfeng] The constructor mutates the arguments
var obj = new Model(json); var obj = new Model(json);
obj = obj.toObject(); obj = obj.toObject();
assert.deepEqual(obj, copy); assert.deepEqual(obj, copy);
done(); done();
}); });
}); });

View File

@ -4,36 +4,36 @@ var should = require('./init.js');
var Schema = require('../').Schema; var Schema = require('../').Schema;
var ModelBuilder = require('../').ModelBuilder; var ModelBuilder = require('../').ModelBuilder;
describe('JSON property', function() { describe('JSON property', function () {
var dataSource, Model; var dataSource, Model;
it('should be defined', function() { it('should be defined', function () {
dataSource = getSchema(); dataSource = getSchema();
Model = dataSource.define('Model', {propertyName: ModelBuilder.JSON}); Model = dataSource.define('Model', {propertyName: ModelBuilder.JSON});
var m = new Model; var m = new Model;
(new Boolean('propertyName' in m)).should.eql(true); (new Boolean('propertyName' in m)).should.eql(true);
should.not.exist(m.propertyName); should.not.exist(m.propertyName);
}); });
it('should accept JSON in constructor and return object', function() { it('should accept JSON in constructor and return object', function () {
var m = new Model({ var m = new Model({
propertyName: '{"foo": "bar"}' propertyName: '{"foo": "bar"}'
});
m.propertyName.should.be.an.Object;
m.propertyName.foo.should.equal('bar');
}); });
m.propertyName.should.be.an.Object;
m.propertyName.foo.should.equal('bar');
});
it('should accept object in setter and return object', function() { it('should accept object in setter and return object', function () {
var m = new Model; var m = new Model;
m.propertyName = {"foo": "bar"}; m.propertyName = {"foo": "bar"};
m.propertyName.should.be.an.Object; m.propertyName.should.be.an.Object;
m.propertyName.foo.should.equal('bar'); m.propertyName.foo.should.equal('bar');
}); });
it('should accept string in setter and return string', function() { it('should accept string in setter and return string', function () {
var m = new Model; var m = new Model;
m.propertyName = '{"foo": "bar"}'; m.propertyName = '{"foo": "bar"}';
m.propertyName.should.be.a.String; m.propertyName.should.be.a.String;
m.propertyName.should.equal('{"foo": "bar"}'); m.propertyName.should.equal('{"foo": "bar"}');
}); });
}); });

View File

@ -3,8 +3,8 @@ var should = require('./init.js');
var loopbackData = require('../'); var loopbackData = require('../');
describe('loopback-datasource-juggler', function() { describe('loopback-datasource-juggler', function () {
it('should expose version', function () { it('should expose version', function () {
loopbackData.version.should.equal(require('../package.json').version); loopbackData.version.should.equal(require('../package.json').version);
}); });
}); });

File diff suppressed because it is too large Load Diff

View File

@ -4,254 +4,258 @@ var should = require('./init.js');
var db, Person; var db, Person;
var ValidationError = require('..').ValidationError; var ValidationError = require('..').ValidationError;
describe('manipulation', function() { describe('manipulation', function () {
before(function(done) { before(function (done) {
db = getSchema(); 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);
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) { describe('create', function () {
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 return instance of object', function(done) { before(function (done) {
var person = Person.create(function(err, p) { Person.destroyAll(done);
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);
});
}); });
describe('save', function() { it('should create instance', function (done) {
Person.create({name: 'Anatoliy'}, function (err, p) {
it('should save new object', function(done) { p.name.should.equal('Anatoliy');
var p = new Person; should.not.exist(err);
p.save(function(err) { should.exist(p);
should.not.exist(err); Person.findById(p.id, function (err, person) {
should.exist(p.id); person.id.should.equal(p.id);
done(); 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() { it('should return instance of object', function (done) {
var person; var person = Person.create(function (err, p) {
p.id.should.eql(person.id);
before(function(done) { done();
Person.destroyAll(function() { });
person = Person.create(done); should.exist(person);
}); person.should.be.an.instanceOf(Person);
}); should.not.exist(person.id);
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 work when called without callback', function (done) {
Person.afterCreate = function (next) {
it('should destroy record', function(done) { this.should.be.an.instanceOf(Person);
Person.create(function(err, p){ this.name.should.equal('Nickolay');
p.destroy(function(err) { should.exist(this.id);
should.not.exist(err); Person.afterCreate = null;
Person.exists(p.id, function(err, ex) { next();
ex.should.not.be.ok; setTimeout(done, 10);
done(); };
}); Person.create({name: 'Nickolay'});
});
});
});
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 create instance with blank data', function (done) {
it('should initialize object properly', function() { Person.create(function (err, p) {
var hw = 'Hello word', should.not.exist(err);
now = Date.now(), should.exist(p);
person = new Person({name: hw}); should.not.exists(p.name);
Person.findById(p.id, function (err, person) {
person.name.should.equal(hw); person.id.should.equal(p.id);
person.propertyChanged('name').should.be.false; should.not.exists(person.name);
person.name = 'Goodbye, Lenin'; done();
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');
// });
}); });
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');
// });
});
}); });

View File

@ -11,243 +11,236 @@ var ModelDefinition = require('../lib/model-definition');
describe('ModelDefinition class', function () { describe('ModelDefinition class', function () {
it('should be able to define plain models', function (done) { it('should be able to define plain models', function (done) {
var modelBuilder = new ModelBuilder(); var modelBuilder = new ModelBuilder();
var User = new ModelDefinition(modelBuilder, 'User', { var User = new ModelDefinition(modelBuilder, 'User', {
name: "string", name: "string",
bio: ModelBuilder.Text, bio: ModelBuilder.Text,
approved: Boolean, approved: Boolean,
joinedAt: Date, joinedAt: Date,
age: "number" age: "number"
}); });
User.build(); User.build();
assert.equal(User.properties.name.type, String); assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text); assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean); assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date); assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number); 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(); done();
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(); });
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) { User.build();
var modelBuilder = new ModelBuilder(); 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', { var json = User.toJSON();
name: "string", assert.equal(json.name, "User");
bio: ModelBuilder.Text, assert.equal(json.properties.name.type, "String");
approved: Boolean, assert.equal(json.properties.bio.type, "Text");
joinedAt: Date, assert.equal(json.properties.approved.type, "Boolean");
age: "number" 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}); done();
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 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 json = User.toJSON();
var modelBuilder = new ModelBuilder(); 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', { assert.equal(json.properties.address.type, 'Address');
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: {
street: String,
city: String,
zipCode: String,
state: String
}
});
User.build(); done();
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' }, it('should report correct id names', function (done) {
city: { type: 'String' }, var modelBuilder = new ModelBuilder();
zipCode: { type: 'String' },
state: { type: 'String' } });
done();
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) { it('should report correct table/column names', function (done) {
var modelBuilder = new ModelBuilder(); var modelBuilder = new ModelBuilder();
var Address = modelBuilder.define('Address', { var User = new ModelDefinition(modelBuilder, 'User', {
street: String, userId: {type: String, id: true, oracle: {column: 'ID'}},
city: String, name: "string"
zipCode: String, }, {oracle: {table: 'USER'}});
state: String
});
var User = new ModelDefinition(modelBuilder, 'User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: Address
}); assert.equal(User.tableName('oracle'), 'USER');
assert.equal(User.tableName('mysql'), 'User');
User.build(); assert.equal(User.columnName('oracle', 'userId'), 'ID');
assert.equal(User.properties.name.type, String); assert.equal(User.columnName('mysql', 'userId'), 'userId');
assert.equal(User.properties.bio.type, ModelBuilder.Text); done();
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 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) { assert(anotherChild.prototype instanceof baseChild);
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);
});
}); });

View File

@ -4,80 +4,81 @@ Text = Schema.Text
require('./spec_helper').init exports require('./spec_helper').init exports
schemas = schemas =
neo4j: neo4j:
url: 'http://localhost:7474/' url: 'http://localhost:7474/'
mongoose: mongoose:
url: 'mongodb://localhost/test' url: 'mongodb://localhost/test'
redis: {} redis: {}
memory: {} memory: {}
cradle: {} cradle: {}
nano: nano:
url: 'http://localhost:5984/nano-test' url: 'http://localhost:5984/nano-test'
testOrm = (dataSource) -> testOrm = (dataSource) ->
User = Post = 'unknown'
maxUsers = 100
maxPosts = 50000
users = []
User = Post = 'unknown' it 'should define simple', (test) ->
maxUsers = 100 User = dataSource.define 'User', {
maxPosts = 50000 name: String,
users = [] 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', { User.hasMany(Post, {as: 'posts', foreignKey: 'userId'})
name: String, Post.belongsTo(User, {as: 'author', foreignKey: 'userId'})
bio: Text,
approved: Boolean,
joinedAt: Date,
age: Number
}
Post = dataSource.define 'Post', test.done()
title: { type: String, length: 255, index: true }
content: { type: Text }
date: { type: Date, detault: Date.now }
published: { type: Boolean, default: false }
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}) it 'should create users', (test) ->
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}) 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) -> it 'do some queries using foreign keys', (test) ->
wait = maxUsers wait = 4
done = (e, u) -> done = ->
users.push(u) test.done() if --wait == 0
test.done() if --wait == 0 ts = Date.now()
User.create(done) for i in [1..maxUsers] 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) -> return
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 'do some queries using foreign keys', (test) -> it 'should destroy all data', (test) ->
wait = 4 Post.destroyAll ->
done = -> test.done() if --wait == 0 User.destroyAll(test.done)
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)
Object.keys(schemas).forEach (schemaName) -> Object.keys(schemas).forEach (schemaName) ->
return if process.env.ONLY && process.env.ONLY != schemaName return if process.env.ONLY && process.env.ONLY != schemaName
context schemaName, -> context schemaName, ->
dataSource = new Schema schemaName, schemas[schemaName] dataSource = new Schema schemaName, schemas[schemaName]
testOrm(dataSource) testOrm(dataSource)

View File

@ -3,251 +3,251 @@ var should = require('./init.js');
var db, Book, Chapter, Author, Reader; var db, Book, Chapter, Author, Reader;
describe('relations', function() { describe('relations', function () {
before(function(done) { before(function (done) {
db = getSchema(); db = getSchema();
Book = db.define('Book', {name: String}); Book = db.define('Book', {name: String});
Chapter = db.define('Chapter', {name: {type: String, index: true}}); Chapter = db.define('Chapter', {name: {type: String, index: true}});
Author = db.define('Author', {name: String}); Author = db.define('Author', {name: String});
Reader = db.define('Reader', {name: String}); Reader = db.define('Reader', {name: String});
db.automigrate(function() { db.automigrate(function () {
Book.destroyAll(function() { Book.destroyAll(function () {
Chapter.destroyAll(function() { Chapter.destroyAll(function () {
Author.destroyAll(function() { Author.destroyAll(function () {
Reader.destroyAll(done); 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() { it('can be declared in short form', function (done) {
db.disconnect(); 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('should build record on scope', function (done) {
it('can be declared in different ways', function(done) { Book.create(function (err, book) {
Book.hasMany(Chapter); var c = book.chapters.build();
Book.hasMany(Reader, {as: 'users'}); c.bookId.should.equal(book.id);
Book.hasMany(Author, {foreignKey: 'projectId'}); c.save(done);
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();
});
}
});
}); });
describe('belongsTo', function() { it('should create record on scope', function (done) {
var List, Item, Fear, Mind; Book.create(function (err, book) {
book.chapters.create(function (err, c) {
it('can be declared in different ways', function() { should.not.exist(err);
List = db.define('List', {name: String}); should.exist(c);
Item = db.define('Item', {name: String}); c.bookId.should.equal(book.id);
Fear = db.define('Fear'); done();
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() { it.skip('should fetch all scoped instances', function (done) {
var Article, Tag, ArticleTag; Book.create(function (err, book) {
it('can be declared', function(done) { book.chapters.create({name: 'a'}, function () {
Article = db.define('Article', {title: String}); book.chapters.create({name: 'z'}, function () {
Tag = db.define('Tag', {name: String}); book.chapters.create({name: 'c'}, function () {
Article.hasAndBelongsToMany('tags'); fetch(book);
ArticleTag = db.models.ArticleTag;
db.automigrate(function() {
Article.destroyAll(function() {
Tag.destroyAll(function() {
ArticleTag.destroyAll(done)
});
});
}); });
});
}); });
});
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) { book.chapters({order: 'name DESC'}, function (e, c) {
Article.create(function(e, article) { should.not.exist(e);
article.tags.create({name: 'popular'}, function(e, t) { should.exist(c);
t.should.be.an.instanceOf(Tag); c.shift().name.should.equal('z');
// console.log(t); c.pop().name.should.equal('a');
ArticleTag.findOne(function(e, at) { done();
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();
});
});
});
});
});
}); });
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();
});
});
});
});
});
});
}); });

View File

@ -3,55 +3,55 @@ var should = require('./init.js');
var db = getSchema(), slave = getSchema(), Model, SlaveModel; var db = getSchema(), slave = getSchema(), Model, SlaveModel;
describe('dataSource', function() { describe('dataSource', function () {
it('should define Model', function() { it('should define Model', function () {
Model = db.define('Model'); Model = db.define('Model');
Model.dataSource.should.eql(db); Model.dataSource.should.eql(db);
var m = new Model; var m = new Model;
m.getDataSource().should.eql(db); 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() { db.models.Model.count(function (err, c) {
SlaveModel = slave.copyModel(Model); should.not.exist(err);
SlaveModel.dataSource.should.eql(slave); should.exist(c);
slave.should.not.eql(db); c.should.equal(0);
var sm = new SlaveModel; called.should.be.false;
sm.should.be.instanceOf(Model); tr.exec(function () {
sm.getDataSource().should.not.eql(db); setTimeout(function () {
sm.getDataSource().should.eql(slave); called.should.be.true;
}); db.models.Model.count(function (err, c) {
c.should.equal(3);
it('should automigrate', function(done) { done();
db.automigrate(done); });
}); }, 100);
});
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);
});
});
}); });
});
}); });

View File

@ -3,62 +3,62 @@ var should = require('./init.js');
var db, Railway, Station; var db, Railway, Station;
describe('sc0pe', function() { describe('sc0pe', function () {
before(function() { before(function () {
db = getSchema(); db = getSchema();
Railway = db.define('Railway', { Railway = db.define('Railway', {
URID: {type: String, index: true} 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}
});
}); });
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) { beforeEach(function (done) {
Railway.destroyAll(function() { Railway.destroyAll(function () {
Station.destroyAll(done); Station.destroyAll(done);
});
}); });
});
it('should define scope with query', function(done) { it('should define scope with query', function (done) {
Station.scope('active', {where: {isActive: true}}); Station.scope('active', {where: {isActive: true}});
Station.active.create(function(err, station) { Station.active.create(function (err, station) {
should.not.exist(err); should.not.exist(err);
should.exist(station); should.exist(station);
should.exist(station.isActive); should.exist(station.isActive);
station.isActive.should.be.true; station.isActive.should.be.true;
done(); done();
});
}); });
});
it('should allow scope chaining', function(done) { it('should allow scope chaining', function (done) {
Station.scope('active', {where: {isActive: true}}); Station.scope('active', {where: {isActive: true}});
Station.scope('subway', {where: {isUndeground: true}}); Station.scope('subway', {where: {isUndeground: true}});
Station.active.subway.create(function(err, station) { Station.active.subway.create(function (err, station) {
should.not.exist(err); should.not.exist(err);
should.exist(station); should.exist(station);
station.isActive.should.be.true; station.isActive.should.be.true;
station.isUndeground.should.be.true; station.isUndeground.should.be.true;
done(); done();
}) })
}); });
it('should query all', function(done) { it('should query all', function (done) {
Station.scope('active', {where: {isActive: true}}); Station.scope('active', {where: {isActive: true}});
Station.scope('inactive', {where: {isActive: false}}); Station.scope('inactive', {where: {isActive: false}});
Station.scope('ground', {where: {isUndeground: true}}); Station.scope('ground', {where: {isUndeground: true}});
Station.active.ground.create(function() { Station.active.ground.create(function () {
Station.inactive.ground.create(function() { Station.inactive.ground.create(function () {
Station.ground.inactive(function(err, ss) { Station.ground.inactive(function (err, ss) {
ss.should.have.lengthOf(1); ss.should.have.lengthOf(1);
done(); done();
});
});
}); });
});
}); });
});
}); });

View File

@ -1,56 +1,56 @@
/* /*
if (!process.env.TRAVIS) { if (!process.env.TRAVIS) {
var semicov = require('semicov'); var semicov = require('semicov');
semicov.init('lib', 'LoopbackData'); semicov.init('lib', 'LoopbackData');
process.on('exit', semicov.report); process.on('exit', semicov.report);
} }
*/ */
try { try {
global.sinon = require('sinon'); global.sinon = require('sinon');
} catch (e) { } catch (e) {
// ignore // ignore
} }
var group_name = false, EXT_EXP; var group_name = false, EXT_EXP;
function it(should, test_case) { function it(should, test_case) {
check_external_exports(); check_external_exports();
if (group_name) { if (group_name) {
EXT_EXP[group_name][should] = test_case; EXT_EXP[group_name][should] = test_case;
} else { } else {
EXT_EXP[should] = test_case; EXT_EXP[should] = test_case;
} }
} }
global.it = it; global.it = it;
function context(name, tests) { function context(name, tests) {
check_external_exports(); check_external_exports();
EXT_EXP[name] = {}; EXT_EXP[name] = {};
group_name = name; group_name = name;
tests({ tests({
before: function (f) { before: function (f) {
it('setUp', f); it('setUp', f);
}, },
after: function (f) { after: function (f) {
it('tearDown', f); it('tearDown', f);
} }
}); });
group_name = false; group_name = false;
} }
global.context = context; global.context = context;
exports.init = function init(external_exports) { exports.init = function init(external_exports) {
EXT_EXP = external_exports; EXT_EXP = external_exports;
if (external_exports.done) { if (external_exports.done) {
external_exports.done(); external_exports.done();
} }
}; };
function check_external_exports() { function check_external_exports() {
if (!EXT_EXP) throw new Error( if (!EXT_EXP) throw new Error(
'Before run this, please ensure that ' + 'Before run this, please ensure that ' +
'require("spec_helper").init(exports); called'); 'require("spec_helper").init(exports); called');
} }

View File

@ -1,15 +1,15 @@
{ {
"title": { "title": {
"type": "String" "type": "String"
}, },
"author": { "author": {
"type": "String", "type": "String",
"default": "Raymond" "default": "Raymond"
}, },
"body": "String", "body": "String",
"date": { "date": {
"type": "Date" "type": "Date"
}, },
"hidden": "Boolean", "hidden": "Boolean",
"comments": ["String"] "comments": ["String"]
} }

View File

@ -1,83 +1,83 @@
[ [
{ {
"name": "Address", "name": "Address",
"properties": { "properties": {
"label": "string", "label": "string",
"street": "string", "street": "string",
"city": "string", "city": "string",
"zipCode": "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": "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"
}
}
] ]

View File

@ -4,10 +4,8 @@ var fieldsToArray = utils.fieldsToArray;
var removeUndefined = utils.removeUndefined; var removeUndefined = utils.removeUndefined;
var mergeSettings = utils.mergeSettings; var mergeSettings = utils.mergeSettings;
describe('util.fieldsToArray', function () {
describe('util.fieldsToArray', function(){ it('Turn objects and strings into an array of fields to include when finding models', function () {
it('Turn objects and strings into an array of fields to include when finding models', function() {
function sample(fields) { function sample(fields) {
var properties = ['foo', 'bar', 'bat', 'baz']; var properties = ['foo', 'bar', 'bat', 'baz'];
@ -30,90 +28,90 @@ describe('util.fieldsToArray', function(){
}); });
}); });
describe('util.removeUndefined', function(){ describe('util.removeUndefined', function () {
it('Remove undefined values from the query object', function() { it('Remove undefined values from the query object', function () {
var q1 = {where: {x: 1, y: undefined}}; var q1 = {where: {x: 1, y: undefined}};
should.deepEqual(removeUndefined(q1), {where: {x: 1}}); should.deepEqual(removeUndefined(q1), {where: {x: 1}});
var q2 = {where: {x: 1, y: 2}}; var q2 = {where: {x: 1, y: 2}};
should.deepEqual(removeUndefined(q2), {where: {x: 1, y: 2}}); should.deepEqual(removeUndefined(q2), {where: {x: 1, y: 2}});
var q3 = {where: {x: 1, y: {in: [2, undefined]}}}; var q3 = {where: {x: 1, y: {in: [2, undefined]}}};
should.deepEqual(removeUndefined(q3), {where: {x: 1, y: {in: [2]}}}); 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 date = new Date();
var q4 = {where: {x: 1, y: date}}; var q4 = {where: {x: 1, y: date}};
should.deepEqual(removeUndefined(q4), {where: {x: 1, y: date}}); should.deepEqual(removeUndefined(q4), {where: {x: 1, y: date}});
}); });
}); });
describe('util.parseSettings', function(){ describe('util.parseSettings', function () {
it('Parse a full url into a settings object', function() { it('Parse a full url into a settings object', function () {
var url = 'mongodb://x:y@localhost:27017/mydb?w=2'; var url = 'mongodb://x:y@localhost:27017/mydb?w=2';
var settings = utils.parseSettings(url); var settings = utils.parseSettings(url);
should.equal(settings.hostname, 'localhost'); should.equal(settings.hostname, 'localhost');
should.equal(settings.port, 27017); should.equal(settings.port, 27017);
should.equal(settings.host, 'localhost'); should.equal(settings.host, 'localhost');
should.equal(settings.user, 'x'); should.equal(settings.user, 'x');
should.equal(settings.password, 'y'); should.equal(settings.password, 'y');
should.equal(settings.database, 'mydb'); should.equal(settings.database, 'mydb');
should.equal(settings.connector, 'mongodb'); should.equal(settings.connector, 'mongodb');
should.equal(settings.w, '2'); should.equal(settings.w, '2');
should.equal(settings.url, 'mongodb://x:y@localhost:27017/mydb?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() { it('Parse a url without auth into a settings object', function () {
var url = 'mongodb://localhost:27017/mydb/abc?w=2'; var url = 'mongodb://localhost:27017/mydb/abc?w=2';
var settings = utils.parseSettings(url); var settings = utils.parseSettings(url);
should.equal(settings.hostname, 'localhost'); should.equal(settings.hostname, 'localhost');
should.equal(settings.port, 27017); should.equal(settings.port, 27017);
should.equal(settings.host, 'localhost'); should.equal(settings.host, 'localhost');
should.equal(settings.user, undefined); should.equal(settings.user, undefined);
should.equal(settings.password, undefined); should.equal(settings.password, undefined);
should.equal(settings.database, 'mydb'); should.equal(settings.database, 'mydb');
should.equal(settings.connector, 'mongodb'); should.equal(settings.connector, 'mongodb');
should.equal(settings.w, '2'); should.equal(settings.w, '2');
should.equal(settings.url, 'mongodb://localhost:27017/mydb/abc?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() { 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 url = 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&engine=InnoDB';
var settings = utils.parseSettings(url); var settings = utils.parseSettings(url);
should.equal(settings.hostname, '127.0.0.1'); should.equal(settings.hostname, '127.0.0.1');
should.equal(settings.port, 3306); should.equal(settings.port, 3306);
should.equal(settings.host, '127.0.0.1'); should.equal(settings.host, '127.0.0.1');
should.equal(settings.user, undefined); should.equal(settings.user, undefined);
should.equal(settings.password, undefined); should.equal(settings.password, undefined);
should.equal(settings.database, 'mydb'); should.equal(settings.database, 'mydb');
should.equal(settings.connector, 'mysql'); should.equal(settings.connector, 'mysql');
should.equal(settings.x.a, '1'); should.equal(settings.x.a, '1');
should.equal(settings.x.b, '2'); should.equal(settings.x.b, '2');
should.equal(settings.engine, 'InnoDB'); should.equal(settings.engine, 'InnoDB');
should.equal(settings.url, 'mysql://127.0.0.1:3306/mydb?x[a]=1&x[b]=2&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() { it('Parse a url without auth into a settings object', function () {
var url = 'memory://?x=1'; var url = 'memory://?x=1';
var settings = utils.parseSettings(url); var settings = utils.parseSettings(url);
should.equal(settings.hostname, ''); should.equal(settings.hostname, '');
should.equal(settings.user, undefined); should.equal(settings.user, undefined);
should.equal(settings.password, undefined); should.equal(settings.password, undefined);
should.equal(settings.database, undefined); should.equal(settings.database, undefined);
should.equal(settings.connector, 'memory'); should.equal(settings.connector, 'memory');
should.equal(settings.x, '1'); should.equal(settings.x, '1');
should.equal(settings.url, 'memory://?x=1'); should.equal(settings.url, 'memory://?x=1');
}); });
}); });

View File

@ -5,198 +5,198 @@ var j = require('../'), db, User;
var ValidationError = j.ValidationError; var ValidationError = j.ValidationError;
function getValidAttributes() { function getValidAttributes() {
return { return {
name: 'Anatoliy', name: 'Anatoliy',
email: 'email@example.com', email: 'email@example.com',
state: '', state: '',
age: 26, age: 26,
gender: 'male', gender: 'male',
createdByAdmin: false, createdByAdmin: false,
createdByScript: true 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) { describe('lifecycle', function () {
User.destroyAll(function() {
delete User._validations; 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(); done();
});
}); });
}); });
after(function() { it('should work on update', function (done) {
db.disconnect(); delete User._validations;
}); User.validatesPresenceOf('name');
User.create({name: 'Valid'}, function (e, d) {
describe('commons', function() { d.updateAttribute('name', null, function (e) {
should.exist(e);
describe('skipping', function() { e.should.be.instanceOf(Error);
e.should.be.instanceOf(ValidationError);
it('should allow to skip using if: attribute', function() { d.updateAttribute('name', 'Vasiliy', function (e) {
User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'}); should.not.exist(e);
var user = new User; done();
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;
}); });
})
}); });
});
describe('lifecycle', function() { it('should return error code', function (done) {
delete User._validations;
it('should work on create', function(done) { User.validatesPresenceOf('name');
delete User._validations; User.create(function (e, u) {
User.validatesPresenceOf('name'); should.exist(e);
User.create(function(e, u) { e.details.codes.name.should.eql(['presence']);
should.exist(e); done();
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 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() { var user = new User(getValidAttributes())
User.validatesPresenceOf('name', 'email'); user.isValid().should.be.true;
var u = new User;
u.isValid().should.not.be.true; user.createdByScript = false;
u.name = 1; user.isValid().should.be.false;
u.email = 2; user.errors.domain.should.eql(['can\'t be blank']);
u.isValid().should.be.true;
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() { it('should handle same object modification', function (done) {
User.validatesPresenceOf('domain', {unless: 'createdByScript'}); User.validatesUniquenessOf('email');
var u = new User({email: 'hey'});
var user = new User(getValidAttributes()) Boolean(u.isValid(function (valid) {
user.isValid().should.be.true; valid.should.be.true;
u.save(function () {
user.createdByScript = false; u.name = 'Goghi';
user.isValid().should.be.false; u.isValid(function (valid) {
user.errors.domain.should.eql(['can\'t be blank']); valid.should.be.true;
u.save(done);
user.domain = 'domain'; });
user.isValid().should.be.true;
}); });
// 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) { describe('format', function () {
User.validatesUniquenessOf('email'); it('should validate format');
var u = new User({email: 'hey'}); it('should overwrite default blank message with custom format message');
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('numericality', function () {
it('should validate numericality');
});
describe('format', function() { describe('inclusion', function () {
it('should validate format'); it('should validate inclusion');
it('should overwrite default blank message with custom format message'); });
});
describe('numericality', function() { describe('exclusion', function () {
it('should validate numericality'); it('should validate exclusion');
}); });
describe('inclusion', function() { describe('length', function () {
it('should validate inclusion'); it('should validate length');
}); });
describe('exclusion', function() { describe('custom', function () {
it('should validate exclusion'); it('should validate using custom sync validation');
}); it('should validate using custom async validation');
});
describe('length', function() {
it('should validate length');
});
describe('custom', function() {
it('should validate using custom sync validation');
it('should validate using custom async validation');
});
}); });