Merge branch 'release/1.2.13' into production

This commit is contained in:
Miroslav Bajtoš 2014-01-27 10:15:12 +01:00
commit 758cea9f5c
56 changed files with 8844 additions and 8782 deletions

View File

@ -4,7 +4,9 @@ LoopBack DataSource Juggler is an ORM that provides a common set of interfaces
for interacting with databases, REST APIs, and other data sources. It was
initially forked from [JugglingDB](https://github.com/1602/jugglingdb).
For full documentation, see [StrongLoop Suite Documentation](http://docs.strongloop.com/display/DOC/Data+Source+Juggler).
**For full documentation, see the official StrongLoop documentation**:
* [Data sources and connectors](http://docs.strongloop.com/display/DOC/Data+sources+and+connectors)
* [Data Source Juggler](http://docs.strongloop.com/display/DOC/Data+Source+Juggler).
## Installation

View File

@ -58,12 +58,15 @@ The `DataSource` constructor is available from `loopback-datasource-juggler` mod
- connector: The name or instance of the connector module
- settings: An object of properties to configure the connector
var dataSource = new DataSource({
connector: require('loopback-connector-mongodb'),
host: 'localhost',
port: 27017,
database: 'mydb'
});
```
var dataSource = new DataSource({
connector: require('loopback-connector-mongodb'),
host: 'localhost',
port: 27017,
database: 'mydb'
});
```
#### connector
@ -75,10 +78,11 @@ The `connector` argument passed the DataSource constructor can be one of the fol
to 'loopback-connector-<shortName>'
* A local module under ./connectors/<connectorName> folder
var ds1 = new DataSource('memory');
var ds2 = new DataSource('loopback-connector-mongodb'));
var ds3 = new DataSource(require('loopback-connector-oracle'));
```
var ds1 = new DataSource('memory');
var ds2 = new DataSource('loopback-connector-mongodb'));
var ds3 = new DataSource(require('loopback-connector-oracle'));
```
**Note**: LoopBack provides a built-in connector named as `memory` to use in-memory
store for CRUD operations.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,83 +1,83 @@
[
{
"name": "Address",
"properties": {
"label": "string",
"street": "string",
"city": "string",
"zipCode": "string"
}
},
{
"name": "Account",
"properties": {
"id": "string",
"customer": {
"type": "Customer",
"relation": {
"type": "belongsTo",
"as": "account"
}
},
"balance": "number"
}
},
{
"name": "Customer",
"options": {
"oracle": {
"owner": "STRONGLOOP",
"table": "CUSTOMER"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"doc": "Customer ID"
},
"firstName": {
"type": "string",
"trim": true,
"required": true,
"oracle": {
"column": "FNAME",
"type": "VARCHAR",
"length": 32
}
},
"lastName": {
"type": "string",
"trim": true,
"required": true,
"oracle": {
"column": "LNAME",
"type": "VARCHAR",
"length": 32
}
},
"vip": {
"type": "boolean",
"doc": "indicate if the customer is a VIP",
"oracle": {
"column": "VIP",
"type": "CHAR",
"length": 1
}
},
"emails": [
{
"type": "string",
"trim": true
}
],
"address": {
"type": "Address"
},
"account": "Account"
}
{
"name": "Address",
"properties": {
"label": "string",
"street": "string",
"city": "string",
"zipCode": "string"
}
},
{
"name": "Account",
"properties": {
"id": "string",
"customer": {
"type": "Customer",
"relation": {
"type": "belongsTo",
"as": "account"
}
},
"balance": "number"
}
},
{
"name": "Customer",
"options": {
"oracle": {
"owner": "STRONGLOOP",
"table": "CUSTOMER"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"doc": "Customer ID"
},
"firstName": {
"type": "string",
"trim": true,
"required": true,
"oracle": {
"column": "FNAME",
"type": "VARCHAR",
"length": 32
}
},
"lastName": {
"type": "string",
"trim": true,
"required": true,
"oracle": {
"column": "LNAME",
"type": "VARCHAR",
"length": 32
}
},
"vip": {
"type": "boolean",
"doc": "indicate if the customer is a VIP",
"oracle": {
"column": "VIP",
"type": "CHAR",
"length": 1
}
},
"emails": [
{
"type": "string",
"trim": true
}
],
"address": {
"type": "Address"
},
"account": "Account"
}
}
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1130
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

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

View File

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

View File

@ -26,135 +26,137 @@ function Inclusion() {
*
*/
Inclusion.include = function (objects, include, cb) {
var self = this;
var self = this;
if (
(include.constructor.name == 'Array' && include.length == 0) ||
(include.constructor.name == 'Object' && Object.keys(include).length == 0)
) {
cb(null, objects);
return;
if (
(include.constructor.name == 'Array' && include.length == 0) ||
(include.constructor.name == 'Object' && Object.keys(include).length == 0)
) {
cb(null, objects);
return;
}
include = processIncludeJoin(include);
var keyVals = {};
var objsByKeys = {};
var nbCallbacks = 0;
for (var i = 0; i < include.length; i++) {
var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys);
if (callback !== null) {
nbCallbacks++;
callback(function () {
nbCallbacks--;
if (nbCallbacks == 0) {
cb(null, objects);
}
});
} else {
cb(null, objects);
}
}
function processIncludeJoin(ij) {
if (typeof ij === 'string') {
ij = [ij];
}
if (ij.constructor.name === 'Object') {
var newIj = [];
for (var key in ij) {
var obj = {};
obj[key] = ij[key];
newIj.push(obj);
}
return newIj;
}
return ij;
}
function processIncludeItem(objs, include, keyVals, objsByKeys) {
var relations = self.relations;
if (include.constructor.name === 'Object') {
var relationName = Object.keys(include)[0];
var subInclude = include[relationName];
} else {
var relationName = include;
var subInclude = [];
}
var relation = relations[relationName];
if (!relation) {
return function () {
cb(new Error('Relation "' + relationName + '" is not defined for '
+ self.modelName + ' model'));
}
}
include = processIncludeJoin(include);
var req = {'where': {}};
var keyVals = {};
var objsByKeys = {};
if (!keyVals[relation.keyFrom]) {
objsByKeys[relation.keyFrom] = {};
objs.filter(Boolean).forEach(function (obj) {
if (!objsByKeys[relation.keyFrom][obj[relation.keyFrom]]) {
objsByKeys[relation.keyFrom][obj[relation.keyFrom]] = [];
}
objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj);
});
keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]);
}
var nbCallbacks = 0;
for (var i = 0; i < include.length; i++) {
var callback = processIncludeItem(objects, include[i], keyVals, objsByKeys);
if (callback !== null) {
nbCallbacks++;
callback(function() {
nbCallbacks--;
if (nbCallbacks == 0) {
cb(null, objects);
if (keyVals[relation.keyFrom].length > 0) {
// deep clone is necessary since inq seems to change the processed array
var keysToBeProcessed = {};
var inValues = [];
for (var j = 0; j < keyVals[relation.keyFrom].length; j++) {
keysToBeProcessed[keyVals[relation.keyFrom][j]] = true;
if (keyVals[relation.keyFrom][j] !== 'null'
&& keyVals[relation.keyFrom][j] !== 'undefined') {
inValues.push(keyVals[relation.keyFrom][j]);
}
}
req['where'][relation.keyTo] = {inq: inValues};
req['include'] = subInclude;
return function (cb) {
relation.modelTo.find(req, function (err, objsIncluded) {
for (var i = 0; i < objsIncluded.length; i++) {
delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
for (var j = 0; j < objectsFrom.length; j++) {
if (!objectsFrom[j].__cachedRelations) {
objectsFrom[j].__cachedRelations = {};
}
if (relation.multiple) {
if (!objectsFrom[j].__cachedRelations[relationName]) {
objectsFrom[j].__cachedRelations[relationName] = [];
}
});
} else {
cb(null, objects);
}
objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]);
} else {
objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i];
}
}
}
// No relation have been found for these keys
for (var key in keysToBeProcessed) {
var objectsFrom = objsByKeys[relation.keyFrom][key];
for (var j = 0; j < objectsFrom.length; j++) {
if (!objectsFrom[j].__cachedRelations) {
objectsFrom[j].__cachedRelations = {};
}
objectsFrom[j].__cachedRelations[relationName] =
relation.multiple ? [] : null;
}
}
cb(err, objsIncluded);
});
};
}
function processIncludeJoin(ij) {
if (typeof ij === 'string') {
ij = [ij];
}
if (ij.constructor.name === 'Object') {
var newIj = [];
for (var key in ij) {
var obj = {};
obj[key] = ij[key];
newIj.push(obj);
}
return newIj;
}
return ij;
}
function processIncludeItem(objs, include, keyVals, objsByKeys) {
var relations = self.relations;
if (include.constructor.name === 'Object') {
var relationName = Object.keys(include)[0];
var subInclude = include[relationName];
} else {
var relationName = include;
var subInclude = [];
}
var relation = relations[relationName];
if (!relation) {
return function() {
cb(new Error('Relation "' + relationName + '" is not defined for ' + self.modelName + ' model'));
}
}
var req = {'where': {}};
if (!keyVals[relation.keyFrom]) {
objsByKeys[relation.keyFrom] = {};
objs.filter(Boolean).forEach(function(obj) {
if (!objsByKeys[relation.keyFrom][obj[relation.keyFrom]]) {
objsByKeys[relation.keyFrom][obj[relation.keyFrom]] = [];
}
objsByKeys[relation.keyFrom][obj[relation.keyFrom]].push(obj);
});
keyVals[relation.keyFrom] = Object.keys(objsByKeys[relation.keyFrom]);
}
if (keyVals[relation.keyFrom].length > 0) {
// deep clone is necessary since inq seems to change the processed array
var keysToBeProcessed = {};
var inValues = [];
for (var j = 0; j < keyVals[relation.keyFrom].length; j++) {
keysToBeProcessed[keyVals[relation.keyFrom][j]] = true;
if (keyVals[relation.keyFrom][j] !== 'null' && keyVals[relation.keyFrom][j] !== 'undefined') {
inValues.push(keyVals[relation.keyFrom][j]);
}
}
req['where'][relation.keyTo] = {inq: inValues};
req['include'] = subInclude;
return function(cb) {
relation.modelTo.find(req, function(err, objsIncluded) {
for (var i = 0; i < objsIncluded.length; i++) {
delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
for (var j = 0; j < objectsFrom.length; j++) {
if (!objectsFrom[j].__cachedRelations) {
objectsFrom[j].__cachedRelations = {};
}
if (relation.multiple) {
if (!objectsFrom[j].__cachedRelations[relationName]) {
objectsFrom[j].__cachedRelations[relationName] = [];
}
objectsFrom[j].__cachedRelations[relationName].push(objsIncluded[i]);
} else {
objectsFrom[j].__cachedRelations[relationName] = objsIncluded[i];
}
}
}
// No relation have been found for these keys
for (var key in keysToBeProcessed) {
var objectsFrom = objsByKeys[relation.keyFrom][key];
for (var j = 0; j < objectsFrom.length; j++) {
if (!objectsFrom[j].__cachedRelations) {
objectsFrom[j].__cachedRelations = {};
}
objectsFrom[j].__cachedRelations[relationName] = relation.multiple ? [] : null;
}
}
cb(err, objsIncluded);
});
};
}
return null;
}
return null;
}
}

View File

@ -1,60 +1,60 @@
module.exports = function getIntrospector(ModelBuilder) {
function introspectType(value) {
function introspectType(value) {
// Unknown type, using Any
if (value === null || value === undefined) {
return ModelBuilder.Any;
}
// Check registered schemaTypes
for (var t in ModelBuilder.schemaTypes) {
var st = ModelBuilder.schemaTypes[t];
if (st !== Object && st !== Array && (value instanceof st)) {
return t;
}
}
var type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') {
return type;
}
if (value instanceof Date) {
return 'date';
}
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
if (value[i] === null || value[i] === undefined) {
continue;
}
var itemType = introspectType(value[i]);
if (itemType) {
return [itemType];
}
}
return 'array';
}
if (type === 'function') {
return value.constructor.name;
}
var properties = {};
for (var p in value) {
var itemType = introspectType(value[p]);
if (itemType) {
properties[p] = itemType;
}
}
if (Object.keys(properties).length === 0) {
return 'object';
}
return properties;
// Unknown type, using Any
if (value === null || value === undefined) {
return ModelBuilder.Any;
}
return introspectType;
// Check registered schemaTypes
for (var t in ModelBuilder.schemaTypes) {
var st = ModelBuilder.schemaTypes[t];
if (st !== Object && st !== Array && (value instanceof st)) {
return t;
}
}
var type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean') {
return type;
}
if (value instanceof Date) {
return 'date';
}
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
if (value[i] === null || value[i] === undefined) {
continue;
}
var itemType = introspectType(value[i]);
if (itemType) {
return [itemType];
}
}
return 'array';
}
if (type === 'function') {
return value.constructor.name;
}
var properties = {};
for (var p in value) {
var itemType = introspectType(value[p]);
if (itemType) {
properties[p] = itemType;
}
}
if (Object.keys(properties).length === 0) {
return 'object';
}
return properties;
}
return introspectType;
}

View File

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

View File

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

View File

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

View File

@ -21,27 +21,27 @@ module.exports = ModelDefinition;
*
*/
function ModelDefinition(modelBuilder, name, properties, settings) {
if (!(this instanceof ModelDefinition)) {
// Allow to call ModelDefinition without new
return new ModelDefinition(modelBuilder, name, properties, settings);
}
this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance;
assert(name, 'name is missing');
if (!(this instanceof ModelDefinition)) {
// Allow to call ModelDefinition without new
return new ModelDefinition(modelBuilder, name, properties, settings);
}
this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance;
assert(name, 'name is missing');
if (arguments.length === 2 && typeof name === 'object') {
var schema = name;
this.name = schema.name;
this.rawProperties = schema.properties || {}; // Keep the raw property definitions
this.settings = schema.settings || {};
} else {
assert(typeof name === 'string', 'name must be a string');
this.name = name;
this.rawProperties = properties || {}; // Keep the raw property definitions
this.settings = settings || {};
}
this.relations = [];
this.properties = null;
this.build();
if (arguments.length === 2 && typeof name === 'object') {
var schema = name;
this.name = schema.name;
this.rawProperties = schema.properties || {}; // Keep the raw property definitions
this.settings = schema.settings || {};
} else {
assert(typeof name === 'string', 'name must be a string');
this.name = name;
this.rawProperties = properties || {}; // Keep the raw property definitions
this.settings = settings || {};
}
this.relations = [];
this.properties = null;
this.build();
}
util.inherits(ModelDefinition, EventEmitter);
@ -49,18 +49,17 @@ util.inherits(ModelDefinition, EventEmitter);
// Set up types
require('./types')(ModelDefinition);
/**
* Return table name for specified `modelName`
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
*/
ModelDefinition.prototype.tableName = function (connectorType) {
var settings = this.settings;
if(settings[connectorType]) {
return settings[connectorType].table || settings[connectorType].tableName || this.name;
} else {
return this.name;
}
var settings = this.settings;
if (settings[connectorType]) {
return settings[connectorType].table || settings[connectorType].tableName || this.name;
} else {
return this.name;
}
};
/**
@ -70,16 +69,16 @@ ModelDefinition.prototype.tableName = function (connectorType) {
* @returns {String} columnName
*/
ModelDefinition.prototype.columnName = function (connectorType, propertyName) {
if(!propertyName) {
return propertyName;
}
this.build();
var property = this.properties[propertyName];
if(property && property[connectorType]) {
return property[connectorType].column || property[connectorType].columnName || propertyName;
} else {
return propertyName;
}
if (!propertyName) {
return propertyName;
}
this.build();
var property = this.properties[propertyName];
if (property && property[connectorType]) {
return property[connectorType].column || property[connectorType].columnName || propertyName;
} else {
return propertyName;
}
};
/**
@ -89,16 +88,16 @@ ModelDefinition.prototype.columnName = function (connectorType, propertyName) {
* @returns {Object} column metadata
*/
ModelDefinition.prototype.columnMetadata = function (connectorType, propertyName) {
if(!propertyName) {
return propertyName;
}
this.build();
var property = this.properties[propertyName];
if(property && property[connectorType]) {
return property[connectorType];
} else {
return null;
}
if (!propertyName) {
return propertyName;
}
this.build();
var property = this.properties[propertyName];
if (property && property[connectorType]) {
return property[connectorType];
} else {
return null;
}
};
/**
@ -107,17 +106,17 @@ ModelDefinition.prototype.columnMetadata = function (connectorType, propertyName
* @returns {String[]} column names
*/
ModelDefinition.prototype.columnNames = function (connectorType) {
this.build();
var props = this.properties;
var cols = [];
for(var p in props) {
if(props[p][connectorType]) {
cols.push(property[connectorType].column || props[p][connectorType].columnName || p);
} else {
cols.push(p);
}
this.build();
var props = this.properties;
var cols = [];
for (var p in props) {
if (props[p][connectorType]) {
cols.push(property[connectorType].column || props[p][connectorType].columnName || p);
} else {
cols.push(p);
}
return cols;
}
return cols;
};
/**
@ -125,27 +124,27 @@ ModelDefinition.prototype.columnNames = function (connectorType) {
* @returns {Object[]} property name/index for IDs
*/
ModelDefinition.prototype.ids = function () {
if(this._ids) {
return this._ids;
if (this._ids) {
return this._ids;
}
var ids = [];
this.build();
var props = this.properties;
for (var key in props) {
var id = props[key].id;
if (!id) {
continue;
}
var ids = [];
this.build();
var props = this.properties;
for (var key in props) {
var id = props[key].id;
if(!id) {
continue;
}
if(typeof id !== 'number') {
id = 1;
}
ids.push({name: key, id: id});
if (typeof id !== 'number') {
id = 1;
}
ids.sort(function (a, b) {
return a.key - b.key;
});
this._ids = ids;
return ids;
ids.push({name: key, id: id});
}
ids.sort(function (a, b) {
return a.key - b.key;
});
this._ids = ids;
return ids;
};
/**
@ -153,20 +152,21 @@ ModelDefinition.prototype.ids = function () {
* @param {String} modelName The model name
* @returns {String} columnName for ID
*/
ModelDefinition.prototype.idColumnName = function(connectorType) {
return this.columnName(connectorType, this.idName());
ModelDefinition.prototype.idColumnName = function (connectorType) {
return this.columnName(connectorType, this.idName());
};
/**
* Find the ID property name
* @returns {String} property name for ID
*/
ModelDefinition.prototype.idName = function() {
var id = this.ids()[0];
if(this.properties.id && this.properties.id.id) {
return 'id';
} else {}
return id && id.name;
ModelDefinition.prototype.idName = function () {
var id = this.ids()[0];
if (this.properties.id && this.properties.id.id) {
return 'id';
} else {
}
return id && id.name;
};
/**
@ -174,11 +174,11 @@ ModelDefinition.prototype.idName = function() {
* @returns {String[]} property names for IDs
*/
ModelDefinition.prototype.idNames = function () {
var ids = this.ids();
var names = ids.map(function (id) {
return id.name;
});
return names;
var ids = this.ids();
var names = ids.map(function (id) {
return id.name;
});
return names;
};
/**
@ -186,19 +186,19 @@ ModelDefinition.prototype.idNames = function () {
* @returns {{}}
*/
ModelDefinition.prototype.indexes = function () {
this.build();
var indexes = {};
if (this.settings.indexes) {
for (var i in this.settings.indexes) {
indexes[i] = this.settings.indexes[i];
}
this.build();
var indexes = {};
if (this.settings.indexes) {
for (var i in this.settings.indexes) {
indexes[i] = this.settings.indexes[i];
}
for (var p in this.properties) {
if (this.properties[p].index) {
indexes[p + '_index'] = this.properties[p].index;
}
}
for (var p in this.properties) {
if (this.properties[p].index) {
indexes[p + '_index'] = this.properties[p].index;
}
return indexes;
}
return indexes;
};
/**
@ -206,41 +206,41 @@ ModelDefinition.prototype.indexes = function () {
* @param {Boolean} force Forcing rebuild
*/
ModelDefinition.prototype.build = function (forceRebuild) {
if(forceRebuild) {
this.properties = null;
this.relations = [];
this._ids = null;
}
if (this.properties) {
return this.properties;
}
this.properties = {};
for (var p in this.rawProperties) {
var prop = this.rawProperties[p];
var type = this.modelBuilder.resolveType(prop);
if (typeof type === 'string') {
this.relations.push({
source: this.name,
target: type,
type: Array.isArray(prop) ? 'hasMany' : 'belongsTo',
as: p
});
} else {
var typeDef = {
type: type
};
if (typeof prop === 'object' && prop !== null) {
for (var a in prop) {
// Skip the type property but don't delete it Model.extend() shares same instances of the properties from the base class
if (a !== 'type') {
typeDef[a] = prop[a];
}
}
}
this.properties[p] = typeDef;
}
}
if (forceRebuild) {
this.properties = null;
this.relations = [];
this._ids = null;
}
if (this.properties) {
return this.properties;
}
this.properties = {};
for (var p in this.rawProperties) {
var prop = this.rawProperties[p];
var type = this.modelBuilder.resolveType(prop);
if (typeof type === 'string') {
this.relations.push({
source: this.name,
target: type,
type: Array.isArray(prop) ? 'hasMany' : 'belongsTo',
as: p
});
} else {
var typeDef = {
type: type
};
if (typeof prop === 'object' && prop !== null) {
for (var a in prop) {
// Skip the type property but don't delete it Model.extend() shares same instances of the properties from the base class
if (a !== 'type') {
typeDef[a] = prop[a];
}
}
}
this.properties[p] = typeDef;
}
}
return this.properties;
};
/**
@ -249,56 +249,55 @@ ModelDefinition.prototype.build = function (forceRebuild) {
* @param {Object} propertyDefinition The property definition
*/
ModelDefinition.prototype.defineProperty = function (propertyName, propertyDefinition) {
this.rawProperties[propertyName] = propertyDefinition;
this.build(true);
this.rawProperties[propertyName] = propertyDefinition;
this.build(true);
};
function isModelClass(cls) {
if(!cls) {
return false;
}
return cls.prototype instanceof ModelBaseClass;
if (!cls) {
return false;
}
return cls.prototype instanceof ModelBaseClass;
}
ModelDefinition.prototype.toJSON = function(forceRebuild) {
if(forceRebuild) {
this.json = null;
}
if(this.json) {
return json;
}
var json = {
name: this.name,
properties: {},
settings: this.settings
};
this.build(forceRebuild);
var mapper = function(val) {
if(val === undefined || val === null) {
return val;
}
if('function' === typeof val.toJSON) {
// The value has its own toJSON() object
return val.toJSON();
}
if('function' === typeof val) {
if(isModelClass(val)) {
if(val.settings && val.settings.anonymous) {
return val.definition && val.definition.toJSON().properties;
} else {
return val.modelName;
}
}
return val.name;
} else {
return val;
}
};
for(var p in this.properties) {
json.properties[p] = traverse(this.properties[p]).map(mapper);
}
this.json = json;
ModelDefinition.prototype.toJSON = function (forceRebuild) {
if (forceRebuild) {
this.json = null;
}
if (this.json) {
return json;
}
var json = {
name: this.name,
properties: {},
settings: this.settings
};
this.build(forceRebuild);
var mapper = function (val) {
if (val === undefined || val === null) {
return val;
}
if ('function' === typeof val.toJSON) {
// The value has its own toJSON() object
return val.toJSON();
}
if ('function' === typeof val) {
if (isModelClass(val)) {
if (val.settings && val.settings.anonymous) {
return val.definition && val.definition.toJSON().properties;
} else {
return val.modelName;
}
}
return val.name;
} else {
return val;
}
};
for (var p in this.properties) {
json.properties[p] = traverse(this.properties[p]).map(mapper);
}
this.json = json;
return json;
};

View File

@ -6,7 +6,7 @@ module.exports = ModelBaseClass;
/**
* Module dependencies
*/
var util = require('util');
var traverse = require('traverse');
var jutil = require('./jutil');
@ -28,19 +28,19 @@ var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text'];
* @param {Object} data - initial object data
*/
function ModelBaseClass(data) {
this._initProperties(data, true);
this._initProperties(data, true);
}
// FIXME: [rfeng] We need to make sure the input data should not be mutated. Disabled cloning for now to get tests passing
function clone(data) {
/*
if(!(data instanceof ModelBaseClass)) {
if(data && (Array.isArray(data) || 'object' === typeof data)) {
return traverse(data).clone();
}
}
*/
return data;
/*
if(!(data instanceof ModelBaseClass)) {
if(data && (Array.isArray(data) || 'object' === typeof data)) {
return traverse(data).clone();
}
}
*/
return data;
}
/**
* Initialize properties
@ -49,117 +49,117 @@ function clone(data) {
* @private
*/
ModelBaseClass.prototype._initProperties = function (data, applySetters) {
var self = this;
var ctor = this.constructor;
var properties = ctor.definition.build();
data = data || {};
var self = this;
var ctor = this.constructor;
Object.defineProperty(this, '__cachedRelations', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
var properties = ctor.definition.build();
data = data || {};
Object.defineProperty(this, '__data', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
Object.defineProperty(this, '__cachedRelations', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
Object.defineProperty(this, '__dataWas', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
Object.defineProperty(this, '__data', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
if (data['__cachedRelations']) {
this.__cachedRelations = data['__cachedRelations'];
Object.defineProperty(this, '__dataWas', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
if (data['__cachedRelations']) {
this.__cachedRelations = data['__cachedRelations'];
}
// Check if the strict option is set to false for the model
var strict = ctor.definition.settings.strict;
for (var i in data) {
if (i in properties) {
this.__data[i] = this.__dataWas[i] = clone(data[i]);
} else if (i in ctor.relations) {
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo];
this.__cachedRelations[i] = data[i];
} else {
if (strict === false) {
this.__data[i] = this.__dataWas[i] = clone(data[i]);
} else if (strict === 'throw') {
throw new Error('Unknown property: ' + i);
}
}
}
if (applySetters === true) {
for (var propertyName in data) {
if ((propertyName in properties) || (propertyName in ctor.relations)) {
self[propertyName] = self.__data[propertyName] || data[propertyName];
}
}
}
// Set the unknown properties as properties to the object
if (strict === false) {
for (var propertyName in data) {
if (!(propertyName in properties)) {
self[propertyName] = self.__data[propertyName] || data[propertyName];
}
}
}
ctor.forEachProperty(function (propertyName) {
if ('undefined' === typeof self.__data[propertyName]) {
self.__data[propertyName] = self.__dataWas[propertyName] = getDefault(propertyName);
} else {
self.__dataWas[propertyName] = self.__data[propertyName];
}
// Check if the strict option is set to false for the model
var strict = ctor.definition.settings.strict;
});
for (var i in data) {
if (i in properties) {
this.__data[i] = this.__dataWas[i] = clone(data[i]);
} else if (i in ctor.relations) {
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo];
this.__cachedRelations[i] = data[i];
} else {
if(strict === false) {
this.__data[i] = this.__dataWas[i] = clone(data[i]);
} else if(strict === 'throw') {
throw new Error('Unknown property: ' + i);
}
ctor.forEachProperty(function (propertyName) {
var type = properties[propertyName].type;
if (BASE_TYPES.indexOf(type.name) === -1) {
if (typeof self.__data[propertyName] !== 'object' && self.__data[propertyName]) {
try {
self.__data[propertyName] = JSON.parse(self.__data[propertyName] + '');
} catch (e) {
self.__data[propertyName] = String(self.__data[propertyName]);
}
}
if (type.name === 'Array' || Array.isArray(type)) {
if (!(self.__data[propertyName] instanceof List)) {
self.__data[propertyName] = new List(self.__data[propertyName], type, self);
}
}
}
if (applySetters === true) {
for(var propertyName in data) {
if((propertyName in properties) || (propertyName in ctor.relations)) {
self[propertyName] = self.__data[propertyName] || data[propertyName];
}
}
});
function getDefault(propertyName) {
var def = properties[propertyName]['default'];
if (def !== undefined) {
if (typeof def === 'function') {
return def();
} else {
return def;
}
} else {
return undefined;
}
}
// Set the unknown properties as properties to the object
if(strict === false) {
for(var propertyName in data) {
if(!(propertyName in properties)) {
self[propertyName] = self.__data[propertyName] || data[propertyName];
}
}
}
ctor.forEachProperty(function (propertyName) {
if ('undefined' === typeof self.__data[propertyName]) {
self.__data[propertyName] = self.__dataWas[propertyName] = getDefault(propertyName);
} else {
self.__dataWas[propertyName] = self.__data[propertyName];
}
});
ctor.forEachProperty(function (propertyName) {
var type = properties[propertyName].type;
if (BASE_TYPES.indexOf(type.name) === -1) {
if (typeof self.__data[propertyName] !== 'object' && self.__data[propertyName]) {
try {
self.__data[propertyName] = JSON.parse(self.__data[propertyName] + '');
} catch (e) {
self.__data[propertyName] = String(self.__data[propertyName]);
}
}
if (type.name === 'Array' || Array.isArray(type)) {
if(!(self.__data[propertyName] instanceof List)) {
self.__data[propertyName] = new List(self.__data[propertyName], type, self);
}
}
}
});
function getDefault(propertyName) {
var def = properties[propertyName]['default'];
if (def !== undefined) {
if (typeof def === 'function') {
return def();
} else {
return def;
}
} else {
return undefined;
}
}
this.trigger('initialize');
this.trigger('initialize');
}
/**
@ -167,24 +167,24 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
* @param {Object} params - various property configuration
*/
ModelBaseClass.defineProperty = function (prop, params) {
this.dataSource.defineProperty(this.modelName, prop, params);
this.dataSource.defineProperty(this.modelName, prop, params);
};
ModelBaseClass.getPropertyType = function (propName) {
var prop = this.definition.properties[propName];
if(!prop) {
// The property is not part of the definition
return null;
}
if (!prop.type) {
throw new Error('Type not defined for property ' + this.modelName + '.' + propName);
// return null;
}
return prop.type.name;
var prop = this.definition.properties[propName];
if (!prop) {
// The property is not part of the definition
return null;
}
if (!prop.type) {
throw new Error('Type not defined for property ' + this.modelName + '.' + propName);
// return null;
}
return prop.type.name;
};
ModelBaseClass.prototype.getPropertyType = function (propName) {
return this.constructor.getPropertyType(propName);
return this.constructor.getPropertyType(propName);
};
/**
@ -193,7 +193,7 @@ ModelBaseClass.prototype.getPropertyType = function (propName) {
* @override default toString method
*/
ModelBaseClass.toString = function () {
return '[Model ' + this.modelName + ']';
return '[Model ' + this.modelName + ']';
};
/**
@ -205,37 +205,37 @@ ModelBaseClass.toString = function () {
* @returns {Object} - canonical object representation (no getters and setters)
*/
ModelBaseClass.prototype.toObject = function (onlySchema) {
var data = {};
var self = this;
var data = {};
var self = this;
var schemaLess = this.constructor.definition.settings.strict === false || !onlySchema;
this.constructor.forEachProperty(function (propertyName) {
if (self[propertyName] instanceof List) {
data[propertyName] = self[propertyName].toObject(!schemaLess);
} else if (self.__data.hasOwnProperty(propertyName)) {
if(self[propertyName] !== undefined && self[propertyName]!== null && self[propertyName].toObject) {
data[propertyName] = self[propertyName].toObject(!schemaLess);
} else {
data[propertyName] = self[propertyName];
}
} else {
data[propertyName] = null;
}
});
if (schemaLess) {
for(var propertyName in self.__data) {
if (!data.hasOwnProperty(propertyName)) {
var val = self.hasOwnProperty(propertyName) ? self[propertyName] : self.__data[propertyName];
if(val !== undefined && val!== null && val.toObject) {
data[propertyName] = val.toObject(!schemaLess);
} else {
data[propertyName] = val;
}
}
}
var schemaLess = this.constructor.definition.settings.strict === false || !onlySchema;
this.constructor.forEachProperty(function (propertyName) {
if (self[propertyName] instanceof List) {
data[propertyName] = self[propertyName].toObject(!schemaLess);
} else if (self.__data.hasOwnProperty(propertyName)) {
if (self[propertyName] !== undefined && self[propertyName] !== null && self[propertyName].toObject) {
data[propertyName] = self[propertyName].toObject(!schemaLess);
} else {
data[propertyName] = self[propertyName];
}
} else {
data[propertyName] = null;
}
return data;
});
if (schemaLess) {
for (var propertyName in self.__data) {
if (!data.hasOwnProperty(propertyName)) {
var val = self.hasOwnProperty(propertyName) ? self[propertyName] : self.__data[propertyName];
if (val !== undefined && val !== null && val.toObject) {
data[propertyName] = val.toObject(!schemaLess);
} else {
data[propertyName] = val;
}
}
}
}
return data;
};
// ModelBaseClass.prototype.hasOwnProperty = function (prop) {
@ -244,13 +244,13 @@ ModelBaseClass.prototype.toObject = function (onlySchema) {
// };
ModelBaseClass.prototype.toJSON = function () {
return this.toObject();
return this.toObject();
};
ModelBaseClass.prototype.fromObject = function (obj) {
for(var key in obj) {
this[key] = obj[key];
}
for (var key in obj) {
this[key] = obj[key];
}
};
/**
@ -260,7 +260,7 @@ ModelBaseClass.prototype.fromObject = function (obj) {
* @return Boolean
*/
ModelBaseClass.prototype.propertyChanged = function propertyChanged(propertyName) {
return this.__data[propertyName] !== this.__dataWas[propertyName];
return this.__data[propertyName] !== this.__dataWas[propertyName];
};
/**
@ -270,23 +270,23 @@ ModelBaseClass.prototype.propertyChanged = function propertyChanged(propertyName
* initial state
*/
ModelBaseClass.prototype.reset = function () {
var obj = this;
for(var k in obj) {
if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) {
delete obj[k];
}
if (obj.propertyChanged(k)) {
obj[k] = obj[k + '$was'];
}
var obj = this;
for (var k in obj) {
if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) {
delete obj[k];
}
if (obj.propertyChanged(k)) {
obj[k] = obj[k + '$was'];
}
}
};
ModelBaseClass.prototype.inspect = function () {
return util.inspect(this.__data, false, 4, true);
return util.inspect(this.__data, false, 4, true);
};
ModelBaseClass.mixin = function(anotherClass, options) {
return jutil.mixin(this, anotherClass, options);
ModelBaseClass.mixin = function (anotherClass, options) {
return jutil.mixin(this, anotherClass, options);
};
ModelBaseClass.prototype.getDataSource = function () {

View File

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

View File

@ -5,192 +5,192 @@ exports.defineScope = defineScope;
function defineScope(cls, targetClass, name, params, methods) {
// collect meta info about scope
if (!cls._scopeMeta) {
cls._scopeMeta = {};
}
// collect meta info about scope
if (!cls._scopeMeta) {
cls._scopeMeta = {};
}
// only makes sence to add scope in meta if base and target classes
// are same
if (cls === targetClass) {
cls._scopeMeta[name] = params;
} else {
if (!targetClass._scopeMeta) {
targetClass._scopeMeta = {};
// only makes sence to add scope in meta if base and target classes
// are same
if (cls === targetClass) {
cls._scopeMeta[name] = params;
} else {
if (!targetClass._scopeMeta) {
targetClass._scopeMeta = {};
}
}
// Define a property for the scope
Object.defineProperty(cls, name, {
enumerable: false,
configurable: true,
/**
* This defines a property for the scope. For example, user.accounts or
* User.vips. Please note the cls can be the model class or prototype of
* the model class.
*
* The property value is function. It can be used to query the scope,
* such as user.accounts(condOrRefresh, cb) or User.vips(cb). The value
* can also have child properties for create/build/delete. For example,
* user.accounts.create(act, cb).
*
*/
get: function () {
var f = function caller(condOrRefresh, cb) {
var actualCond = {};
var actualRefresh = false;
var saveOnCache = true;
if (arguments.length === 1) {
cb = condOrRefresh;
} else if (arguments.length === 2) {
if (typeof condOrRefresh === 'boolean') {
actualRefresh = condOrRefresh;
} else {
actualCond = condOrRefresh;
actualRefresh = true;
saveOnCache = false;
}
} else {
throw new Error('Method can be only called with one or two arguments');
}
}
// Define a property for the scope
Object.defineProperty(cls, name, {
enumerable: false,
configurable: true,
/**
* This defines a property for the scope. For example, user.accounts or
* User.vips. Please note the cls can be the model class or prototype of
* the model class.
*
* The property value is function. It can be used to query the scope,
* such as user.accounts(condOrRefresh, cb) or User.vips(cb). The value
* can also have child properties for create/build/delete. For example,
* user.accounts.create(act, cb).
*
*/
get: function () {
var f = function caller(condOrRefresh, cb) {
var actualCond = {};
var actualRefresh = false;
var saveOnCache = true;
if (arguments.length === 1) {
cb = condOrRefresh;
} else if (arguments.length === 2) {
if (typeof condOrRefresh === 'boolean') {
actualRefresh = condOrRefresh;
} else {
actualCond = condOrRefresh;
actualRefresh = true;
saveOnCache = false;
}
} else {
throw new Error('Method can be only called with one or two arguments');
}
if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) {
var self = this;
var params = mergeParams(actualCond, caller._scope);
return targetClass.find(params, function(err, data) {
if (!err && saveOnCache) {
if (!self.__cachedRelations) {
self.__cachedRelations = {};
}
self.__cachedRelations[name] = data;
}
cb(err, data);
});
} else {
cb(null, this.__cachedRelations[name]);
}
};
f._scope = typeof params === 'function' ? params.call(this) : params;
f.build = build;
f.create = create;
f.destroyAll = destroyAll;
for (var i in methods) {
f[i] = methods[i].bind(this);
if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) {
var self = this;
var params = mergeParams(actualCond, caller._scope);
return targetClass.find(params, function (err, data) {
if (!err && saveOnCache) {
if (!self.__cachedRelations) {
self.__cachedRelations = {};
}
self.__cachedRelations[name] = data;
}
cb(err, data);
});
} else {
cb(null, this.__cachedRelations[name]);
}
};
f._scope = typeof params === 'function' ? params.call(this) : params;
// define sub-scopes
Object.keys(targetClass._scopeMeta).forEach(function (name) {
Object.defineProperty(f, name, {
enumerable: false,
get: function () {
mergeParams(f._scope, targetClass._scopeMeta[name]);
return f;
}
});
}.bind(this));
f.build = build;
f.create = create;
f.destroyAll = destroyAll;
for (var i in methods) {
f[i] = methods[i].bind(this);
}
// define sub-scopes
Object.keys(targetClass._scopeMeta).forEach(function (name) {
Object.defineProperty(f, name, {
enumerable: false,
get: function () {
mergeParams(f._scope, targetClass._scopeMeta[name]);
return f;
}
});
// Wrap the property into a function for remoting
var fn = function() {
// primaryObject.scopeName, such as user.accounts
var f = this[name];
// set receiver to be the scope property whose value is a function
f.apply(this[name], arguments);
};
fn.shared = true;
fn.http = {verb: 'get', path: '/' + name};
fn.accepts = {arg: 'where', type: 'object'};
fn.description = 'Fetches ' + name;
fn.returns = {arg: name, type: 'array', root: true};
cls['__get__' + name] = fn;
var fn_create = function() {
var f = this[name].create;
f.apply(this[name], arguments);
};
fn_create.shared = true;
fn_create.http = {verb: 'post', path: '/' + name};
fn_create.accepts = {arg: 'data', type: 'object', http: {source: 'body'}};
fn_create.description = 'Creates ' + name;
fn_create.returns = {arg: 'data', type: 'object', root: true};
cls['__create__' + name] = fn_create;
var fn_delete = function() {
var f = this[name].destroyAll;
f.apply(this[name], arguments);
};
fn_delete.shared = true;
fn_delete.http = {verb: 'delete', path: '/' + name};
fn_delete.description = 'Deletes ' + name;
fn_delete.returns = {arg: 'data', type: 'object', root: true};
cls['__delete__' + name] = fn_delete;
// and it should have create/build methods with binded thisModelNameId param
function build(data) {
return new targetClass(mergeParams(this._scope, {where:data || {}}).where);
}
function create(data, cb) {
if (typeof data === 'function') {
cb = data;
data = {};
}
this.build(data).save(cb);
}
/*
Callback
- The callback will be called after all elements are destroyed
- For every destroy call which results in an error
- If fetching the Elements on which destroyAll is called results in an error
*/
function destroyAll(cb) {
targetClass.find(this._scope, function (err, data) {
if (err) {
cb(err);
} else {
(function loopOfDestruction (data) {
if(data.length > 0) {
data.shift().destroy(function(err) {
if(err && cb) cb(err);
loopOfDestruction(data);
});
} else {
if(cb) cb();
}
}(data));
}
}
});
}.bind(this));
return f;
}
});
// Wrap the property into a function for remoting
var fn = function () {
// primaryObject.scopeName, such as user.accounts
var f = this[name];
// set receiver to be the scope property whose value is a function
f.apply(this[name], arguments);
};
fn.shared = true;
fn.http = {verb: 'get', path: '/' + name};
fn.accepts = {arg: 'where', type: 'object'};
fn.description = 'Fetches ' + name;
fn.returns = {arg: name, type: 'array', root: true};
cls['__get__' + name] = fn;
var fn_create = function () {
var f = this[name].create;
f.apply(this[name], arguments);
};
fn_create.shared = true;
fn_create.http = {verb: 'post', path: '/' + name};
fn_create.accepts = {arg: 'data', type: 'object', http: {source: 'body'}};
fn_create.description = 'Creates ' + name;
fn_create.returns = {arg: 'data', type: 'object', root: true};
cls['__create__' + name] = fn_create;
var fn_delete = function () {
var f = this[name].destroyAll;
f.apply(this[name], arguments);
};
fn_delete.shared = true;
fn_delete.http = {verb: 'delete', path: '/' + name};
fn_delete.description = 'Deletes ' + name;
fn_delete.returns = {arg: 'data', type: 'object', root: true};
cls['__delete__' + name] = fn_delete;
// and it should have create/build methods with binded thisModelNameId param
function build(data) {
return new targetClass(mergeParams(this._scope, {where: data || {}}).where);
}
function create(data, cb) {
if (typeof data === 'function') {
cb = data;
data = {};
}
this.build(data).save(cb);
}
/*
Callback
- The callback will be called after all elements are destroyed
- For every destroy call which results in an error
- If fetching the Elements on which destroyAll is called results in an error
*/
function destroyAll(cb) {
targetClass.find(this._scope, function (err, data) {
if (err) {
cb(err);
} else {
(function loopOfDestruction(data) {
if (data.length > 0) {
data.shift().destroy(function (err) {
if (err && cb) cb(err);
loopOfDestruction(data);
});
} else {
if (cb) cb();
}
}(data));
}
});
}
function mergeParams(base, update) {
base = base || {};
if (update.where) {
base.where = merge(base.where, update.where);
}
if (update.include) {
base.include = update.include;
}
if (update.collect) {
base.collect = update.collect;
}
function mergeParams(base, update) {
base = base || {};
if (update.where) {
base.where = merge(base.where, update.where);
}
if (update.include) {
base.include = update.include;
}
if (update.collect) {
base.collect = update.collect;
}
// overwrite order
if (update.order) {
base.order = update.order;
}
return base;
// overwrite order
if (update.order) {
base.order = update.order;
}
return base;
}
}
/**
@ -200,12 +200,12 @@ function defineScope(cls, targetClass, name, params, methods) {
* @returns {Object} `base`
*/
function merge(base, update) {
base = base || {};
if (update) {
Object.keys(update).forEach(function (key) {
base[key] = update[key];
});
}
return base;
base = base || {};
if (update) {
Object.keys(update).forEach(function (key) {
base[key] = update[key];
});
}
return base;
}

View File

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

View File

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

View File

@ -8,54 +8,56 @@ exports.mergeSettings = mergeSettings;
var traverse = require('traverse');
function safeRequire(module) {
try {
return require(module);
} catch (e) {
console.log('Run "npm install loopback-datasource-juggler ' + module + '" command to use loopback-datasource-juggler using ' + module + ' database engine');
process.exit(1);
}
try {
return require(module);
} catch (e) {
console.log('Run "npm install loopback-datasource-juggler ' + module
+ '" command to use loopback-datasource-juggler using ' + module
+ ' database engine');
process.exit(1);
}
}
function fieldsToArray(fields, properties) {
if(!fields) return;
// include all properties by default
var result = properties;
if(typeof fields === 'string') {
return [fields];
if (!fields) return;
// include all properties by default
var result = properties;
if (typeof fields === 'string') {
return [fields];
}
if (Array.isArray(fields) && fields.length > 0) {
// No empty array, including all the fields
return fields;
}
if ('object' === typeof fields) {
// { field1: boolean, field2: boolean ... }
var included = [];
var excluded = [];
var keys = Object.keys(fields);
if (!keys.length) return;
keys.forEach(function (k) {
if (fields[k]) {
included.push(k);
} else if ((k in fields) && !fields[k]) {
excluded.push(k);
}
});
if (included.length > 0) {
result = included;
} else if (excluded.length > 0) {
excluded.forEach(function (e) {
var index = result.indexOf(e);
result.splice(index, 1);
});
}
if (Array.isArray(fields) && fields.length > 0) {
// No empty array, including all the fields
return fields;
}
if ('object' === typeof fields) {
// { field1: boolean, field2: boolean ... }
var included = [];
var excluded = [];
var keys = Object.keys(fields);
if(!keys.length) return;
keys.forEach(function (k) {
if (fields[k]) {
included.push(k);
} else if ((k in fields) && !fields[k]) {
excluded.push(k);
}
});
if (included.length > 0) {
result = included;
} else if (excluded.length > 0) {
excluded.forEach(function (e) {
var index = result.indexOf(e);
result.splice(index, 1);
});
}
}
return result;
}
return result;
}
function selectFields(fields) {
@ -63,10 +65,10 @@ function selectFields(fields) {
return function (obj) {
var result = {};
var key;
for (var i = 0; i < fields.length; i++) {
key = fields[i];
result[key] = obj[key];
}
return result;
@ -79,24 +81,25 @@ function selectFields(fields) {
* @returns {exports.map|*}
*/
function removeUndefined(query) {
if (typeof query !== 'object' || query === null) {
return query;
if (typeof query !== 'object' || query === null) {
return query;
}
// WARNING: [rfeng] Use map() will cause mongodb to produce invalid BSON
// as traverse doesn't transform the ObjectId correctly
return traverse(query).forEach(function (x) {
if (x === undefined) {
this.remove();
}
// WARNING: [rfeng] Use map() will cause mongodb to produce invalid BSON
// as traverse doesn't transform the ObjectId correctly
return traverse(query).forEach(function (x) {
if (x === undefined) {
this.remove();
}
if (!Array.isArray(x) && (typeof x === 'object' && x !== null && x.constructor !== Object)) {
// This object is not a plain object
this.update(x, true); // Stop navigating into this object
return x;
}
if (!Array.isArray(x) && (typeof x === 'object' && x !== null
&& x.constructor !== Object)) {
// This object is not a plain object
this.update(x, true); // Stop navigating into this object
return x;
}
return x;
});
return x;
});
}
var url = require('url');
@ -108,25 +111,25 @@ var qs = require('qs');
* @returns {Object} The settings object
*/
function parseSettings(urlStr) {
if(!urlStr) {
return {};
if (!urlStr) {
return {};
}
var uri = url.parse(urlStr, false);
var settings = {};
settings.connector = uri.protocol && uri.protocol.split(':')[0]; // Remove the trailing :
settings.host = settings.hostname = uri.hostname;
settings.port = uri.port && Number(uri.port); // port is a string
settings.user = settings.username = uri.auth && uri.auth.split(':')[0]; // <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 = {};
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;
}
return settings;
}
/**

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "loopback-datasource-juggler",
"version": "1.2.12",
"version": "1.2.13",
"description": "LoopBack DataSoure Juggler",
"keywords": [
"StrongLoop",

View File

@ -2,347 +2,345 @@
var should = require('./init.js');
var db, User;
describe('basic-querying', function() {
describe('basic-querying', function () {
before(function(done) {
db = getSchema();
User = db.define('User', {
name: {type: String, index: true, sort: true},
email: {type: String, index: true},
role: {type: String, index: true},
order: {type: Number, index: true, sort: true}
});
db.automigrate(done);
before(function (done) {
db = getSchema();
User = db.define('User', {
name: {type: String, index: true, sort: true},
email: {type: String, index: true},
role: {type: String, index: true},
order: {type: Number, index: true, sort: true}
});
db.automigrate(done);
describe('findById', function() {
});
before(function(done) {
User.destroyAll(done);
});
it('should query by id: not found', function(done) {
User.findById(1, function(err, u) {
should.not.exist(u);
should.not.exist(err);
done();
});
});
it('should query by id: found', function(done) {
User.create(function(err, u) {
should.not.exist(err);
should.exist(u.id);
User.findById(u.id, function(err, u) {
should.exist(u);
should.not.exist(err);
u.should.be.an.instanceOf(User);
done();
});
});
});
describe('findById', function () {
before(function (done) {
User.destroyAll(done);
});
describe('find', function() {
it('should query by id: not found', function (done) {
User.findById(1, function (err, u) {
should.not.exist(u);
should.not.exist(err);
done();
});
});
before(seed);
it('should query by id: found', function (done) {
User.create(function (err, u) {
should.not.exist(err);
should.exist(u.id);
User.findById(u.id, function (err, u) {
should.exist(u);
should.not.exist(err);
u.should.be.an.instanceOf(User);
done();
});
});
});
it('should query collection', function(done) {
User.find(function(err, users) {
should.exists(users);
should.not.exists(err);
users.should.have.lengthOf(6);
done();
});
});
it('should query limited collection', function(done) {
User.find({limit: 3}, function(err, users) {
should.exists(users);
should.not.exists(err);
users.should.have.lengthOf(3);
done();
});
});
it('should query offset collection with limit', function(done) {
User.find({skip: 1, limit: 4}, function(err, users) {
should.exists(users);
should.not.exists(err);
users.should.have.lengthOf(4);
done();
});
});
});
it('should query filtered collection', function(done) {
User.find({where: {role: 'lead'}}, function(err, users) {
should.exists(users);
should.not.exists(err);
users.should.have.lengthOf(2);
done();
});
});
describe('find', function () {
it('should query collection sorted by numeric field', function(done) {
User.find({order: 'order'}, function(err, users) {
should.exists(users);
should.not.exists(err);
users.forEach(function(u, i) {
u.order.should.eql(i + 1);
});
done();
});
});
before(seed);
it('should query collection desc sorted by numeric field', function(done) {
User.find({order: 'order DESC'}, function(err, users) {
should.exists(users);
should.not.exists(err);
users.forEach(function(u, i) {
u.order.should.eql(users.length - i);
});
done();
});
});
it('should query collection', function (done) {
User.find(function (err, users) {
should.exists(users);
should.not.exists(err);
users.should.have.lengthOf(6);
done();
});
});
it('should query collection sorted by string field', function(done) {
User.find({order: 'name'}, function(err, users) {
should.exists(users);
should.not.exists(err);
users.shift().name.should.equal('George Harrison');
users.shift().name.should.equal('John Lennon');
users.pop().name.should.equal('Stuart Sutcliffe');
done();
});
});
it('should query limited collection', function (done) {
User.find({limit: 3}, function (err, users) {
should.exists(users);
should.not.exists(err);
users.should.have.lengthOf(3);
done();
});
});
it('should query collection desc sorted by string field', function(done) {
User.find({order: 'name DESC'}, function(err, users) {
should.exists(users);
should.not.exists(err);
users.pop().name.should.equal('George Harrison');
users.pop().name.should.equal('John Lennon');
users.shift().name.should.equal('Stuart Sutcliffe');
done();
});
it('should query offset collection with limit', function (done) {
User.find({skip: 1, limit: 4}, function (err, users) {
should.exists(users);
should.not.exists(err);
users.should.have.lengthOf(4);
done();
});
});
it('should query filtered collection', function (done) {
User.find({where: {role: 'lead'}}, function (err, users) {
should.exists(users);
should.not.exists(err);
users.should.have.lengthOf(2);
done();
});
});
it('should query collection sorted by numeric field', function (done) {
User.find({order: 'order'}, function (err, users) {
should.exists(users);
should.not.exists(err);
users.forEach(function (u, i) {
u.order.should.eql(i + 1);
});
it('should only include fields as specified', function(done) {
var remaining = 0;
function sample(fields) {
return {
expect: function (arr) {
remaining++;
User.find({fields: fields}, function(err, users) {
remaining--;
if(err) return done(err);
should.exists(users);
if(remaining === 0) {
done();
}
users.forEach(function (user) {
var obj = user.toObject();
Object.keys(obj)
.forEach(function (key) {
// if the obj has an unexpected value
if(obj[key] !== undefined && arr.indexOf(key) === -1) {
console.log('Given fields:', fields);
console.log('Got:', key, obj[key]);
console.log('Expected:', arr);
throw new Error('should not include data for key: '+ key);
}
});
});
});
}
done();
});
});
it('should query collection desc sorted by numeric field', function (done) {
User.find({order: 'order DESC'}, function (err, users) {
should.exists(users);
should.not.exists(err);
users.forEach(function (u, i) {
u.order.should.eql(users.length - i);
});
done();
});
});
it('should query collection sorted by string field', function (done) {
User.find({order: 'name'}, function (err, users) {
should.exists(users);
should.not.exists(err);
users.shift().name.should.equal('George Harrison');
users.shift().name.should.equal('John Lennon');
users.pop().name.should.equal('Stuart Sutcliffe');
done();
});
});
it('should query collection desc sorted by string field', function (done) {
User.find({order: 'name DESC'}, function (err, users) {
should.exists(users);
should.not.exists(err);
users.pop().name.should.equal('George Harrison');
users.pop().name.should.equal('John Lennon');
users.shift().name.should.equal('Stuart Sutcliffe');
done();
});
});
it('should only include fields as specified', function (done) {
var remaining = 0;
function sample(fields) {
return {
expect: function (arr) {
remaining++;
User.find({fields: fields}, function (err, users) {
remaining--;
if (err) return done(err);
should.exists(users);
if (remaining === 0) {
done();
}
}
sample({name: true}).expect(['name']);
sample({name: false}).expect(['id', 'email', 'role', 'order']);
sample({name: false, id: true}).expect(['id']);
sample({id: true}).expect(['id']);
sample('id').expect(['id']);
sample(['id']).expect(['id']);
sample(['email']).expect(['email']);
});
users.forEach(function (user) {
var obj = user.toObject();
Object.keys(obj)
.forEach(function (key) {
// if the obj has an unexpected value
if (obj[key] !== undefined && arr.indexOf(key) === -1) {
console.log('Given fields:', fields);
console.log('Got:', key, obj[key]);
console.log('Expected:', arr);
throw new Error('should not include data for key: ' + key);
}
});
});
});
}
}
}
sample({name: true}).expect(['name']);
sample({name: false}).expect(['id', 'email', 'role', 'order']);
sample({name: false, id: true}).expect(['id']);
sample({id: true}).expect(['id']);
sample('id').expect(['id']);
sample(['id']).expect(['id']);
sample(['email']).expect(['email']);
});
describe('count', function() {
});
before(seed);
describe('count', function () {
it('should query total count', function(done) {
User.count(function(err, n) {
should.not.exist(err);
should.exist(n);
n.should.equal(6);
done();
});
});
before(seed);
it('should query filtered count', function(done) {
User.count({role: 'lead'}, function(err, n) {
should.not.exist(err);
should.exist(n);
n.should.equal(2);
done();
});
});
it('should query total count', function (done) {
User.count(function (err, n) {
should.not.exist(err);
should.exist(n);
n.should.equal(6);
done();
});
});
describe('findOne', function() {
it('should query filtered count', function (done) {
User.count({role: 'lead'}, function (err, n) {
should.not.exist(err);
should.exist(n);
n.should.equal(2);
done();
});
});
});
before(seed);
describe('findOne', function () {
it('should find first record (default sort by id)', function(done) {
User.all({order: 'id'}, function(err, users) {
User.findOne(function(e, u) {
should.not.exist(e);
should.exist(u);
u.id.toString().should.equal(users[0].id.toString());
done();
});
});
before(seed);
it('should find first record (default sort by id)', function (done) {
User.all({order: 'id'}, function (err, users) {
User.findOne(function (e, u) {
should.not.exist(e);
should.exist(u);
u.id.toString().should.equal(users[0].id.toString());
done();
});
it('should find first record', function(done) {
User.findOne({order: 'order'}, function(e, u) {
should.not.exist(e);
should.exist(u);
u.order.should.equal(1);
u.name.should.equal('Paul McCartney');
done();
});
});
it('should find last record', function(done) {
User.findOne({order: 'order DESC'}, function(e, u) {
should.not.exist(e);
should.exist(u);
u.order.should.equal(6);
u.name.should.equal('Ringo Starr');
done();
});
});
it('should find last record in filtered set', function(done) {
User.findOne({
where: {role: 'lead'},
order: 'order DESC'
}, function(e, u) {
should.not.exist(e);
should.exist(u);
u.order.should.equal(2);
u.name.should.equal('John Lennon');
done();
});
});
it('should work even when find by id', function(done) {
User.findOne(function(e, u) {
User.findOne({where: {id: u.id}}, function(err, user) {
should.not.exist(err);
should.exist(user);
done();
});
});
});
});
});
describe('exists', function() {
before(seed);
it('should check whether record exist', function(done) {
User.findOne(function(e, u) {
User.exists(u.id, function(err, exists) {
should.not.exist(err);
should.exist(exists);
exists.should.be.ok;
done();
});
});
});
it('should check whether record not exist', function(done) {
User.destroyAll(function() {
User.exists(42, function(err, exists) {
should.not.exist(err);
exists.should.not.be.ok;
done();
});
});
});
it('should find first record', function (done) {
User.findOne({order: 'order'}, function (e, u) {
should.not.exist(e);
should.exist(u);
u.order.should.equal(1);
u.name.should.equal('Paul McCartney');
done();
});
});
describe('destroyAll with where option', function() {
before(seed);
it('should only delete instances that satisfy the where condition', function(done) {
User.destroyAll({name: 'John Lennon'}, function() {
User.find({where: {name: 'John Lennon'}}, function(err, data) {
should.not.exist(err);
data.length.should.equal(0);
User.find({where: {name: 'Paul McCartney'}}, function(err, data) {
should.not.exist(err);
data.length.should.equal(1);
done();
});
});
});
});
it('should find last record', function (done) {
User.findOne({order: 'order DESC'}, function (e, u) {
should.not.exist(e);
should.exist(u);
u.order.should.equal(6);
u.name.should.equal('Ringo Starr');
done();
});
});
it('should find last record in filtered set', function (done) {
User.findOne({
where: {role: 'lead'},
order: 'order DESC'
}, function (e, u) {
should.not.exist(e);
should.exist(u);
u.order.should.equal(2);
u.name.should.equal('John Lennon');
done();
});
});
it('should work even when find by id', function (done) {
User.findOne(function (e, u) {
User.findOne({where: {id: u.id}}, function (err, user) {
should.not.exist(err);
should.exist(user);
done();
});
});
});
});
describe('exists', function () {
before(seed);
it('should check whether record exist', function (done) {
User.findOne(function (e, u) {
User.exists(u.id, function (err, exists) {
should.not.exist(err);
should.exist(exists);
exists.should.be.ok;
done();
});
});
});
it('should check whether record not exist', function (done) {
User.destroyAll(function () {
User.exists(42, function (err, exists) {
should.not.exist(err);
exists.should.not.be.ok;
done();
});
});
});
});
describe('destroyAll with where option', function () {
before(seed);
it('should only delete instances that satisfy the where condition', function (done) {
User.destroyAll({name: 'John Lennon'}, function () {
User.find({where: {name: 'John Lennon'}}, function (err, data) {
should.not.exist(err);
data.length.should.equal(0);
User.find({where: {name: 'Paul McCartney'}}, function (err, data) {
should.not.exist(err);
data.length.should.equal(1);
done();
});
});
});
});
});
});
function seed(done) {
var count = 0;
var beatles = [
{
name: 'John Lennon',
email: 'john@b3atl3s.co.uk',
role: 'lead',
order: 2
}, {
name: 'Paul McCartney',
email: 'paul@b3atl3s.co.uk',
role: 'lead',
order: 1
},
{name: 'George Harrison', order: 5},
{name: 'Ringo Starr', order: 6},
{name: 'Pete Best', order: 4},
{name: 'Stuart Sutcliffe', order: 3}
];
User.destroyAll(function() {
beatles.forEach(function(beatle) {
User.create(beatle, ok);
});
var count = 0;
var beatles = [
{
name: 'John Lennon',
email: 'john@b3atl3s.co.uk',
role: 'lead',
order: 2
},
{
name: 'Paul McCartney',
email: 'paul@b3atl3s.co.uk',
role: 'lead',
order: 1
},
{name: 'George Harrison', order: 5},
{name: 'Ringo Starr', order: 6},
{name: 'Pete Best', order: 4},
{name: 'Stuart Sutcliffe', order: 3}
];
User.destroyAll(function () {
beatles.forEach(function (beatle) {
User.create(beatle, ok);
});
});
function ok() {
if (++count === beatles.length) {
done();
}
function ok() {
if (++count === beatles.length) {
done();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,64 +3,64 @@ var should = require('./init.js');
var db, Model;
describe('datatypes', function() {
describe('datatypes', function () {
before(function(done){
db = getSchema();
Model = db.define('Model', {
str: String,
date: Date,
num: Number,
bool: Boolean,
list: {type: []},
});
db.automigrate(function() {
Model.destroyAll(done);
});
before(function (done) {
db = getSchema();
Model = db.define('Model', {
str: String,
date: Date,
num: Number,
bool: Boolean,
list: {type: []},
});
db.automigrate(function () {
Model.destroyAll(done);
});
});
it('should keep types when get read data from db', function (done) {
var d = new Date, id;
Model.create({
str: 'hello', date: d, num: '3', bool: 1, list: ['test']
}, function (err, m) {
should.not.exist(err);
should.exist(m && m.id);
m.str.should.be.a('string');
m.num.should.be.a('number');
m.bool.should.be.a('boolean');
id = m.id;
testFind(testAll);
});
it('should keep types when get read data from db', function(done) {
var d = new Date, id;
function testFind(next) {
debugger;
Model.findById(id, function (err, m) {
should.not.exist(err);
should.exist(m);
m.str.should.be.a('string');
m.num.should.be.a('number');
m.bool.should.be.a('boolean');
m.date.should.be.an.instanceOf(Date);
m.date.toString().should.equal(d.toString(), 'Time must match');
next();
});
}
Model.create({
str: 'hello', date: d, num: '3', bool: 1, list: ['test']
}, function(err, m) {
should.not.exist(err);
should.exist(m && m.id);
m.str.should.be.a('string');
m.num.should.be.a('number');
m.bool.should.be.a('boolean');
id = m.id;
testFind(testAll);
});
function testAll() {
Model.findOne(function (err, m) {
should.not.exist(err);
should.exist(m);
m.str.should.be.a('string');
m.num.should.be.a('number');
m.bool.should.be.a('boolean');
m.date.should.be.an.instanceOf(Date);
m.date.toString().should.equal(d.toString(), 'Time must match');
done();
});
}
function testFind(next) {
debugger;
Model.findById(id, function(err, m) {
should.not.exist(err);
should.exist(m);
m.str.should.be.a('string');
m.num.should.be.a('number');
m.bool.should.be.a('boolean');
m.date.should.be.an.instanceOf(Date);
m.date.toString().should.equal(d.toString(), 'Time must match');
next();
});
}
function testAll() {
Model.findOne(function(err, m) {
should.not.exist(err);
should.exist(m);
m.str.should.be.a('string');
m.num.should.be.a('number');
m.bool.should.be.a('boolean');
m.date.should.be.an.instanceOf(Date);
m.date.toString().should.equal(d.toString(), 'Time must match');
done();
});
}
});
});
});

View File

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

View File

@ -2,391 +2,423 @@
var should = require('./init.js');
var j = require('../'),
Schema = j.Schema,
AbstractClass = j.AbstractClass,
Hookable = j.Hookable,
Schema = j.Schema,
AbstractClass = j.AbstractClass,
Hookable = j.Hookable,
db, User;
db, User;
describe('hooks', function() {
describe('hooks', function () {
before(function(done) {
db = getSchema();
before(function (done) {
db = getSchema();
User = db.define('User', {
email: {type: String, index: true},
name: String,
password: String,
state: String
});
db.automigrate(done);
User = db.define('User', {
email: {type: String, index: true},
name: String,
password: String,
state: String
});
describe('initialize', function() {
db.automigrate(done);
});
afterEach(function() {
User.afterInitialize = null;
});
it('should be triggered on new', function(done) {
User.afterInitialize = function() {
done();
};
new User;
});
it('should be triggered on create', function(done) {
var user;
User.afterInitialize = function() {
if (this.name === 'Nickolay') {
this.name += ' Rozental';
}
};
User.create({name: 'Nickolay'}, function(err, u) {
u.id.should.be.ok;
u.name.should.equal('Nickolay Rozental');
done();
});
});
describe('initialize', function () {
afterEach(function () {
User.afterInitialize = null;
});
describe('create', function() {
afterEach(removeHooks('Create'));
it('should be triggered on create', function(done) {
addHooks('Create', done);
User.create();
});
it('should not be triggered on new', function() {
User.beforeCreate = function(next) {
should.fail('This should not be called');
next();
};
var u = new User;
});
it('should be triggered on new+save', function(done) {
addHooks('Create', done);
(new User).save();
});
it('afterCreate should not be triggered on failed create', function(done) {
var old = User.dataSource.connector.create;
User.dataSource.connector.create = function(modelName, id, cb) {
cb(new Error('error'));
}
User.afterCreate = function() {
throw new Error('shouldn\'t be called')
};
User.create(function (err, user) {
User.dataSource.connector.create = old;
done();
});
});
it('should be triggered on new', function (done) {
User.afterInitialize = function () {
done();
};
new User;
});
describe('save', function() {
afterEach(removeHooks('Save'));
it('should be triggered on create', function(done) {
addHooks('Save', done);
User.create();
});
it('should be triggered on new+save', function(done) {
addHooks('Save', done);
(new User).save();
});
it('should be triggered on updateAttributes', function(done) {
User.create(function(err, user) {
addHooks('Save', done);
user.updateAttributes({name: 'Anatoliy'});
});
});
it('should be triggered on save', function(done) {
User.create(function(err, user) {
addHooks('Save', done);
user.name = 'Hamburger';
user.save();
});
});
it('should save full object', function(done) {
User.create(function(err, user) {
User.beforeSave = function(next, data) {
data.should.have.keys('id', 'name', 'email',
'password', 'state')
done();
};
user.save();
});
});
it('should save actual modifications to database', function(done) {
User.beforeSave = function(next, data) {
data.password = 'hash';
next();
};
User.destroyAll(function() {
User.create({
email: 'james.bond@example.com',
password: '53cr3t'
}, function() {
User.findOne({
where: {email: 'james.bond@example.com'}
}, function(err, jb) {
jb.password.should.equal('hash');
done();
});
});
});
});
it('should save actual modifications on updateAttributes', function(done) {
User.beforeSave = function(next, data) {
data.password = 'hash';
next();
};
User.destroyAll(function() {
User.create({
email: 'james.bond@example.com'
}, function(err, u) {
u.updateAttribute('password', 'new password', function(e, u) {
should.not.exist(e);
should.exist(u);
u.password.should.equal('hash');
User.findOne({
where: {email: 'james.bond@example.com'}
}, function(err, jb) {
jb.password.should.equal('hash');
done();
});
});
});
});
});
it('should be triggered on create', function (done) {
var user;
User.afterInitialize = function () {
if (this.name === 'Nickolay') {
this.name += ' Rozental';
}
};
User.create({name: 'Nickolay'}, function (err, u) {
u.id.should.be.ok;
u.name.should.equal('Nickolay Rozental');
done();
});
});
describe('update', function() {
afterEach(removeHooks('Update'));
});
it('should not be triggered on create', function() {
User.beforeUpdate = function(next) {
should.fail('This should not be called');
next();
};
User.create();
});
describe('create', function () {
it('should not be triggered on new+save', function() {
User.beforeUpdate = function(next) {
should.fail('This should not be called');
next();
};
(new User).save();
});
afterEach(removeHooks('Create'));
it('should be triggered on updateAttributes', function(done) {
User.create(function (err, user) {
addHooks('Update', done);
user.updateAttributes({name: 'Anatoliy'});
});
});
it('should be triggered on save', function(done) {
User.create(function (err, user) {
addHooks('Update', done);
user.name = 'Hamburger';
user.save();
});
});
it('should update limited set of fields', function(done) {
User.create(function (err, user) {
User.beforeUpdate = function(next, data) {
data.should.have.keys('name', 'email');
done();
};
user.updateAttributes({name: 1, email: 2});
});
});
it('should not trigger after-hook on failed save', function(done) {
User.afterUpdate = function() {
should.fail('afterUpdate shouldn\'t be called')
};
User.create(function (err, user) {
var save = User.dataSource.connector.save;
User.dataSource.connector.save = function(modelName, id, cb) {
User.dataSource.connector.save = save;
cb(new Error('Error'));
}
user.save(function(err) {
done();
});
});
});
it('should be triggered on create', function (done) {
addHooks('Create', done);
User.create();
});
describe('destroy', function() {
afterEach(removeHooks('Destroy'));
it('should be triggered on destroy', function(done) {
var hook = 'not called';
User.beforeDestroy = function(next) {
hook = 'called';
next();
};
User.afterDestroy = function(next) {
hook.should.eql('called');
next();
};
User.create(function (err, user) {
user.destroy(done);
});
});
it('should not trigger after-hook on failed destroy', function(done) {
var destroy = User.dataSource.connector.destroy;
User.dataSource.connector.destroy = function(modelName, id, cb) {
cb(new Error('error'));
}
User.afterDestroy = function() {
should.fail('afterDestroy shouldn\'t be called')
};
User.create(function (err, user) {
user.destroy(function(err) {
User.dataSource.connector.destroy = destroy;
done();
});
});
});
it('should not be triggered on new', function () {
User.beforeCreate = function (next) {
should.fail('This should not be called');
next();
};
var u = new User;
});
describe('lifecycle', function() {
var life = [], user;
before(function(done) {
User.beforeSave = function(d){life.push('beforeSave'); d();};
User.beforeCreate = function(d){life.push('beforeCreate'); d();};
User.beforeUpdate = function(d){life.push('beforeUpdate'); d();};
User.beforeDestroy = function(d){life.push('beforeDestroy');d();};
User.beforeValidate = function(d){life.push('beforeValidate');d();};
User.afterInitialize= function( ){life.push('afterInitialize'); };
User.afterSave = function(d){life.push('afterSave'); d();};
User.afterCreate = function(d){life.push('afterCreate'); d();};
User.afterUpdate = function(d){life.push('afterUpdate'); d();};
User.afterDestroy = function(d){life.push('afterDestroy'); d();};
User.afterValidate = function(d){life.push('afterValidate');d();};
User.create(function(e, u) {
user = u;
life = [];
done();
});
});
beforeEach(function() {
life = [];
});
it('should describe create sequence', function(done) {
User.create(function() {
life.should.eql([
'afterInitialize',
'beforeValidate',
'afterValidate',
'beforeCreate',
'beforeSave',
'afterSave',
'afterCreate'
]);
done();
});
});
it('should describe new+save sequence', function(done) {
var u = new User;
u.save(function() {
life.should.eql([
'afterInitialize',
'beforeValidate',
'afterValidate',
'beforeCreate',
'beforeSave',
'afterSave',
'afterCreate'
]);
done();
});
});
it('should describe updateAttributes sequence', function(done) {
user.updateAttributes({name: 'Antony'}, function() {
life.should.eql([
'beforeValidate',
'afterValidate',
'beforeSave',
'beforeUpdate',
'afterUpdate',
'afterSave',
]);
done();
});
});
it('should describe isValid sequence', function(done) {
should.not.exist(
user.constructor._validations,
'Expected user to have no validations, but she have');
user.isValid(function(valid) {
valid.should.be.true;
life.should.eql([
'beforeValidate',
'afterValidate'
]);
done();
});
});
it('should describe destroy sequence', function(done) {
user.destroy(function() {
life.should.eql([
'beforeDestroy',
'afterDestroy'
]);
done();
});
});
it('should be triggered on new+save', function (done) {
addHooks('Create', done);
(new User).save();
});
it('afterCreate should not be triggered on failed create', function (done) {
var old = User.dataSource.connector.create;
User.dataSource.connector.create = function (modelName, id, cb) {
cb(new Error('error'));
}
User.afterCreate = function () {
throw new Error('shouldn\'t be called')
};
User.create(function (err, user) {
User.dataSource.connector.create = old;
done();
});
});
});
describe('save', function () {
afterEach(removeHooks('Save'));
it('should be triggered on create', function (done) {
addHooks('Save', done);
User.create();
});
it('should be triggered on new+save', function (done) {
addHooks('Save', done);
(new User).save();
});
it('should be triggered on updateAttributes', function (done) {
User.create(function (err, user) {
addHooks('Save', done);
user.updateAttributes({name: 'Anatoliy'});
});
});
it('should be triggered on save', function (done) {
User.create(function (err, user) {
addHooks('Save', done);
user.name = 'Hamburger';
user.save();
});
});
it('should save full object', function (done) {
User.create(function (err, user) {
User.beforeSave = function (next, data) {
data.should.have.keys('id', 'name', 'email',
'password', 'state')
done();
};
user.save();
});
});
it('should save actual modifications to database', function (done) {
User.beforeSave = function (next, data) {
data.password = 'hash';
next();
};
User.destroyAll(function () {
User.create({
email: 'james.bond@example.com',
password: '53cr3t'
}, function () {
User.findOne({
where: {email: 'james.bond@example.com'}
}, function (err, jb) {
jb.password.should.equal('hash');
done();
});
});
});
});
it('should save actual modifications on updateAttributes', function (done) {
User.beforeSave = function (next, data) {
data.password = 'hash';
next();
};
User.destroyAll(function () {
User.create({
email: 'james.bond@example.com'
}, function (err, u) {
u.updateAttribute('password', 'new password', function (e, u) {
should.not.exist(e);
should.exist(u);
u.password.should.equal('hash');
User.findOne({
where: {email: 'james.bond@example.com'}
}, function (err, jb) {
jb.password.should.equal('hash');
done();
});
});
});
});
});
});
describe('update', function () {
afterEach(removeHooks('Update'));
it('should not be triggered on create', function () {
User.beforeUpdate = function (next) {
should.fail('This should not be called');
next();
};
User.create();
});
it('should not be triggered on new+save', function () {
User.beforeUpdate = function (next) {
should.fail('This should not be called');
next();
};
(new User).save();
});
it('should be triggered on updateAttributes', function (done) {
User.create(function (err, user) {
addHooks('Update', done);
user.updateAttributes({name: 'Anatoliy'});
});
});
it('should be triggered on save', function (done) {
User.create(function (err, user) {
addHooks('Update', done);
user.name = 'Hamburger';
user.save();
});
});
it('should update limited set of fields', function (done) {
User.create(function (err, user) {
User.beforeUpdate = function (next, data) {
data.should.have.keys('name', 'email');
done();
};
user.updateAttributes({name: 1, email: 2});
});
});
it('should not trigger after-hook on failed save', function (done) {
User.afterUpdate = function () {
should.fail('afterUpdate shouldn\'t be called')
};
User.create(function (err, user) {
var save = User.dataSource.connector.save;
User.dataSource.connector.save = function (modelName, id, cb) {
User.dataSource.connector.save = save;
cb(new Error('Error'));
}
user.save(function (err) {
done();
});
});
});
});
describe('destroy', function () {
afterEach(removeHooks('Destroy'));
it('should be triggered on destroy', function (done) {
var hook = 'not called';
User.beforeDestroy = function (next) {
hook = 'called';
next();
};
User.afterDestroy = function (next) {
hook.should.eql('called');
next();
};
User.create(function (err, user) {
user.destroy(done);
});
});
it('should not trigger after-hook on failed destroy', function (done) {
var destroy = User.dataSource.connector.destroy;
User.dataSource.connector.destroy = function (modelName, id, cb) {
cb(new Error('error'));
}
User.afterDestroy = function () {
should.fail('afterDestroy shouldn\'t be called')
};
User.create(function (err, user) {
user.destroy(function (err) {
User.dataSource.connector.destroy = destroy;
done();
});
});
});
});
describe('lifecycle', function () {
var life = [], user;
before(function (done) {
User.beforeSave = function (d) {
life.push('beforeSave');
d();
};
User.beforeCreate = function (d) {
life.push('beforeCreate');
d();
};
User.beforeUpdate = function (d) {
life.push('beforeUpdate');
d();
};
User.beforeDestroy = function (d) {
life.push('beforeDestroy');
d();
};
User.beforeValidate = function (d) {
life.push('beforeValidate');
d();
};
User.afterInitialize = function () {
life.push('afterInitialize');
};
User.afterSave = function (d) {
life.push('afterSave');
d();
};
User.afterCreate = function (d) {
life.push('afterCreate');
d();
};
User.afterUpdate = function (d) {
life.push('afterUpdate');
d();
};
User.afterDestroy = function (d) {
life.push('afterDestroy');
d();
};
User.afterValidate = function (d) {
life.push('afterValidate');
d();
};
User.create(function (e, u) {
user = u;
life = [];
done();
});
});
beforeEach(function () {
life = [];
});
it('should describe create sequence', function (done) {
User.create(function () {
life.should.eql([
'afterInitialize',
'beforeValidate',
'afterValidate',
'beforeCreate',
'beforeSave',
'afterSave',
'afterCreate'
]);
done();
});
});
it('should describe new+save sequence', function (done) {
var u = new User;
u.save(function () {
life.should.eql([
'afterInitialize',
'beforeValidate',
'afterValidate',
'beforeCreate',
'beforeSave',
'afterSave',
'afterCreate'
]);
done();
});
});
it('should describe updateAttributes sequence', function (done) {
user.updateAttributes({name: 'Antony'}, function () {
life.should.eql([
'beforeValidate',
'afterValidate',
'beforeSave',
'beforeUpdate',
'afterUpdate',
'afterSave',
]);
done();
});
});
it('should describe isValid sequence', function (done) {
should.not.exist(
user.constructor._validations,
'Expected user to have no validations, but she have');
user.isValid(function (valid) {
valid.should.be.true;
life.should.eql([
'beforeValidate',
'afterValidate'
]);
done();
});
});
it('should describe destroy sequence', function (done) {
user.destroy(function () {
life.should.eql([
'beforeDestroy',
'afterDestroy'
]);
done();
});
});
});
});
function addHooks(name, done) {
var called = false, random = String(Math.floor(Math.random() * 1000));
User['before' + name] = function(next, data) {
called = true;
data.email = random;
next();
};
User['after' + name] = function(next) {
(new Boolean(called)).should.equal(true);
this.email.should.equal(random);
done();
};
var called = false, random = String(Math.floor(Math.random() * 1000));
User['before' + name] = function (next, data) {
called = true;
data.email = random;
next();
};
User['after' + name] = function (next) {
(new Boolean(called)).should.equal(true);
this.email.should.equal(random);
done();
};
}
function removeHooks(name) {
return function() {
User['after' + name] = null;
User['before' + name] = null;
};
return function () {
User['after' + name] = null;
User['before' + name] = null;
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

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 ValidationError = require('..').ValidationError;
describe('manipulation', function() {
describe('manipulation', function () {
before(function(done) {
db = getSchema();
Person = db.define('Person', {
name: String,
gender: String,
married: Boolean,
age: {type: Number, index: true},
dob: Date,
createdAt: {type: Number, default: Date.now}
});
db.automigrate(done);
before(function (done) {
db = getSchema();
Person = db.define('Person', {
name: String,
gender: String,
married: Boolean,
age: {type: Number, index: true},
dob: Date,
createdAt: {type: Number, default: Date.now}
});
describe('create', function() {
db.automigrate(done);
before(function(done) {
Person.destroyAll(done);
});
});
it('should create instance', function(done) {
Person.create({name: 'Anatoliy'}, function(err, p) {
p.name.should.equal('Anatoliy');
should.not.exist(err);
should.exist(p);
Person.findById(p.id, function(err, person) {
person.id.should.equal(p.id);
person.name.should.equal('Anatoliy');
done();
});
});
});
describe('create', function () {
it('should return instance of object', function(done) {
var person = Person.create(function(err, p) {
p.id.should.eql(person.id);
done();
});
should.exist(person);
person.should.be.an.instanceOf(Person);
should.not.exist(person.id);
});
it('should work when called without callback', function(done) {
Person.afterCreate = function(next) {
this.should.be.an.instanceOf(Person);
this.name.should.equal('Nickolay');
should.exist(this.id);
Person.afterCreate = null;
next();
setTimeout(done, 10);
};
Person.create({name: 'Nickolay'});
});
it('should create instance with blank data', function(done) {
Person.create(function(err, p) {
should.not.exist(err);
should.exist(p);
should.not.exists(p.name);
Person.findById(p.id, function(err, person) {
person.id.should.equal(p.id);
should.not.exists(person.name);
done();
});
});
});
it('should work when called with no data and callback', function(done) {
Person.afterCreate = function(next) {
this.should.be.an.instanceOf(Person);
should.not.exist(this.name);
should.exist(this.id);
Person.afterCreate = null;
next();
setTimeout(done, 30);
};
Person.create();
});
it('should create batch of objects', function(done) {
var batch = [{name: 'Shaltay'}, {name: 'Boltay'}, {}];
Person.create(batch, function(e, ps) {
should.not.exist(e);
should.exist(ps);
ps.should.be.instanceOf(Array);
ps.should.have.lengthOf(batch.length);
Person.validatesPresenceOf('name');
Person.create(batch, function(errors, persons) {
delete Person._validations;
should.exist(errors);
errors.should.have.lengthOf(batch.length);
should.not.exist(errors[0]);
should.not.exist(errors[1]);
should.exist(errors[2]);
should.exist(persons);
persons.should.have.lengthOf(batch.length);
persons[0].errors.should.be.false;
done();
}).should.be.instanceOf(Array);
}).should.have.lengthOf(3);
});
before(function (done) {
Person.destroyAll(done);
});
describe('save', function() {
it('should save new object', function(done) {
var p = new Person;
p.save(function(err) {
should.not.exist(err);
should.exist(p.id);
done();
});
it('should create instance', function (done) {
Person.create({name: 'Anatoliy'}, function (err, p) {
p.name.should.equal('Anatoliy');
should.not.exist(err);
should.exist(p);
Person.findById(p.id, function (err, person) {
person.id.should.equal(p.id);
person.name.should.equal('Anatoliy');
done();
});
it('should save existing object', function(done) {
Person.findOne(function(err, p) {
should.not.exist(err);
p.name = 'Hans';
p.propertyChanged('name').should.be.true;
p.save(function(err) {
should.not.exist(err);
p.propertyChanged('name').should.be.false;
Person.findOne(function(err, p) {
should.not.exist(err);
p.name.should.equal('Hans');
p.propertyChanged('name').should.be.false;
done();
});
});
});
});
it('should save invalid object (skipping validation)', function(done) {
Person.findOne(function(err, p) {
should.not.exist(err);
p.isValid = function(done) {
process.nextTick(done);
return false;
};
p.name = 'Nana';
p.save(function(err) {
should.exist(err);
p.propertyChanged('name').should.be.true;
p.save({validate: false}, function(err) {
should.not.exist(err);
p.propertyChanged('name').should.be.false;
done();
});
});
});
});
it('should save throw error on validation', function() {
Person.findOne(function(err, p) {
should.not.exist(err);
p.isValid = function(cb) {
cb(false);
return false;
};
(function() {
p.save({
'throws': true
});
}).should.throw(ValidationError);
});
});
});
});
describe('updateAttributes', function() {
var person;
before(function(done) {
Person.destroyAll(function() {
person = Person.create(done);
});
});
it('should update one attribute', function(done) {
person.updateAttribute('name', 'Paul Graham', function(err, p) {
should.not.exist(err);
Person.all(function(e, ps) {
should.not.exist(err);
ps.should.have.lengthOf(1);
ps.pop().name.should.equal('Paul Graham');
done();
});
});
});
it('should return instance of object', function (done) {
var person = Person.create(function (err, p) {
p.id.should.eql(person.id);
done();
});
should.exist(person);
person.should.be.an.instanceOf(Person);
should.not.exist(person.id);
});
describe('destroy', function() {
it('should destroy record', function(done) {
Person.create(function(err, p){
p.destroy(function(err) {
should.not.exist(err);
Person.exists(p.id, function(err, ex) {
ex.should.not.be.ok;
done();
});
});
});
});
it('should destroy all records', function (done) {
Person.destroyAll(function (err) {
should.not.exist(err);
Person.all(function (err, posts) {
posts.should.have.lengthOf(0);
Person.count(function (err, count) {
count.should.eql(0);
done();
});
});
});
});
// TODO: implement destroy with filtered set
it('should destroy filtered set of records');
it('should work when called without callback', function (done) {
Person.afterCreate = function (next) {
this.should.be.an.instanceOf(Person);
this.name.should.equal('Nickolay');
should.exist(this.id);
Person.afterCreate = null;
next();
setTimeout(done, 10);
};
Person.create({name: 'Nickolay'});
});
describe('initialize', function() {
it('should initialize object properly', function() {
var hw = 'Hello word',
now = Date.now(),
person = new Person({name: hw});
person.name.should.equal(hw);
person.propertyChanged('name').should.be.false;
person.name = 'Goodbye, Lenin';
person.name$was.should.equal(hw);
person.propertyChanged('name').should.be.true;
(person.createdAt >= now).should.be.true;
person.isNewRecord().should.be.true;
it('should create instance with blank data', function (done) {
Person.create(function (err, p) {
should.not.exist(err);
should.exist(p);
should.not.exists(p.name);
Person.findById(p.id, function (err, person) {
person.id.should.equal(p.id);
should.not.exists(person.name);
done();
});
// it('should work when constructor called as function', function() {
// var p = Person({name: 'John Resig'});
// p.should.be.an.instanceOf(Person);
// p.name.should.equal('John Resig');
// });
});
});
it('should work when called with no data and callback', function (done) {
Person.afterCreate = function (next) {
this.should.be.an.instanceOf(Person);
should.not.exist(this.name);
should.exist(this.id);
Person.afterCreate = null;
next();
setTimeout(done, 30);
};
Person.create();
});
it('should create batch of objects', function (done) {
var batch = [
{name: 'Shaltay'},
{name: 'Boltay'},
{}
];
Person.create(batch,function (e, ps) {
should.not.exist(e);
should.exist(ps);
ps.should.be.instanceOf(Array);
ps.should.have.lengthOf(batch.length);
Person.validatesPresenceOf('name');
Person.create(batch,function (errors, persons) {
delete Person._validations;
should.exist(errors);
errors.should.have.lengthOf(batch.length);
should.not.exist(errors[0]);
should.not.exist(errors[1]);
should.exist(errors[2]);
should.exist(persons);
persons.should.have.lengthOf(batch.length);
persons[0].errors.should.be.false;
done();
}).should.be.instanceOf(Array);
}).should.have.lengthOf(3);
});
});
describe('save', function () {
it('should save new object', function (done) {
var p = new Person;
p.save(function (err) {
should.not.exist(err);
should.exist(p.id);
done();
});
});
it('should save existing object', function (done) {
Person.findOne(function (err, p) {
should.not.exist(err);
p.name = 'Hans';
p.propertyChanged('name').should.be.true;
p.save(function (err) {
should.not.exist(err);
p.propertyChanged('name').should.be.false;
Person.findOne(function (err, p) {
should.not.exist(err);
p.name.should.equal('Hans');
p.propertyChanged('name').should.be.false;
done();
});
});
});
});
it('should save invalid object (skipping validation)', function (done) {
Person.findOne(function (err, p) {
should.not.exist(err);
p.isValid = function (done) {
process.nextTick(done);
return false;
};
p.name = 'Nana';
p.save(function (err) {
should.exist(err);
p.propertyChanged('name').should.be.true;
p.save({validate: false}, function (err) {
should.not.exist(err);
p.propertyChanged('name').should.be.false;
done();
});
});
});
});
it('should save throw error on validation', function () {
Person.findOne(function (err, p) {
should.not.exist(err);
p.isValid = function (cb) {
cb(false);
return false;
};
(function () {
p.save({
'throws': true
});
}).should.throw(ValidationError);
});
});
});
describe('updateAttributes', function () {
var person;
before(function (done) {
Person.destroyAll(function () {
person = Person.create(done);
});
});
it('should update one attribute', function (done) {
person.updateAttribute('name', 'Paul Graham', function (err, p) {
should.not.exist(err);
Person.all(function (e, ps) {
should.not.exist(err);
ps.should.have.lengthOf(1);
ps.pop().name.should.equal('Paul Graham');
done();
});
});
});
});
describe('destroy', function () {
it('should destroy record', function (done) {
Person.create(function (err, p) {
p.destroy(function (err) {
should.not.exist(err);
Person.exists(p.id, function (err, ex) {
ex.should.not.be.ok;
done();
});
});
});
});
it('should destroy all records', function (done) {
Person.destroyAll(function (err) {
should.not.exist(err);
Person.all(function (err, posts) {
posts.should.have.lengthOf(0);
Person.count(function (err, count) {
count.should.eql(0);
done();
});
});
});
});
// TODO: implement destroy with filtered set
it('should destroy filtered set of records');
});
describe('initialize', function () {
it('should initialize object properly', function () {
var hw = 'Hello word',
now = Date.now(),
person = new Person({name: hw});
person.name.should.equal(hw);
person.propertyChanged('name').should.be.false;
person.name = 'Goodbye, Lenin';
person.name$was.should.equal(hw);
person.propertyChanged('name').should.be.true;
(person.createdAt >= now).should.be.true;
person.isNewRecord().should.be.true;
});
// it('should work when constructor called as function', function() {
// var p = Person({name: 'John Resig'});
// p.should.be.an.instanceOf(Person);
// p.name.should.equal('John Resig');
// });
});
});

View File

@ -11,243 +11,236 @@ var ModelDefinition = require('../lib/model-definition');
describe('ModelDefinition class', function () {
it('should be able to define plain models', function (done) {
var modelBuilder = new ModelBuilder();
it('should be able to define plain models', function (done) {
var modelBuilder = new ModelBuilder();
var User = new ModelDefinition(modelBuilder, 'User', {
name: "string",
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: "number"
});
var User = new ModelDefinition(modelBuilder, 'User', {
name: "string",
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: "number"
});
User.build();
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
User.build();
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
var json = User.toJSON();
assert.equal(json.name, "User");
assert.equal(json.properties.name.type, "String");
assert.equal(json.properties.bio.type, "Text");
assert.equal(json.properties.approved.type, "Boolean");
assert.equal(json.properties.joinedAt.type, "Date");
assert.equal(json.properties.age.type, "Number");
var json = User.toJSON();
assert.equal(json.name, "User");
assert.equal(json.properties.name.type, "String");
assert.equal(json.properties.bio.type, "Text");
assert.equal(json.properties.approved.type, "Boolean");
assert.equal(json.properties.joinedAt.type, "Date");
assert.equal(json.properties.age.type, "Number");
done();
done();
});
it('should be able to define additional properties', function (done) {
var modelBuilder = new ModelBuilder();
var User = new ModelDefinition(modelBuilder, 'User', {
name: "string",
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: "number"
});
User.build();
User.defineProperty("id", {type: "number", id: true});
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
assert.equal(User.properties.id.type, Number);
done();
});
it('should be able to define nesting models', function (done) {
var modelBuilder = new ModelBuilder();
var User = new ModelDefinition(modelBuilder, 'User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: {
street: String,
city: String,
zipCode: String,
state: String
}
});
User.build();
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
assert.equal(typeof User.properties.address.type, 'function');
var json = User.toJSON();
assert.equal(json.name, "User");
assert.equal(json.properties.name.type, "String");
assert.equal(json.properties.bio.type, "Text");
assert.equal(json.properties.approved.type, "Boolean");
assert.equal(json.properties.joinedAt.type, "Date");
assert.equal(json.properties.age.type, "Number");
assert.deepEqual(json.properties.address.type, { street: { type: 'String' },
city: { type: 'String' },
zipCode: { type: 'String' },
state: { type: 'String' } });
done();
});
it('should be able to define referencing models', function (done) {
var modelBuilder = new ModelBuilder();
var Address = modelBuilder.define('Address', {
street: String,
city: String,
zipCode: String,
state: String
});
var User = new ModelDefinition(modelBuilder, 'User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: Address
});
it('should be able to define additional properties', function (done) {
var modelBuilder = new ModelBuilder();
User.build();
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
assert.equal(User.properties.address.type, Address);
var User = new ModelDefinition(modelBuilder, 'User', {
name: "string",
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: "number"
});
var json = User.toJSON();
assert.equal(json.name, "User");
assert.equal(json.properties.name.type, "String");
assert.equal(json.properties.bio.type, "Text");
assert.equal(json.properties.approved.type, "Boolean");
assert.equal(json.properties.joinedAt.type, "Date");
assert.equal(json.properties.age.type, "Number");
User.build();
assert.equal(json.properties.address.type, 'Address');
User.defineProperty("id", {type: "number", id: true});
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
done();
assert.equal(User.properties.id.type, Number);
done();
});
it('should be able to define referencing models by name', function (done) {
var modelBuilder = new ModelBuilder();
var Address = modelBuilder.define('Address', {
street: String,
city: String,
zipCode: String,
state: String
});
var User = new ModelDefinition(modelBuilder, 'User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: 'Address'
});
User.build();
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
assert.equal(User.properties.address.type, Address);
it('should be able to define nesting models', function (done) {
var modelBuilder = new ModelBuilder();
var json = User.toJSON();
assert.equal(json.name, "User");
assert.equal(json.properties.name.type, "String");
assert.equal(json.properties.bio.type, "Text");
assert.equal(json.properties.approved.type, "Boolean");
assert.equal(json.properties.joinedAt.type, "Date");
assert.equal(json.properties.age.type, "Number");
var User = new ModelDefinition(modelBuilder, 'User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: {
street: String,
city: String,
zipCode: String,
state: String
}
});
assert.equal(json.properties.address.type, 'Address');
User.build();
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
assert.equal(typeof User.properties.address.type, 'function');
done();
var json = User.toJSON();
assert.equal(json.name, "User");
assert.equal(json.properties.name.type, "String");
assert.equal(json.properties.bio.type, "Text");
assert.equal(json.properties.approved.type, "Boolean");
assert.equal(json.properties.joinedAt.type, "Date");
assert.equal(json.properties.age.type, "Number");
});
assert.deepEqual(json.properties.address.type, { street: { type: 'String' },
city: { type: 'String' },
zipCode: { type: 'String' },
state: { type: 'String' } });
done();
it('should report correct id names', function (done) {
var modelBuilder = new ModelBuilder();
var User = new ModelDefinition(modelBuilder, 'User', {
userId: {type: String, id: true},
name: "string",
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: "number"
});
assert.equal(User.idName(), 'userId');
assert.deepEqual(User.idNames(), ['userId']);
done();
});
it('should be able to define referencing models', function (done) {
var modelBuilder = new ModelBuilder();
it('should report correct table/column names', function (done) {
var modelBuilder = new ModelBuilder();
var Address = modelBuilder.define('Address', {
street: String,
city: String,
zipCode: String,
state: String
});
var User = new ModelDefinition(modelBuilder, 'User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: Address
var User = new ModelDefinition(modelBuilder, 'User', {
userId: {type: String, id: true, oracle: {column: 'ID'}},
name: "string"
}, {oracle: {table: 'USER'}});
});
User.build();
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
assert.equal(User.properties.address.type, Address);
var json = User.toJSON();
assert.equal(json.name, "User");
assert.equal(json.properties.name.type, "String");
assert.equal(json.properties.bio.type, "Text");
assert.equal(json.properties.approved.type, "Boolean");
assert.equal(json.properties.joinedAt.type, "Date");
assert.equal(json.properties.age.type, "Number");
assert.equal(json.properties.address.type, 'Address');
done();
assert.equal(User.tableName('oracle'), 'USER');
assert.equal(User.tableName('mysql'), 'User');
assert.equal(User.columnName('oracle', 'userId'), 'ID');
assert.equal(User.columnName('mysql', 'userId'), 'userId');
done();
});
it('should inherit prototype using option.base', function () {
var memory = new DataSource({connector: Memory});
var modelBuilder = memory.modelBuilder;
var parent = memory.createModel('parent', {}, {
relations: {
children: {
type: 'hasMany',
model: 'anotherChild'
}
}
});
var baseChild = modelBuilder.define('baseChild');
baseChild.attachTo(memory);
// the name of this must begin with a letter < b
// for this test to fail
var anotherChild = baseChild.extend('anotherChild');
it('should be able to define referencing models by name', function (done) {
var modelBuilder = new ModelBuilder();
var Address = modelBuilder.define('Address', {
street: String,
city: String,
zipCode: String,
state: String
});
var User = new ModelDefinition(modelBuilder, 'User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: 'Address'
});
User.build();
assert.equal(User.properties.name.type, String);
assert.equal(User.properties.bio.type, ModelBuilder.Text);
assert.equal(User.properties.approved.type, Boolean);
assert.equal(User.properties.joinedAt.type, Date);
assert.equal(User.properties.age.type, Number);
assert.equal(User.properties.address.type, Address);
var json = User.toJSON();
assert.equal(json.name, "User");
assert.equal(json.properties.name.type, "String");
assert.equal(json.properties.bio.type, "Text");
assert.equal(json.properties.approved.type, "Boolean");
assert.equal(json.properties.joinedAt.type, "Date");
assert.equal(json.properties.age.type, "Number");
assert.equal(json.properties.address.type, 'Address');
done();
});
it('should report correct id names', function (done) {
var modelBuilder = new ModelBuilder();
var User = new ModelDefinition(modelBuilder, 'User', {
userId: {type: String, id: true},
name: "string",
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: "number"
});
assert.equal(User.idName(), 'userId');
assert.deepEqual(User.idNames(), ['userId']);
done();
});
it('should report correct table/column names', function (done) {
var modelBuilder = new ModelBuilder();
var User = new ModelDefinition(modelBuilder, 'User', {
userId: {type: String, id: true, oracle: {column: 'ID'}},
name: "string"
}, {oracle: {table: 'USER'}});
assert.equal(User.tableName('oracle'), 'USER');
assert.equal(User.tableName('mysql'), 'User');
assert.equal(User.columnName('oracle', 'userId'), 'ID');
assert.equal(User.columnName('mysql', 'userId'), 'userId');
done();
});
it('should inherit prototype using option.base', function () {
var memory = new DataSource({connector: Memory});
var modelBuilder = memory.modelBuilder;
var parent = memory.createModel('parent', {}, {
relations: {
children: {
type: 'hasMany',
model: 'anotherChild'
}
}
});
var baseChild = modelBuilder.define('baseChild');
baseChild.attachTo(memory);
// the name of this must begin with a letter < b
// for this test to fail
var anotherChild = baseChild.extend('anotherChild');
assert(anotherChild.prototype instanceof baseChild);
});
assert(anotherChild.prototype instanceof baseChild);
});
});

View File

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

View File

@ -3,251 +3,251 @@ var should = require('./init.js');
var db, Book, Chapter, Author, Reader;
describe('relations', function() {
before(function(done) {
db = getSchema();
Book = db.define('Book', {name: String});
Chapter = db.define('Chapter', {name: {type: String, index: true}});
Author = db.define('Author', {name: String});
Reader = db.define('Reader', {name: String});
describe('relations', function () {
before(function (done) {
db = getSchema();
Book = db.define('Book', {name: String});
Chapter = db.define('Chapter', {name: {type: String, index: true}});
Author = db.define('Author', {name: String});
Reader = db.define('Reader', {name: String});
db.automigrate(function() {
Book.destroyAll(function() {
Chapter.destroyAll(function() {
Author.destroyAll(function() {
Reader.destroyAll(done);
});
});
});
db.automigrate(function () {
Book.destroyAll(function () {
Chapter.destroyAll(function () {
Author.destroyAll(function () {
Reader.destroyAll(done);
});
});
});
});
});
after(function () {
db.disconnect();
});
describe('hasMany', function () {
it('can be declared in different ways', function (done) {
Book.hasMany(Chapter);
Book.hasMany(Reader, {as: 'users'});
Book.hasMany(Author, {foreignKey: 'projectId'});
var b = new Book;
b.chapters.should.be.an.instanceOf(Function);
b.users.should.be.an.instanceOf(Function);
b.authors.should.be.an.instanceOf(Function);
Object.keys((new Chapter).toObject()).should.include('bookId');
Object.keys((new Author).toObject()).should.include('projectId');
db.automigrate(done);
});
after(function() {
db.disconnect();
it('can be declared in short form', function (done) {
Author.hasMany('readers');
(new Author).readers.should.be.an.instanceOf(Function);
Object.keys((new Reader).toObject()).should.include('authorId');
db.autoupdate(done);
});
describe('hasMany', function() {
it('can be declared in different ways', function(done) {
Book.hasMany(Chapter);
Book.hasMany(Reader, {as: 'users'});
Book.hasMany(Author, {foreignKey: 'projectId'});
var b = new Book;
b.chapters.should.be.an.instanceOf(Function);
b.users.should.be.an.instanceOf(Function);
b.authors.should.be.an.instanceOf(Function);
Object.keys((new Chapter).toObject()).should.include('bookId');
Object.keys((new Author).toObject()).should.include('projectId');
db.automigrate(done);
});
it('can be declared in short form', function(done) {
Author.hasMany('readers');
(new Author).readers.should.be.an.instanceOf(Function);
Object.keys((new Reader).toObject()).should.include('authorId');
db.autoupdate(done);
});
it('should build record on scope', function(done) {
Book.create(function(err, book) {
var c = book.chapters.build();
c.bookId.should.equal(book.id);
c.save(done);
});
});
it('should create record on scope', function(done) {
Book.create(function(err, book) {
book.chapters.create(function(err, c) {
should.not.exist(err);
should.exist(c);
c.bookId.should.equal(book.id);
done();
});
});
});
it.skip('should fetch all scoped instances', function(done) {
Book.create(function(err, book) {
book.chapters.create({name: 'a'}, function() {
book.chapters.create({name: 'z'}, function() {
book.chapters.create({name: 'c'}, function() {
fetch(book);
});
});
});
});
function fetch(book) {
book.chapters(function(err, ch) {
should.not.exist(err);
should.exist(ch);
ch.should.have.lengthOf(3);
book.chapters({order: 'name DESC'}, function(e, c) {
should.not.exist(e);
should.exist(c);
c.shift().name.should.equal('z');
c.pop().name.should.equal('a');
done();
});
});
}
});
it('should find scoped record', function(done) {
var id;
Book.create(function(err, book) {
book.chapters.create({name: 'a'}, function(err, ch) {
id = ch.id;
book.chapters.create({name: 'z'}, function() {
book.chapters.create({name: 'c'}, function() {
fetch(book);
});
});
});
});
function fetch(book) {
book.chapters.findById(id, function(err, ch) {
should.not.exist(err);
should.exist(ch);
ch.id.should.equal(id);
done();
});
}
});
it('should build record on scope', function (done) {
Book.create(function (err, book) {
var c = book.chapters.build();
c.bookId.should.equal(book.id);
c.save(done);
});
});
describe('belongsTo', function() {
var List, Item, Fear, Mind;
it('can be declared in different ways', function() {
List = db.define('List', {name: String});
Item = db.define('Item', {name: String});
Fear = db.define('Fear');
Mind = db.define('Mind');
// syntax 1 (old)
Item.belongsTo(List);
Object.keys((new Item).toObject()).should.include('listId');
(new Item).list.should.be.an.instanceOf(Function);
// syntax 2 (new)
Fear.belongsTo('mind');
Object.keys((new Fear).toObject()).should.include('mindId');
(new Fear).mind.should.be.an.instanceOf(Function);
// (new Fear).mind.build().should.be.an.instanceOf(Mind);
it('should create record on scope', function (done) {
Book.create(function (err, book) {
book.chapters.create(function (err, c) {
should.not.exist(err);
should.exist(c);
c.bookId.should.equal(book.id);
done();
});
it('can be used to query data', function(done) {
List.hasMany('todos', {model: Item});
db.automigrate(function() {
List.create(function(e, list) {
should.not.exist(e);
should.exist(list);
list.todos.create(function(err, todo) {
todo.list(function(e, l) {
should.not.exist(e);
should.exist(l);
l.should.be.an.instanceOf(List);
todo.list().should.equal(l.id);
done();
});
});
});
});
});
it('could accept objects when creating on scope', function(done) {
List.create(function(e, list) {
should.not.exist(e);
should.exist(list);
Item.create({list: list}, function(err, item) {
should.not.exist(err);
should.exist(item);
should.exist(item.listId);
item.listId.should.equal(list.id);
item.__cachedRelations.list.should.equal(list);
done();
});
});
});
});
});
describe('hasAndBelongsToMany', function() {
var Article, Tag, ArticleTag;
it('can be declared', function(done) {
Article = db.define('Article', {title: String});
Tag = db.define('Tag', {name: String});
Article.hasAndBelongsToMany('tags');
ArticleTag = db.models.ArticleTag;
db.automigrate(function() {
Article.destroyAll(function() {
Tag.destroyAll(function() {
ArticleTag.destroyAll(done)
});
});
it.skip('should fetch all scoped instances', function (done) {
Book.create(function (err, book) {
book.chapters.create({name: 'a'}, function () {
book.chapters.create({name: 'z'}, function () {
book.chapters.create({name: 'c'}, function () {
fetch(book);
});
});
});
});
function fetch(book) {
book.chapters(function (err, ch) {
should.not.exist(err);
should.exist(ch);
ch.should.have.lengthOf(3);
it('should allow to create instances on scope', function(done) {
Article.create(function(e, article) {
article.tags.create({name: 'popular'}, function(e, t) {
t.should.be.an.instanceOf(Tag);
// console.log(t);
ArticleTag.findOne(function(e, at) {
should.exist(at);
at.tagId.toString().should.equal(t.id.toString());
at.articleId.toString().should.equal(article.id.toString());
done();
});
});
});
book.chapters({order: 'name DESC'}, function (e, c) {
should.not.exist(e);
should.exist(c);
c.shift().name.should.equal('z');
c.pop().name.should.equal('a');
done();
});
});
it('should allow to fetch scoped instances', function(done) {
Article.findOne(function(e, article) {
article.tags(function(e, tags) {
should.not.exist(e);
should.exist(tags);
done();
});
});
});
it('should allow to add connection with instance', function(done) {
Article.findOne(function(e, article) {
Tag.create({name: 'awesome'}, function(e, tag) {
article.tags.add(tag, function(e, at) {
should.not.exist(e);
should.exist(at);
at.should.be.an.instanceOf(ArticleTag);
at.tagId.should.equal(tag.id);
at.articleId.should.equal(article.id);
done();
});
});
});
});
it('should allow to remove connection with instance', function(done) {
Article.findOne(function(e, article) {
article.tags(function(e, tags) {
var len = tags.length;
tags.should.not.be.empty;
article.tags.remove(tags[0], function(e) {
should.not.exist(e);
article.tags(true, function(e, tags) {
tags.should.have.lengthOf(len - 1);
done();
});
});
});
});
});
}
});
it('should find scoped record', function (done) {
var id;
Book.create(function (err, book) {
book.chapters.create({name: 'a'}, function (err, ch) {
id = ch.id;
book.chapters.create({name: 'z'}, function () {
book.chapters.create({name: 'c'}, function () {
fetch(book);
});
});
});
});
function fetch(book) {
book.chapters.findById(id, function (err, ch) {
should.not.exist(err);
should.exist(ch);
ch.id.should.equal(id);
done();
});
}
});
});
describe('belongsTo', function () {
var List, Item, Fear, Mind;
it('can be declared in different ways', function () {
List = db.define('List', {name: String});
Item = db.define('Item', {name: String});
Fear = db.define('Fear');
Mind = db.define('Mind');
// syntax 1 (old)
Item.belongsTo(List);
Object.keys((new Item).toObject()).should.include('listId');
(new Item).list.should.be.an.instanceOf(Function);
// syntax 2 (new)
Fear.belongsTo('mind');
Object.keys((new Fear).toObject()).should.include('mindId');
(new Fear).mind.should.be.an.instanceOf(Function);
// (new Fear).mind.build().should.be.an.instanceOf(Mind);
});
it('can be used to query data', function (done) {
List.hasMany('todos', {model: Item});
db.automigrate(function () {
List.create(function (e, list) {
should.not.exist(e);
should.exist(list);
list.todos.create(function (err, todo) {
todo.list(function (e, l) {
should.not.exist(e);
should.exist(l);
l.should.be.an.instanceOf(List);
todo.list().should.equal(l.id);
done();
});
});
});
});
});
it('could accept objects when creating on scope', function (done) {
List.create(function (e, list) {
should.not.exist(e);
should.exist(list);
Item.create({list: list}, function (err, item) {
should.not.exist(err);
should.exist(item);
should.exist(item.listId);
item.listId.should.equal(list.id);
item.__cachedRelations.list.should.equal(list);
done();
});
});
});
});
describe('hasAndBelongsToMany', function () {
var Article, Tag, ArticleTag;
it('can be declared', function (done) {
Article = db.define('Article', {title: String});
Tag = db.define('Tag', {name: String});
Article.hasAndBelongsToMany('tags');
ArticleTag = db.models.ArticleTag;
db.automigrate(function () {
Article.destroyAll(function () {
Tag.destroyAll(function () {
ArticleTag.destroyAll(done)
});
});
});
});
it('should allow to create instances on scope', function (done) {
Article.create(function (e, article) {
article.tags.create({name: 'popular'}, function (e, t) {
t.should.be.an.instanceOf(Tag);
// console.log(t);
ArticleTag.findOne(function (e, at) {
should.exist(at);
at.tagId.toString().should.equal(t.id.toString());
at.articleId.toString().should.equal(article.id.toString());
done();
});
});
});
});
it('should allow to fetch scoped instances', function (done) {
Article.findOne(function (e, article) {
article.tags(function (e, tags) {
should.not.exist(e);
should.exist(tags);
done();
});
});
});
it('should allow to add connection with instance', function (done) {
Article.findOne(function (e, article) {
Tag.create({name: 'awesome'}, function (e, tag) {
article.tags.add(tag, function (e, at) {
should.not.exist(e);
should.exist(at);
at.should.be.an.instanceOf(ArticleTag);
at.tagId.should.equal(tag.id);
at.articleId.should.equal(article.id);
done();
});
});
});
});
it('should allow to remove connection with instance', function (done) {
Article.findOne(function (e, article) {
article.tags(function (e, tags) {
var len = tags.length;
tags.should.not.be.empty;
article.tags.remove(tags[0], function (e) {
should.not.exist(e);
article.tags(true, function (e, tags) {
tags.should.have.lengthOf(len - 1);
done();
});
});
});
});
});
});
});

View File

@ -3,55 +3,55 @@ var should = require('./init.js');
var db = getSchema(), slave = getSchema(), Model, SlaveModel;
describe('dataSource', function() {
describe('dataSource', function () {
it('should define Model', function() {
Model = db.define('Model');
Model.dataSource.should.eql(db);
var m = new Model;
m.getDataSource().should.eql(db);
it('should define Model', function () {
Model = db.define('Model');
Model.dataSource.should.eql(db);
var m = new Model;
m.getDataSource().should.eql(db);
});
it('should clone existing model', function () {
SlaveModel = slave.copyModel(Model);
SlaveModel.dataSource.should.eql(slave);
slave.should.not.eql(db);
var sm = new SlaveModel;
sm.should.be.instanceOf(Model);
sm.getDataSource().should.not.eql(db);
sm.getDataSource().should.eql(slave);
});
it('should automigrate', function (done) {
db.automigrate(done);
});
it('should create transaction', function (done) {
var tr = db.transaction();
tr.connected.should.be.false;
tr.connecting.should.be.false;
var called = false;
tr.models.Model.create(Array(3), function () {
called = true;
});
tr.connected.should.be.false;
tr.connecting.should.be.true;
it('should clone existing model', function() {
SlaveModel = slave.copyModel(Model);
SlaveModel.dataSource.should.eql(slave);
slave.should.not.eql(db);
var sm = new SlaveModel;
sm.should.be.instanceOf(Model);
sm.getDataSource().should.not.eql(db);
sm.getDataSource().should.eql(slave);
});
it('should automigrate', function(done) {
db.automigrate(done);
});
it('should create transaction', function(done) {
var tr = db.transaction();
tr.connected.should.be.false;
tr.connecting.should.be.false;
var called = false;
tr.models.Model.create(Array(3), function () {
called = true;
});
tr.connected.should.be.false;
tr.connecting.should.be.true;
db.models.Model.count(function(err, c) {
should.not.exist(err);
should.exist(c);
c.should.equal(0);
called.should.be.false;
tr.exec(function () {
setTimeout(function() {
called.should.be.true;
db.models.Model.count(function(err, c) {
c.should.equal(3);
done();
});
}, 100);
});
});
db.models.Model.count(function (err, c) {
should.not.exist(err);
should.exist(c);
c.should.equal(0);
called.should.be.false;
tr.exec(function () {
setTimeout(function () {
called.should.be.true;
db.models.Model.count(function (err, c) {
c.should.equal(3);
done();
});
}, 100);
});
});
});
});

View File

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

View File

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

View File

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

View File

@ -1,83 +1,83 @@
[
{
"name": "Address",
"properties": {
"label": "string",
"street": "string",
"city": "string",
"zipCode": "string"
}
},
{
"name": "Account",
"properties": {
"id": "string",
"customer": {
"type": "Customer",
"relation": {
"type": "belongsTo",
"as": "account"
}
},
"balance": "number"
}
},
{
"name": "Customer",
"options": {
"oracle": {
"owner": "STRONGLOOP",
"table": "CUSTOMER"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"doc": "Customer ID"
},
"firstName": {
"type": "string",
"trim": true,
"required": true,
"oracle": {
"column": "FNAME",
"type": "VARCHAR",
"length": 32
}
},
"lastName": {
"type": "string",
"trim": true,
"required": true,
"oracle": {
"column": "LNAME",
"type": "VARCHAR",
"length": 32
}
},
"vip": {
"type": "boolean",
"doc": "indicate if the customer is a VIP",
"oracle": {
"column": "VIP",
"type": "CHAR",
"length": 1
}
},
"emails": [
{
"type": "string",
"trim": true
}
],
"address": {
"type": "Address"
},
"account": "Account"
}
{
"name": "Address",
"properties": {
"label": "string",
"street": "string",
"city": "string",
"zipCode": "string"
}
},
{
"name": "Account",
"properties": {
"id": "string",
"customer": {
"type": "Customer",
"relation": {
"type": "belongsTo",
"as": "account"
}
},
"balance": "number"
}
},
{
"name": "Customer",
"options": {
"oracle": {
"owner": "STRONGLOOP",
"table": "CUSTOMER"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"doc": "Customer ID"
},
"firstName": {
"type": "string",
"trim": true,
"required": true,
"oracle": {
"column": "FNAME",
"type": "VARCHAR",
"length": 32
}
},
"lastName": {
"type": "string",
"trim": true,
"required": true,
"oracle": {
"column": "LNAME",
"type": "VARCHAR",
"length": 32
}
},
"vip": {
"type": "boolean",
"doc": "indicate if the customer is a VIP",
"oracle": {
"column": "VIP",
"type": "CHAR",
"length": 1
}
},
"emails": [
{
"type": "string",
"trim": true
}
],
"address": {
"type": "Address"
},
"account": "Account"
}
}
]

View File

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

View File

@ -5,198 +5,198 @@ var j = require('../'), db, User;
var ValidationError = j.ValidationError;
function getValidAttributes() {
return {
name: 'Anatoliy',
email: 'email@example.com',
state: '',
age: 26,
gender: 'male',
createdByAdmin: false,
createdByScript: true
};
return {
name: 'Anatoliy',
email: 'email@example.com',
state: '',
age: 26,
gender: 'male',
createdByAdmin: false,
createdByScript: true
};
}
describe('validations', function() {
describe('validations', function () {
before(function (done) {
db = getSchema();
User = db.define('User', {
email: String,
name: String,
password: String,
state: String,
age: Number,
gender: String,
domain: String,
pendingPeriod: Number,
createdByAdmin: Boolean,
createdByScript: Boolean,
updatedAt: Date
});
db.automigrate(done);
});
beforeEach(function (done) {
User.destroyAll(function () {
delete User._validations;
done();
});
});
after(function () {
db.disconnect();
});
describe('commons', function () {
describe('skipping', function () {
it('should allow to skip using if: attribute', function () {
User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'});
var user = new User;
user.createdByAdmin = true;
user.isValid().should.be.false;
user.errors.pendingPeriod.should.eql(['can\'t be blank']);
user.pendingPeriod = 1
user.isValid().should.be.true;
});
before(function(done) {
db = getSchema();
User = db.define('User', {
email: String,
name: String,
password: String,
state: String,
age: Number,
gender: String,
domain: String,
pendingPeriod: Number,
createdByAdmin: Boolean,
createdByScript: Boolean,
updatedAt: Date
});
db.automigrate(done);
});
beforeEach(function(done) {
User.destroyAll(function() {
delete User._validations;
describe('lifecycle', function () {
it('should work on create', function (done) {
delete User._validations;
User.validatesPresenceOf('name');
User.create(function (e, u) {
should.exist(e);
User.create({name: 'Valid'}, function (e, d) {
should.not.exist(e);
done();
});
});
});
});
after(function() {
db.disconnect();
});
describe('commons', function() {
describe('skipping', function() {
it('should allow to skip using if: attribute', function() {
User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'});
var user = new User;
user.createdByAdmin = true;
user.isValid().should.be.false;
user.errors.pendingPeriod.should.eql(['can\'t be blank']);
user.pendingPeriod = 1
user.isValid().should.be.true;
it('should work on update', function (done) {
delete User._validations;
User.validatesPresenceOf('name');
User.create({name: 'Valid'}, function (e, d) {
d.updateAttribute('name', null, function (e) {
should.exist(e);
e.should.be.instanceOf(Error);
e.should.be.instanceOf(ValidationError);
d.updateAttribute('name', 'Vasiliy', function (e) {
should.not.exist(e);
done();
});
})
});
});
describe('lifecycle', function() {
it('should work on create', function(done) {
delete User._validations;
User.validatesPresenceOf('name');
User.create(function(e, u) {
should.exist(e);
User.create({name: 'Valid'}, function(e, d) {
should.not.exist(e);
done();
});
});
});
it('should work on update', function(done) {
delete User._validations;
User.validatesPresenceOf('name');
User.create({name: 'Valid'}, function(e, d) {
d.updateAttribute('name', null, function(e) {
should.exist(e);
e.should.be.instanceOf(Error);
e.should.be.instanceOf(ValidationError);
d.updateAttribute('name', 'Vasiliy', function(e) {
should.not.exist(e);
done();
});
})
});
});
it('should return error code', function(done) {
delete User._validations;
User.validatesPresenceOf('name');
User.create(function(e, u) {
should.exist(e);
e.details.codes.name.should.eql(['presence']);
done();
});
});
it('should allow to modify error after validation', function(done) {
User.afterValidate = function(next) {
next();
};
done();
});
it('should return error code', function (done) {
delete User._validations;
User.validatesPresenceOf('name');
User.create(function (e, u) {
should.exist(e);
e.details.codes.name.should.eql(['presence']);
done();
});
});
it('should allow to modify error after validation', function (done) {
User.afterValidate = function (next) {
next();
};
done();
});
});
});
describe('presence', function () {
it('should validate presence', function () {
User.validatesPresenceOf('name', 'email');
var u = new User;
u.isValid().should.not.be.true;
u.name = 1;
u.email = 2;
u.isValid().should.be.true;
});
describe('presence', function() {
it('should skip validation by property (if/unless)', function () {
User.validatesPresenceOf('domain', {unless: 'createdByScript'});
it('should validate presence', function() {
User.validatesPresenceOf('name', 'email');
var u = new User;
u.isValid().should.not.be.true;
u.name = 1;
u.email = 2;
u.isValid().should.be.true;
var user = new User(getValidAttributes())
user.isValid().should.be.true;
user.createdByScript = false;
user.isValid().should.be.false;
user.errors.domain.should.eql(['can\'t be blank']);
user.domain = 'domain';
user.isValid().should.be.true;
});
});
describe('uniqueness', function () {
it('should validate uniqueness', function (done) {
User.validatesUniquenessOf('email');
var u = new User({email: 'hey'});
Boolean(u.isValid(function (valid) {
valid.should.be.true;
u.save(function () {
var u2 = new User({email: 'hey'});
u2.isValid(function (valid) {
valid.should.be.false;
done();
});
});
})).should.be.false;
});
it('should skip validation by property (if/unless)', function() {
User.validatesPresenceOf('domain', {unless: 'createdByScript'});
var user = new User(getValidAttributes())
user.isValid().should.be.true;
user.createdByScript = false;
user.isValid().should.be.false;
user.errors.domain.should.eql(['can\'t be blank']);
user.domain = 'domain';
user.isValid().should.be.true;
it('should handle same object modification', function (done) {
User.validatesUniquenessOf('email');
var u = new User({email: 'hey'});
Boolean(u.isValid(function (valid) {
valid.should.be.true;
u.save(function () {
u.name = 'Goghi';
u.isValid(function (valid) {
valid.should.be.true;
u.save(done);
});
});
// async validations always falsy when called as sync
})).should.not.be.ok;
});
describe('uniqueness', function() {
it('should validate uniqueness', function(done) {
User.validatesUniquenessOf('email');
var u = new User({email: 'hey'});
Boolean(u.isValid(function(valid) {
valid.should.be.true;
u.save(function() {
var u2 = new User({email: 'hey'});
u2.isValid(function(valid) {
valid.should.be.false;
done();
});
});
})).should.be.false;
});
});
it('should handle same object modification', function(done) {
User.validatesUniquenessOf('email');
var u = new User({email: 'hey'});
Boolean(u.isValid(function(valid) {
valid.should.be.true;
u.save(function() {
u.name = 'Goghi';
u.isValid(function(valid) {
valid.should.be.true;
u.save(done);
});
});
// async validations always falsy when called as sync
})).should.not.be.ok;
});
describe('format', function () {
it('should validate format');
it('should overwrite default blank message with custom format message');
});
});
describe('numericality', function () {
it('should validate numericality');
});
describe('format', function() {
it('should validate format');
it('should overwrite default blank message with custom format message');
});
describe('inclusion', function () {
it('should validate inclusion');
});
describe('numericality', function() {
it('should validate numericality');
});
describe('exclusion', function () {
it('should validate exclusion');
});
describe('inclusion', function() {
it('should validate inclusion');
});
describe('length', function () {
it('should validate length');
});
describe('exclusion', function() {
it('should validate exclusion');
});
describe('length', function() {
it('should validate length');
});
describe('custom', function() {
it('should validate using custom sync validation');
it('should validate using custom async validation');
});
describe('custom', function () {
it('should validate using custom sync validation');
it('should validate using custom async validation');
});
});