Merge branch 'release/1.3.7' into production

This commit is contained in:
Raymond Feng 2014-03-19 17:14:52 -07:00
commit 074330e7d8
16 changed files with 811 additions and 478 deletions

View File

@ -1,51 +1,13 @@
{
"content": [
{
"title": "LoopBack DataSource API",
"depth": 2
},
{
"title": "Model builder",
"depth": 3
},
"lib/model-builder.js",
{
"title": "Types",
"depth": 3
},
"lib/types.js",
{
"title": "GeoPoint",
"depth": 3
},
"lib/geo.js",
{
"title": "Model",
"depth": 3
},
"lib/model.js",
{
"title": "DataSource",
"depth": 3
},
"lib/datasource.js",
{
"title": "Data access mixins",
"depth": 3
},
"lib/geo.js",
"lib/dao.js",
"lib/hooks.js",
"lib/model.js",
"lib/model-builder.js",
"lib/include.js",
"lib/relations.js",
"lib/validations.js",
{
"title": "Base class for SQL connectors",
"depth": 3
},
"lib/sql.js"
],
"codeSectionDepth": 4,
@ -54,4 +16,3 @@
"/docs": "/docs"
}
}

View File

@ -2,7 +2,7 @@ var DataSource = require('../index').DataSource;
var ds = new DataSource('memory');
var Order = ds.createModel('Order', {
customerId: Number,
items: [String],
orderDate: Date
});
@ -12,8 +12,11 @@ var Customer = ds.createModel('Customer', {
Order.belongsTo(Customer);
var order1, order2, order3;
Customer.create({name: 'John'}, function (err, customer) {
Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) {
Order.create({customerId: customer.id, orderDate: new Date(), items: ['Book']}, function (err, order) {
order1 = order;
order.customer(console.log);
order.customer(true, console.log);
@ -22,20 +25,34 @@ Customer.create({name: 'John'}, function (err, customer) {
order.customer(console.log);
});
});
Order.create({orderDate: new Date(), items: ['Phone']}, function (err, order) {
order.customer.create({name: 'Smith'}, function(err, customer2) {
console.log(order, customer2);
order.save(function(err, order) {
order2 = order;
});
});
var customer3 = order.customer.build({name: 'Tom'});
console.log('Customer 3', customer3);
});
});
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) {
order3 = 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);
customer.orders.findById(order3.id, console.log);
customer.orders.destroy(order3.id, console.log);
});
});
});
@ -60,13 +77,32 @@ Appointment.belongsTo(Physician);
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);
Physician.create({name: 'Dr John'}, function (err, physician1) {
Physician.create({name: 'Dr Smith'}, function (err, physician2) {
Patient.create({name: 'Mary'}, function (err, patient1) {
Patient.create({name: 'Ben'}, function (err, patient2) {
Appointment.create({appointmentDate: new Date(), physicianId: physician1.id, patientId: patient1.id},
function (err, appt1) {
Appointment.create({appointmentDate: new Date(), physicianId: physician1.id, patientId: patient2.id},
function (err, appt2) {
physician1.patients(console.log);
physician1.patients({where: {name: 'Mary'}}, console.log);
patient1.physicians(console.log);
// Build an appointment?
var patient3 = patient1.physicians.build({name: 'Dr X'});
console.log('Physician 3: ', patient3, patient3.constructor.modelName);
// Create a physician?
patient1.physicians.create({name: 'Dr X'}, function(err, patient4) {
console.log('Physician 4: ', patient4, patient4.constructor.modelName);
});
});
});
});
});
});
});
@ -84,7 +120,22 @@ 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);
assembly.parts(function(err, parts) {
console.log('Parts: ', parts);
});
// Build an part?
var part3 = assembly.parts.build({partNumber: 'door'});
console.log('Part3: ', part3, part3.constructor.modelName);
// Create a part?
assembly.parts.create({partNumber: 'door'}, function(err, part4) {
console.log('Part4: ', part4, part4.constructor.modelName);
Assembly.find({include: 'parts'}, function(err, assemblies) {
console.log('Assemblies: ', assemblies);
});
});
});
});

View File

@ -51,6 +51,24 @@ Memory.prototype.connect = function (callback) {
}
};
function serialize(obj) {
if(obj === null || obj === undefined) {
return obj;
}
return JSON.stringify(obj);
}
function deserialize(dbObj) {
if(dbObj === null || dbObj === undefined) {
return dbObj;
}
if(typeof dbObj === 'string') {
return JSON.parse(dbObj);
} else {
return dbObj;
}
}
Memory.prototype.loadFromFile = function(callback) {
var self = this;
if (self.settings.file) {
@ -142,7 +160,7 @@ Memory.prototype.create = function create(model, data, callback) {
if(!this.cache[model]) {
this.cache[model] = {};
}
this.cache[model][id] = JSON.stringify(data);
this.cache[model][id] = serialize(data);
this.saveToFile(id, callback);
};
@ -161,7 +179,7 @@ Memory.prototype.updateOrCreate = function (model, data, callback) {
};
Memory.prototype.save = function save(model, data, callback) {
this.cache[model][this.getIdValue(model, data)] = JSON.stringify(data);
this.cache[model][this.getIdValue(model, data)] = serialize(data);
this.saveToFile(data, callback);
};
@ -185,7 +203,7 @@ Memory.prototype.destroy = function destroy(model, id, callback) {
Memory.prototype.fromDb = function (model, data) {
if (!data) return null;
data = JSON.parse(data);
data = deserialize(data);
var props = this._models[model].properties;
for (var key in data) {
var val = data[key];
@ -374,8 +392,8 @@ Memory.prototype.updateAttributes = function updateAttributes(model, id, data, c
this.setIdValue(model, data, id);
var cachedModels = this.cache[model];
var modelAsString = cachedModels && this.cache[model][id];
var modelData = modelAsString && JSON.parse(modelAsString);
var modelData = cachedModels && this.cache[model][id];
modelData = modelData && deserialize(modelData);
if (modelData) {
this.save(model, merge(modelData, data), cb);

View File

@ -1,9 +1,9 @@
/**
/*!
* Module exports class Model
*/
module.exports = DataAccessObject;
/**
/*!
* Module dependencies
*/
var jutil = require('./jutil');
@ -19,15 +19,14 @@ var fieldsToArray = utils.fieldsToArray;
var removeUndefined = utils.removeUndefined;
/**
* DAO class - base class for all persist objects
* provides **common API** to access any database connector.
* This class describes only abstract behavior layer, refer to `lib/connectors/*.js`
* to learn more about specific connector implementations
* Base class for all persistent objects.
* Provides a common API to access any database connector.
* This class describes only abstract behavior. Refer to the specific connector (`lib/connectors/*.js`) for details.
*
* `DataAccessObject` mixes `Inclusion` classes methods
* `DataAccessObject` mixes `Inclusion` classes methods.
*
* @constructor
* @param {Object} data - initial object data
* @class DataAccessObject
* @param {Object} data Initial object data
*/
function DataAccessObject() {
if (DataAccessObject._mixins) {
@ -71,14 +70,15 @@ DataAccessObject._forDB = function (data) {
};
/**
* Create new instance of Model class, saved in database
*
* @param data [optional]
* @param callback(err, obj)
* callback called with arguments:
* Create new instance of Model class, saved in database.
* The callback function is called with arguments:
*
* - err (null or Error)
* - instance (null or Model)
*
* @param data {Object} Optional data object
* @param callback {Function} Callback function
*/
DataAccessObject.create = function (data, callback) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
@ -208,9 +208,9 @@ function stillConnecting(dataSource, obj, args) {
}
/**
* Update or insert a model instance
* Update or insert a model instance.
* @param {Object} data The model instance data
* @param {Function} [callback] The callback function
* @param {Function} callback The callback function (optional).
*/
DataAccessObject.upsert = DataAccessObject.updateOrCreate = function upsert(data, callback) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
@ -254,9 +254,9 @@ setRemoting(DataAccessObject.upsert, {
* Find one record, same as `all`, limited by 1 and return object, not collection,
* if not found, create using data provided as second argument
*
* @param {Object} query - search conditions: {where: {test: 'me'}}.
* @param {Object} data - object to create.
* @param {Function} cb - callback called with (err, instance)
* @param {Object} query Search conditions: {where: {test: 'me'}}.
* @param {Object} data Object to create.
* @param {Function} cb Callback called with (err, instance)
*/
DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) {
if (query === undefined) {
@ -282,8 +282,8 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) {
/**
* Check whether a model instance exists in database
*
* @param {id} id - identifier of object (primary key value)
* @param {Function} cb - callbacl called with (err, exists: Bool)
* @param {id} id Identifier of object (primary key value)
* @param {Function} cb Callback function called with (err, exists: Bool)
*/
DataAccessObject.exists = function exists(id, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
@ -306,8 +306,8 @@ setRemoting(DataAccessObject.exists, {
/**
* Find object by id
*
* @param {*} id - primary key value
* @param {Function} cb - callback called with (err, instance)
* @param {*} id Primary key value
* @param {Function} cb Callback called with (err, instance)
*/
DataAccessObject.findById = function find(id, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
@ -460,50 +460,48 @@ DataAccessObject._coerce = function (where) {
* Find all instances of Model, matched by query
* make sure you have marked as `index: true` fields for filter or sort
*
* @param {Object} params (optional)
* @param {Object} [query] the query object
*
* - where: Object `{ key: val, key2: {gt: 'val2'}}`
* - include: String, Object or Array. See DataAccessObject.include documentation.
* - include: String, Object or Array. See `DataAccessObject.include()`.
* - order: String
* - limit: Number
* - skip: Number
*
* @param {Function} callback (required) called with arguments:
*
* - err (null or Error)
* - Array of instances
*
* @param {Object} params (optional)
* @param {Function} callback (required) called with two arguments: err (null or Error), array of instances
*/
DataAccessObject.find = function find(params, cb) {
DataAccessObject.find = function find(query, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
if (arguments.length === 1) {
cb = params;
params = null;
cb = query;
query = null;
}
var constr = this;
params = params || {};
query = query || {};
if (params.where) {
params.where = this._coerce(params.where);
if (query.where) {
query.where = this._coerce(query.where);
}
var fields = params.fields;
var near = params && geo.nearFilter(params.where);
var fields = query.fields;
var near = query && geo.nearFilter(query.where);
var supportsGeo = !!this.getDataSource().connector.buildNearFilter;
// normalize fields as array of included property names
if (fields) {
params.fields = fieldsToArray(fields, Object.keys(this.definition.properties));
query.fields = fieldsToArray(fields, Object.keys(this.definition.properties));
}
params = removeUndefined(params);
query = removeUndefined(query);
if (near) {
if (supportsGeo) {
// convert it
this.getDataSource().connector.buildNearFilter(params, near);
} else if (params.where) {
this.getDataSource().connector.buildNearFilter(query, near);
} else if (query.where) {
// do in memory query
// using all documents
this.getDataSource().connector.all(this.modelName, {}, function (err, data) {
@ -525,7 +523,7 @@ DataAccessObject.find = function find(params, cb) {
});
});
memory.all(modelName, params, cb);
memory.all(modelName, query, cb);
} else {
cb(null, []);
}
@ -536,24 +534,24 @@ DataAccessObject.find = function find(params, cb) {
}
}
this.getDataSource().connector.all(this.modelName, params, function (err, data) {
this.getDataSource().connector.all(this.modelName, query, function (err, data) {
if (data && data.forEach) {
data.forEach(function (d, i) {
var obj = new constr();
obj._initProperties(d, {fields: params.fields});
obj._initProperties(d, {fields: query.fields});
if (params && params.include) {
if (params.collect) {
if (query && query.include) {
if (query.collect) {
// The collect property indicates that the query is to return the
// standlone items for a related model, not as child of the parent object
// For example, article.tags
obj = obj.__cachedRelations[params.collect];
obj = obj.__cachedRelations[query.collect];
} else {
// This handles the case to return parent items including the related
// models. For example, Article.find({include: 'tags'}, ...);
// Try to normalize the include
var includes = params.include || [];
var includes = query.include || [];
if (typeof includes === 'string') {
includes = [includes];
} else if (!Array.isArray(includes) && typeof includes === 'object') {
@ -598,19 +596,19 @@ setRemoting(DataAccessObject.find, {
/**
* Find one record, same as `all`, limited by 1 and return object, not collection
*
* @param {Object} params - search conditions: {where: {test: 'me'}}
* @param {Object} query - search conditions: {where: {test: 'me'}}
* @param {Function} cb - callback called with (err, instance)
*/
DataAccessObject.findOne = function findOne(params, cb) {
DataAccessObject.findOne = function findOne(query, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
if (typeof params === 'function') {
cb = params;
params = {};
if (typeof query === 'function') {
cb = query;
query = {};
}
params = params || {};
params.limit = 1;
this.find(params, function (err, collection) {
query = query || {};
query.limit = 1;
this.find(query, function (err, collection) {
if (err || !collection || !collection.length > 0) return cb(err, null);
cb(err, collection[0]);
});
@ -626,7 +624,7 @@ setRemoting(DataAccessObject.findOne, {
/**
* Destroy all matching records
* @param {Object} [where] An object that defines the criteria
* @param {Function} [cb] - callback called with (err)
* @param {Function} [cb] Callback called with (err)
*/
DataAccessObject.remove =
DataAccessObject.deleteAll =
@ -657,7 +655,7 @@ DataAccessObject.remove =
/**
* Destroy a record by id
* @param {*} id The id value
* @param {Function} cb - callback called with (err)
* @param {Function} cb Callback called with (err)
*/
DataAccessObject.removeById =
DataAccessObject.deleteById =
@ -683,8 +681,8 @@ setRemoting(DataAccessObject.deleteById, {
/**
* Return count of matched records
*
* @param {Object} where - search conditions (optional)
* @param {Function} cb - callback, called with (err, count)
* @param {Object} where Search conditions (optional)
* @param {Function} cb Callback, called with (err, count)
*/
DataAccessObject.count = function (where, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
@ -734,7 +732,6 @@ DataAccessObject.prototype.save = function (options, callback) {
var inst = this;
var data = inst.toObject(true);
var Model = this.constructor;
var modelName = Model.modelName;
if (!getIdValue(Model, this)) {
@ -797,7 +794,7 @@ DataAccessObject.prototype._adapter = function () {
/**
* Delete object from persistence
*
* @triggers `destroy` hook (async) before and after destroying object
* Triggers `destroy` hook (async) before and after destroying object
*/
DataAccessObject.prototype.remove =
DataAccessObject.prototype.delete =
@ -825,9 +822,9 @@ DataAccessObject.prototype.remove =
*
* equals to `updateAttributes({name: value}, cb)
*
* @param {String} name - name of property
* @param {Mixed} value - value of property
* @param {Function} callback - callback called with (err, instance)
* @param {String} name Name of property
* @param {Mixed} value Value of property
* @param {Function} callback Callback function called with (err, instance)
*/
DataAccessObject.prototype.updateAttribute = function updateAttribute(name, value, callback) {
var data = {};
@ -841,14 +838,14 @@ DataAccessObject.prototype.updateAttribute = function updateAttribute(name, valu
* this method performs validation before updating
*
* @trigger `validation`, `save` and `update` hooks
* @param {Object} data - data to update
* @param {Function} callback - callback called with (err, instance)
* @param {Object} data Data to update
* @param {Function} callback Callback function called with (err, instance)
*/
DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
var inst = this;
var Model = this.constructor
var Model = this.constructor;
var model = Model.modelName;
if (typeof data === 'function') {
@ -911,24 +908,19 @@ setRemoting(DataAccessObject.prototype.updateAttributes, {
/**
* Reload object from persistence
*
* @requires `id` member of `object` to be able to call `find`
* @param {Function} callback - called with (err, instance) arguments
* Requires `id` member of `object` to be able to call `find`
* @param {Function} callback Called with (err, instance) arguments
*/
DataAccessObject.prototype.reload = function reload(callback) {
if (stillConnecting(this.getDataSource(), this, arguments)) return;
if (stillConnecting(this.getDataSource(), this, arguments)) {
return;
}
this.constructor.findById(getIdValue(this.constructor, this), callback);
};
/*
setRemoting(DataAccessObject.prototype.reload, {
description: 'Reload a model instance from the data source',
returns: {arg: 'data', type: 'object', root: true}
});
*/
/**
/*!
* Define readonly property on object
*
* @param {Object} obj
@ -947,12 +939,24 @@ function defineReadonlyProp(obj, key, value) {
var defineScope = require('./scope.js').defineScope;
/**
* Define scope
* Define a scope for the model class. Scopes enable you to specify commonly-used
* queries that you can reference as method calls on a model.
*
* @param {String} name The scope name
* @param {Object} query The query object for DataAccessObject.find()
* @param {ModelClass} [targetClass] The model class for the query, default to
* the declaring model
*/
DataAccessObject.scope = function (name, filter, targetClass) {
defineScope(this, targetClass || this, name, filter);
DataAccessObject.scope = function (name, query, targetClass) {
defineScope(this, targetClass || this, name, query);
};
// jutil.mixin(DataAccessObject, validations.Validatable);
/*!
* Add 'include'
*/
jutil.mixin(DataAccessObject, Inclusion);
/*!
* Add 'relation'
*/
jutil.mixin(DataAccessObject, Relation);

View File

@ -19,7 +19,7 @@ if (process.env.DEBUG === 'loopback') {
}
var debug = require('debug')('loopback:datasource');
/**
/*!
* Export public API
*/
exports.DataSource = DataSource;
@ -30,23 +30,32 @@ exports.DataSource = DataSource;
var slice = Array.prototype.slice;
/**
* DataSource - connector-specific classes factory.
* LoopBack models can manipulate data via the DataSource object.
* Attaching a `DataSource` to a `Model` adds instance methods and static methods to the `Model`;
* some of the added methods may be remote methods.
*
* All classes in single dataSource shares same connector type and
* one database connection
*
* @param {String} name - type of dataSource connector (mysql, mongoose, oracle, redis)
* @param {Object} settings - any database-specific settings which we need to
* establish connection (of course it depends on specific connector)
* Define a data source for persisting models.
* Typically, you create a DataSource by calling createDataSource() on the LoopBack object; for example:
* ```js
* var oracle = loopback.createDataSource({
* connector: 'oracle',
* host: '111.22.333.44',
* database: 'MYDB',
* username: 'username',
* password: 'password'
* });
* ```
*
* All classes in single dataSource share same the connector type and
* one database connection. The `settings` argument is an object that can have the following properties:
* - host
* - port
* - username
* - password
* - database
* - debug {Boolean} = false
* - debug (Boolean, default is false)
*
* @example DataSource creation, waiting for connection callback
* @desc For example, the following creates a DataSource, and waits for a connection callback.
* ```
* var dataSource = new DataSource('mysql', { database: 'myapp_test' });
* dataSource.define(...);
@ -54,6 +63,9 @@ var slice = Array.prototype.slice;
* // work with database
* });
* ```
* @class Define new DataSource
* @param {String} name Type of dataSource connector (mysql, mongoose, oracle, redis)
* @param {Object} settings Database-specific settings to establish connection (settings depend on specific connector). See above.
*/
function DataSource(name, settings, modelBuilder) {
if (!(this instanceof DataSource)) {
@ -166,13 +178,18 @@ DataSource.prototype._setupConnector = function () {
// List possible connector module names
function connectorModuleNames(name) {
var names = [name]; // Check the name as is
var names = []; // Check the name as is
if (!name.match(/^\//)) {
names.push('./connectors/' + name); // Check built-in connectors
if (name.indexOf('loopback-connector-') !== 0) {
names.push('loopback-connector-' + name); // Try loopback-connector-<name>
}
}
// Only try the short name if the connector is not from StrongLoop
if(['mongodb', 'oracle', 'mysql', 'postgresql', 'mssql', 'rest', 'soap']
.indexOf(name) === -1) {
names.push(name);
}
return names;
}
@ -477,28 +494,19 @@ DataSource.prototype.setupDataAccess = function (modelClass, settings) {
};
/**
* Define a model class
*
* @param {String} className
* @param {Object} properties - hash of class properties in format
* `{property: Type, property2: Type2, ...}`
* or
* `{property: {type: Type}, property2: {type: Type2}, ...}`
* @param {Object} settings - other configuration of class
* @return newly created class
*
* @example simple case
* Define a model class.
* Simple example:
* ```
* var User = dataSource.define('User', {
* var User = dataSource.createModel('User', {
* email: String,
* password: String,
* birthDate: Date,
* activated: Boolean
* });
* ```
* @example more advanced case
* More advanced example
* ```
* var User = dataSource.define('User', {
* var User = dataSource.createModel('User', {
* email: { type: String, limit: 150, index: true },
* password: { type: String, limit: 50 },
* birthDate: Date,
@ -506,6 +514,29 @@ DataSource.prototype.setupDataAccess = function (modelClass, settings) {
* activated: { type: Boolean, default: false }
* });
* ```
* You can also define an ACL when you create a new data source with the `DataSource.create()` method. For example:
*
* ```js
* var Customer = ds.createModel('Customer', {
* name: {
* type: String,
* acls: [
* {principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
* {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
* ]
* }
* }, {
* acls: [
* {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
* ]
* });
* ```
*
* @param {String} className Name of the model to create.
* @param {Object} properties Hash of class properties in format `{property: Type, property2: Type2, ...}` or `{property: {type: Type}, property2: {type: Type2}, ...}`
* @param {Object} settings Other configuration settings.
* @returns newly created class
*
*/
DataSource.prototype.createModel = DataSource.prototype.define = function defineClass(className, properties, settings) {
@ -591,14 +622,14 @@ DataSource.prototype.mixin = function (ModelCtor) {
};
/**
* @see ModelBuilder.prototype.getModel
* See ModelBuilder.getModel
*/
DataSource.prototype.getModel = function (name, forceCreate) {
return this.modelBuilder.getModel(name, forceCreate);
};
/**
* @see ModelBuilder.prototype.getModelDefinition
* See ModelBuilder.getModelDefinition
*/
DataSource.prototype.getModelDefinition = function (name) {
return this.modelBuilder.getModelDefinition(name);
@ -618,9 +649,9 @@ DataSource.prototype.getTypes = function () {
};
/**
* Check the data source supports the given types
* @param String|String[]) types A type name or an array of type names
* @return {Boolean} true if all types are supported by the data source
* Check the data source supports the specified types.
* @param {String} types Type name or an array of type names. Can also be array of Strings.
* @returns {Boolean} true if all types are supported by the data source
*/
DataSource.prototype.supportTypes = function (types) {
var supportedTypes = this.getTypes();
@ -670,9 +701,9 @@ DataSource.prototype.attach = function (modelClass) {
/**
* Define single property named `prop` on `model`
*
* @param {String} model - name of model
* @param {String} prop - name of property
* @param {Object} params - property settings
* @param {String} model Name of model
* @param {String} prop Name of property
* @param {Object} params Property settings
*/
DataSource.prototype.defineProperty = function (model, prop, params) {
this.modelBuilder.defineProperty(model, prop, params);
@ -685,12 +716,12 @@ DataSource.prototype.defineProperty = function (model, prop, params) {
/**
* Drop each model table and re-create.
* This method make sense only for sql connectors.
* This method applies only to SQL connectors.
*
* @param {String} or {[String]} Models to be migrated, if not present, apply to all models
* @param {Function} [cb] The callback function
* @param {String} Models to be migrated, if not present, apply to all models. This can also be an array of Strings.
* @param {Function} cb Callback function. Optional.
*
* @warning All data will be lost! Use autoupdate if you need your data.
* WARNING: Calling this function will cause all data to be lost! Use autoupdate if you need to preserve data.
*/
DataSource.prototype.automigrate = function (models, cb) {
this.freeze();
@ -709,7 +740,7 @@ DataSource.prototype.automigrate = function (models, cb) {
* Update existing database tables.
* This method make sense only for sql connectors.
*
* @param {String} or {[String]} Models to be migrated, if not present, apply to all models
* @param {String} Models to be migrated, if not present, apply to all models. This can also be an array of Strings.
* @param {Function} [cb] The callback function
*/
DataSource.prototype.autoupdate = function (models, cb) {
@ -729,15 +760,15 @@ DataSource.prototype.autoupdate = function (models, cb) {
* Discover existing database tables.
* This method returns an array of model objects, including {type, name, onwer}
*
* `options`
* Kyes in options object:
*
* all: true - Discovering all models, false - Discovering the models owned by the current user
* views: true - Including views, false - only tables
* limit: The page size
* offset: The starting index
* - all: true - Discovering all models, false - Discovering the models owned by the current user
* - views: true - Including views, false - only tables
* - limit: The page size
* - offset: The starting index
*
* @param {Object} options The options
* @param {Function} [cb] The callback function
* @param {Function} Callback function. Optional.
*
*/
DataSource.prototype.discoverModelDefinitions = function (options, cb) {
@ -765,24 +796,25 @@ DataSource.prototype.discoverModelDefinitionsSync = function (options) {
/**
* Discover properties for a given model.
*
* `property description`
* property description:
*
* owner {String} The database owner or schema
* tableName {String} The table/view name
* columnName {String} The column name
* dataType {String} The data type
* dataLength {Number} The data length
* dataPrecision {Number} The numeric data precision
* dataScale {Number} The numeric data scale
* nullable {Boolean} If the data can be null
*| Key | Type | Description |
*|-----|------|-------------|
*|owner | String | Database owner or schema|
*|tableName | String | Table/view name|
*|columnName | String | Column name|
*|dataType | String | Data type|
*|dataLength | Number | Data length|
*|dataPrecision | Number | Numeric data precision|
*|dataScale |Number | Numeric data scale|
*|nullable |Boolean | If true, then the data can be null|
*
* `options`
*
* owner/schema The database owner/schema
* Options:
* - owner/schema The database owner/schema
*
* @param {String} modelName The table/view name
* @param {Object} options The options
* @param {Function} [cb] The callback function
* @param {Function} cb Callback function. Optional
*
*/
DataSource.prototype.discoverModelProperties = function (modelName, options, cb) {
@ -809,21 +841,20 @@ DataSource.prototype.discoverModelPropertiesSync = function (modelName, options)
};
/**
* Discover primary keys for a given owner/modelName
* Discover primary keys for a given owner/modelName.
*
* Each primary key column description has the following columns:
*
* owner {String} => table schema (may be null)
* tableName {String} => table name
* columnName {String} => column name
* keySeq {Number} => sequence number within primary key( a value of 1 represents the first column of the primary key, a value of 2 would represent the second column within the primary key).
* pkName {String} => primary key name (may be null)
* - owner {String} => table schema (may be null)
* - tableName {String} => table name
* - columnName {String} => column name
* - keySeq {Number} => sequence number within primary key( a value of 1 represents the first column of the primary key, a value of 2 would represent the second column within the primary key).
* - pkName {String} => primary key name (may be null)
*
* The owner, default to current user
* The owner defaults to current user.
*
* `options`
*
* owner/schema The database owner/schema
* Options:
* - owner/schema The database owner/schema
*
* @param {String} modelName The model name
* @param {Object} options The options
@ -968,7 +999,58 @@ function fromDBName(dbName, camelCase) {
}
/**
* Discover one schema from the given model without following the relations
* Discover one schema from the given model without following the relations.
**Example schema from oracle connector:**
*
* ```js
* {
* "name": "Product",
* "options": {
* "idInjection": false,
* "oracle": {
* "schema": "BLACKPOOL",
* "table": "PRODUCT"
* }
* },
* "properties": {
* "id": {
* "type": "String",
* "required": true,
* "length": 20,
* "id": 1,
* "oracle": {
* "columnName": "ID",
* "dataType": "VARCHAR2",
* "dataLength": 20,
* "nullable": "N"
* }
* },
* "name": {
* "type": "String",
* "required": false,
* "length": 64,
* "oracle": {
* "columnName": "NAME",
* "dataType": "VARCHAR2",
* "dataLength": 64,
* "nullable": "Y"
* }
* },
* ...
* "fireModes": {
* "type": "String",
* "required": false,
* "length": 64,
* "oracle": {
* "columnName": "FIRE_MODES",
* "dataType": "VARCHAR2",
* "dataLength": 64,
* "nullable": "Y"
* }
* }
* }
* }
* ```
*
* @param {String} modelName The model name
* @param {Object} [options] The options
@ -1489,7 +1571,7 @@ DataSource.prototype.idNames = function (modelName) {
/**
* Define foreign key to another model
* @param {String} className The model name that owns the key
* @param {String} key - name of key field
* @param {String} key Name of key field
* @param {String} foreignClassName The foreign model name
*/
DataSource.prototype.defineForeignKey = function defineForeignKey(className, key, foreignClassName) {
@ -1528,7 +1610,7 @@ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key
/**
* Close database connection
* @param {Fucntion} [cb] The callback function
* @param {Fucntion} cb The callback function. Optional.
*/
DataSource.prototype.disconnect = function disconnect(cb) {
var self = this;
@ -1545,7 +1627,7 @@ DataSource.prototype.disconnect = function disconnect(cb) {
};
/**
* Copy the model from Master
* Copy the model from Master.
* @param {Function} Master The model constructor
* @returns {Function} The copy of the model constructor
*
@ -1625,7 +1707,8 @@ DataSource.prototype.transaction = function () {
};
/**
* Enable a data source operation to be remote.
* Enable remote access to a data source operation. Each [connector](#connector) has its own set of set
* remotely enabled and disabled operations. To list the operations, call `dataSource.operations()`.
* @param {String} operation The operation name
*/
@ -1639,7 +1722,23 @@ DataSource.prototype.enableRemote = function (operation) {
}
/**
* Disable a data source operation to be remote.
* Disable remote access to a data source operation. Each [connector](#connector) has its own set of set enabled
* and disabled operations. To list the operations, call `dataSource.operations()`.
*
*```js
*
* var oracle = loopback.createDataSource({
* connector: require('loopback-connector-oracle'),
* host: '...',
* ...
* });
* oracle.disableRemote('destroyAll');
* ```
* **Notes:**
*
* - Disabled operations will not be added to attached models.
* - Disabling the remoting for a method only affects client access (it will still be available from server models).
* - Data sources must enable / disable operations before attaching or creating models.
* @param {String} operation The operation name
*/
@ -1671,7 +1770,27 @@ DataSource.prototype.getOperation = function (operation) {
}
/**
* Get all operations.
* Return JSON object describing all operations.
*
* Example return value:
* ```js
* {
* find: {
* remoteEnabled: true,
* accepts: [...],
* returns: [...]
* enabled: true
* },
* save: {
* remoteEnabled: true,
* prototype: true,
* accepts: [...],
* returns: [...],
* enabled: true
* },
* ...
* }
* ```
*/
DataSource.prototype.operations = function () {
return this._operations;
@ -1681,7 +1800,7 @@ DataSource.prototype.operations = function () {
* Define an operation to the data source
* @param {String} name The operation name
* @param {Object} options The options
* @param [Function} fn The function
* @param {Function} fn The function
*/
DataSource.prototype.defineOperation = function (name, options, fn) {
options.fn = fn;
@ -1698,10 +1817,10 @@ DataSource.prototype.isRelational = function () {
};
/**
* Check if the data source is ready
* @param obj
* @param args
* @returns {boolean}
* Check if the data source is ready.
* Returns a Boolean value.
* @param obj {Object} ?
* @param args {Object} ?
*/
DataSource.prototype.ready = function (obj, args) {
var self = this;

View File

@ -1,7 +1,3 @@
/**
* Dependencies.
*/
var assert = require('assert');
/*!
@ -80,12 +76,41 @@ exports.filter = function (arr, filter) {
});
}
/**
* Export the `GeoPoint` class.
*/
exports.GeoPoint = GeoPoint;
/**
* The GeoPoint object represents a physical location.
*
* For example:
*
* ```js
* var here = new GeoPoint({lat: 10.32424, lng: 5.84978});
* ```
*
* Embed a latitude / longitude point in a model.
*
* ```js
* var CoffeeShop = loopback.createModel('coffee-shop', {
* location: 'GeoPoint'
* });
* ```
*
* You can query LoopBack models with a GeoPoint property and an attached data source using geo-spatial filters and
* sorting. For example, the following code finds the three nearest coffee shops.
*
* ```js
* CoffeeShop.attachTo(oracle);
* var here = new GeoPoint({lat: 10.32424, lng: 5.84978});
* CoffeeShop.find( {where: {location: {near: here}}, limit:3}, function(err, nearbyShops) {
* console.info(nearbyShops); // [CoffeeShop, ...]
* });
* ```
* @class GeoPoint
* @param {Object} latlong Object with two Number properties: lat and long.
* @prop {Number} lat
* @prop {Number} lng
*/
function GeoPoint(data) {
if (!(this instanceof GeoPoint)) {
return new GeoPoint(data);
@ -118,7 +143,18 @@ function GeoPoint(data) {
}
/**
* Determine the spherical distance between two geo points.
* Determine the spherical distance between two GeoPoints.
* Specify units of measurement with the 'type' property in options object. Type can be:
* - `miles` (default)
* - `radians`
* - `kilometers`
* - `meters`
* - `miles`
* - `feet`
* - `degrees`
* @param {GeoPoint} pointA Point A
* @param {GeoPoint} pointB Point B
* @param {Object} options Options object; has one key, 'type', to specify the units of measurment (see above). Default is miles.
*/
GeoPoint.distanceBetween = function distanceBetween(a, b, options) {
@ -140,6 +176,14 @@ GeoPoint.distanceBetween = function distanceBetween(a, b, options) {
/**
* Determine the spherical distance to the given point.
* Example:
* ```js
* var here = new GeoPoint({lat: 10, lng: 10});
* var there = new GeoPoint({lat: 5, lng: 5});
* GeoPoint.distanceBetween(here, there, {type: 'miles'}) // 438
* ```
* @param {Object} point GeoPoint object to which to measure distance.
* @param {Object} options Use type key to specify units of measurment (default is miles).
*/
GeoPoint.prototype.distanceTo = function (point, options) {
@ -155,16 +199,19 @@ GeoPoint.prototype.toString = function () {
}
/**
* Si
*/
* @property {Number} PI - Ratio of a circle's circumference to its diameter.
* @property {Number} DEG2RAD - Factor to convert degrees to radians.
* @property {Number} RAD2DEG - Factor to convert radians to degrees.
* @property {Object} EARTH_RADIUS - Radius of the earth.
*/
// ratio of a circle's circumference to its diameter
var PI = 3.1415926535897932384626433832795;
// factor to convert decimal degrees to radians
// factor to convert degrees to radians
var DEG2RAD = 0.01745329252;
// factor to convert decimal degrees to radians
// factor to convert radians degrees to degrees
var RAD2DEG = 57.29577951308;
// radius of the earth

View File

@ -1,83 +1,88 @@
var async = require('async');
var utils = require('./utils');
var isPlainObject = utils.isPlainObject;
var defineCachedRelations = utils.defineCachedRelations;
/**
/*!
* Include mixin for ./model.js
*/
module.exports = Inclusion;
/**
* Inclusion - Model mixin.
*
* @class
*/
function Inclusion() {
}
/**
* Allows you to load relations of several objects and optimize numbers of requests.
*
* @param {Array} objects - array of instances
* @param {String}, {Object} or {Array} include - which relations you want to load.
* @param {Function} cb - Callback called when relations are loaded
* Enables you to load relations of several objects and optimize numbers of requests.
*
* Examples:
*
* - User.include(users, 'posts', function() {}); will load all users posts with only one additional request.
* - User.include(users, ['posts'], function() {}); // same
* - User.include(users, ['posts', 'passports'], function() {}); // will load all users posts and passports with two
* additional requests.
* - Passport.include(passports, {owner: 'posts'}, function() {}); // will load all passports owner (users), and all
* posts of each owner loaded
* - Passport.include(passports, {owner: ['posts', 'passports']}); // ...
* - Passport.include(passports, {owner: [{posts: 'images'}, 'passports']}); // ...
* Load all users' posts with only one additional request:
* `User.include(users, 'posts', function() {});`
* Or
* `User.include(users, ['posts'], function() {});`
*
* Load all users posts and passports with two additional requests:
* `User.include(users, ['posts', 'passports'], function() {});`
*
* Load all passports owner (users), and all posts of each owner loaded:
*```Passport.include(passports, {owner: 'posts'}, function() {});
*``` Passport.include(passports, {owner: ['posts', 'passports']});
*``` Passport.include(passports, {owner: [{posts: 'images'}, 'passports']});
*
* @param {Array} objects Array of instances
* @param {String}, {Object} or {Array} include Which relations to load.
* @param {Function} cb Callback called when relations are loaded
*
*/
Inclusion.include = function (objects, include, cb) {
var self = this;
if (
!include || (Array.isArray(include) && include.length === 0) ||
(isPlainObject(include) && Object.keys(include).length === 0)
) {
cb(null, objects);
return;
if (!include || (Array.isArray(include) && include.length === 0) ||
(isPlainObject(include) && Object.keys(include).length === 0)) {
// The objects are empty
return process.nextTick(function() {
cb && cb(null, objects);
});
}
include = processIncludeJoin(include);
include = normalizeInclude(include);
var keyVals = {};
var objsByKeys = {};
async.each(include, function(item, callback) {
processIncludeItem(objects, item, callback);
}, function(err) {
cb && cb(err, objects);
});
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 (isPlainObject(ij)) {
var newIj = [];
for (var key in ij) {
/*!
* Normalize the include to be an array
* @param include
* @returns {*}
*/
function normalizeInclude(include) {
if (typeof include === 'string') {
return [include];
} else if (isPlainObject(include)) {
// Build an array of key/value pairs
var newInclude = [];
for (var key in include) {
var obj = {};
obj[key] = ij[key];
newIj.push(obj);
obj[key] = include[key];
newInclude.push(obj);
}
return newIj;
return newInclude;
} else {
return include;
}
return ij;
}
function processIncludeItem(objs, include, keyVals, objsByKeys) {
function processIncludeItem(objs, include, cb) {
var relations = self.relations;
var relationName, subInclude;
@ -86,7 +91,7 @@ Inclusion.include = function (objects, include, cb) {
subInclude = include[relationName];
} else {
relationName = include;
subInclude = [];
subInclude = null;
}
var relation = relations[relationName];
@ -97,68 +102,40 @@ Inclusion.include = function (objects, include, cb) {
};
}
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]);
// Calling the relation method for each object
async.each(objs, function (obj, callback) {
if(relation.type === 'belongsTo') {
// If the belongsTo relation doesn't have an owner
if(obj[relation.keyFrom] === null || obj[relation.keyFrom] === undefined) {
defineCachedRelations(obj);
// Set to null if the owner doesn't exist
obj.__cachedRelations[relationName] = null;
obj[relationName] = null;
return callback();
}
}
var inst = (obj instanceof self) ? obj : new self(obj);
var relationMethod = inst[relationName];
// FIXME: [rfeng] How do we pass in the refresh flag?
relationMethod(function (err, result) {
if (err) {
return callback(err);
} else {
defineCachedRelations(obj);
obj.__cachedRelations[relationName] = result;
obj[relationName] = result;
req.where[relation.keyTo] = {inq: inValues};
req.include = subInclude;
return function (cb) {
relation.modelTo.find(req, function (err, objsIncluded) {
var objectsFrom, j;
for (var i = 0; i < objsIncluded.length; i++) {
delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
for (j = 0; j < objectsFrom.length; j++) {
defineCachedRelations(objectsFrom[j]);
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];
}
}
if (subInclude && result) {
var subItems = relation.multiple ? result : [result];
// Recursively include the related models
relation.modelTo.include(subItems, subInclude, callback);
} else {
callback(null, result);
}
}
});
}, cb);
// No relation have been found for these keys
for (var key in keysToBeProcessed) {
objectsFrom = objsByKeys[relation.keyFrom][key];
for (j = 0; j < objectsFrom.length; j++) {
defineCachedRelations(objectsFrom[j]);
objectsFrom[j].__cachedRelations[relationName] =
relation.multiple ? [] : null;
}
}
cb(err, objsIncluded);
});
};
}
return null;
}
};

View File

@ -16,7 +16,7 @@ require('./types')(ModelBuilder);
var introspect = require('./introspection')(ModelBuilder);
/**
/*!
* Export public API
*/
exports.ModelBuilder = exports.Schema = ModelBuilder;
@ -27,9 +27,9 @@ exports.ModelBuilder = exports.Schema = ModelBuilder;
var slice = Array.prototype.slice;
/**
* ModelBuilder - A builder to define data models
* ModelBuilder - A builder to define data models.
*
* @constructor
* @class
*/
function ModelBuilder() {
// create blank models pool
@ -57,11 +57,11 @@ function isModelClass(cls) {
}
/**
* Get a model by name
* Get a model by name.
*
* @param {String} name The model name
* @param {Boolean} forceCreate Indicate if a stub should be created for the
* given name if a model doesn't exist
* @returns {*} The model class
* @param {Boolean} forceCreate Whether the create a stub for the given name if a model doesn't exist.
* Returns {*} The model class
*/
ModelBuilder.prototype.getModel = function (name, forceCreate) {
var model = this.models[name];
@ -81,17 +81,8 @@ ModelBuilder.prototype.getModelDefinition = function (name) {
};
/**
* Define a model class
*
* @param {String} className
* @param {Object} properties - hash of class properties in format
* `{property: Type, property2: Type2, ...}`
* or
* `{property: {type: Type}, property2: {type: Type2}, ...}`
* @param {Object} settings - other configuration of class
* @return newly created class
*
* @example simple case
* Define a model class.
* Simple example:
* ```
* var User = modelBuilder.define('User', {
* email: String,
@ -100,7 +91,7 @@ ModelBuilder.prototype.getModelDefinition = function (name) {
* activated: Boolean
* });
* ```
* @example more advanced case
* More advanced example:
* ```
* var User = modelBuilder.define('User', {
* email: { type: String, limit: 150, index: true },
@ -110,6 +101,12 @@ ModelBuilder.prototype.getModelDefinition = function (name) {
* activated: { type: Boolean, default: false }
* });
* ```
*
* @param {String} className Name of class
* @param {Object} properties Hash of class properties in format `{property: Type, property2: Type2, ...}` or `{property: {type: Type}, property2: {type: Type2}, ...}`
* @param {Object} settings Other configuration of class
* @return newly created class
*
*/
ModelBuilder.prototype.define = function defineClass(className, properties, settings, parent) {
var modelBuilder = this;
@ -331,7 +328,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
/**
* Register a property for the model class
* @param propertyName
* @param {String} propertyName Name of the property.
*/
ModelClass.registerProperty = function (propertyName) {
var properties = modelDefinition.build();
@ -426,9 +423,9 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
/**
* Define single property named `propertyName` on `model`
*
* @param {String} model - name of model
* @param {String} propertyName - name of property
* @param {Object} propertyDefinition - property settings
* @param {String} model Name of model
* @param {String} propertyName Name of property
* @param {Object} propertyDefinition Property settings
*/
ModelBuilder.prototype.defineProperty = function (model, propertyName, propertyDefinition) {
this.definitions[model].defineProperty(propertyName, propertyDefinition);
@ -438,25 +435,25 @@ ModelBuilder.prototype.defineProperty = function (model, propertyName, propertyD
/**
* Extend existing model with bunch of properties
*
* @param {String} model - name of model
* @param {Object} props - hash of properties
*
* Example:
*
* // Instead of doing this:
*
* // amend the content model with competition attributes
* Instead of doing amending the content model with competition attributes like this:
*
* ```js
* db.defineProperty('Content', 'competitionType', { type: String });
* db.defineProperty('Content', 'expiryDate', { type: Date, index: true });
* db.defineProperty('Content', 'isExpired', { type: Boolean, index: true });
*
* // modelBuilder.extend allows to
* // extend the content model with competition attributes
*```
* The extendModel() method enables you to extend the content model with competition attributes.
* ```js
* db.extendModel('Content', {
* competitionType: String,
* expiryDate: { type: Date, index: true },
* isExpired: { type: Boolean, index: true }
* });
*```
*
* @param {String} model Name of model
* @param {Object} props Hash of properties
*/
ModelBuilder.prototype.extendModel = function (model, props) {
var t = this;
@ -523,9 +520,9 @@ ModelBuilder.prototype.getSchemaName = function (name) {
};
/**
* Resolve the type string to be a function, for example, 'String' to String
* Resolve the type string to be a function, for example, 'String' to String.
* Returns {Function} if the type is resolved
* @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) {
@ -620,10 +617,10 @@ ModelBuilder.prototype.buildModels = function (schemas) {
};
/**
* Introspect the json document to build a corresponding model
* Introspect the JSON document to build a corresponding model.
* @param {String} name The model name
* @param {Object} json The json object
* @param [Object} options The options
* @param {Object} json The JSON object
* @param {Object} options The options
* @returns {}
*/
ModelBuilder.prototype.buildModelFromInstance = function (name, json, options) {

View File

@ -1,9 +1,9 @@
/**
/*!
* Module exports class Model
*/
module.exports = ModelBaseClass;
/**
/*!
* Module dependencies
*/
@ -17,15 +17,12 @@ var validations = require('./validations.js');
var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text'];
/**
* Model class - base class for all persist objects
* provides **common API** to access any database connector.
* This class describes only abstract behavior layer, refer to `lib/connectors/*.js`
* to learn more about specific connector implementations
* Model class: base class for all persistent objects.
*
* `ModelBaseClass` mixes `Validatable` and `Hookable` classes methods
*
* @constructor
* @param {Object} data - initial object data
* @class
* @param {Object} data Initial object data
*/
function ModelBaseClass(data, options) {
options = options || {};
@ -119,7 +116,10 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
if (i in properties && typeof data[i] !== 'function') {
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];
if (ctor.relations[i].type === 'belongsTo' && data[i] !== null && data[i] !== undefined) {
// If the related model is populated
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo];
}
this.__cachedRelations[i] = data[i];
} else {
if (strict === false) {
@ -195,8 +195,9 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
};
/**
* @param {String} prop - property name
* @param {Object} params - various property configuration
* Define a property on the model.
* @param {String} prop Property name
* @param {Object} params Various property configuration
*/
ModelBaseClass.defineProperty = function (prop, params) {
this.dataSource.defineProperty(this.modelName, prop, params);
@ -221,20 +222,17 @@ ModelBaseClass.prototype.getPropertyType = function (propName) {
/**
* Return string representation of class
*
* @override default toString method
* This overrides the default `toString()` method
*/
ModelBaseClass.toString = function () {
return '[Model ' + this.modelName + ']';
};
/**
* Convert model instance to a plain JSON object
* Convert model instance to a plain JSON object.
* Returns a canonical object representation (no getters and setters).
*
* @param {Boolean} onlySchema - restrict properties to dataSource only,
* default to false. When onlySchema is true, only properties defined in
* the schema are returned, otherwise all enumerable properties returned
* @returns {Object} - canonical object representation (no getters and setters)
* @param {Boolean} onlySchema Restrict properties to dataSource only. Default is false. If true, the function returns only properties defined in the schema; Otherwise it returns all enumerable properties.
*/
ModelBaseClass.prototype.toObject = function (onlySchema) {
if(onlySchema === undefined) {
@ -303,7 +301,7 @@ ModelBaseClass.prototype.fromObject = function (obj) {
/**
* Checks is property changed based on current property and initial value
*
* @param {String} propertyName - property name
* @param {String} propertyName Property name
* @return Boolean
*/
ModelBaseClass.prototype.propertyChanged = function propertyChanged(propertyName) {
@ -311,10 +309,9 @@ ModelBaseClass.prototype.propertyChanged = function propertyChanged(propertyName
};
/**
* Reset dirty attributes
*
* this method does not perform any database operation it just reset object to it's
* initial state
* Reset dirty attributes.
* This method does not perform any database operations; it just resets the object to its
* initial state.
*/
ModelBaseClass.prototype.reset = function () {
var obj = this;

View File

@ -1,4 +1,4 @@
/**
/*!
* Dependencies
*/
var i8n = require('inflection');
@ -7,9 +7,19 @@ var ModelBaseClass = require('./model.js');
module.exports = Relation;
/**
* Relations class
*
* @class Relation
*/
function Relation() {
}
/**
* Find the relation by foreign key
* @param {*} foreignKey The foreign key
* @returns {Object} The relation object
*/
Relation.relationNameFor = function relationNameFor(foreignKey) {
for (var rel in this.relations) {
if (this.relations[rel].type === 'belongsTo' && this.relations[rel].keyFrom === foreignKey) {
@ -18,6 +28,12 @@ Relation.relationNameFor = function relationNameFor(foreignKey) {
}
};
/*!
* Look up a model by name from the list of given models
* @param {Object} models Models keyed by name
* @param {String} modelName The model name
* @returns {*} The matching model class
*/
function lookupModel(models, modelName) {
if(models[modelName]) {
return models[modelName];
@ -31,11 +47,12 @@ function lookupModel(models, modelName) {
}
/**
* Declare hasMany relation
*
* @param {Relation} anotherClass - class to has many
* @param {Object} params - configuration {as:, foreignKey:}
* @example `User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});`
* Declare "hasMany" relation.
* Example:
* ```User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});```
*
* @param {Relation} anotherClass Class to has many
* @param {Object} params Configuration {as:, foreignKey:}
*/
Relation.hasMany = function hasMany(anotherClass, params) {
var thisClassName = this.modelName;
@ -61,15 +78,22 @@ Relation.hasMany = function hasMany(anotherClass, params) {
modelTo: anotherClass,
multiple: true
};
if (params.through) {
this.relations[methodName].modelThrough = params.through;
}
// 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
findById: findById,
destroy: destroyById
};
if (params.through) {
var fk2 = i8n.camelize(anotherClass.modelName + '_id', true);
// Create an instance of the target model and connect it to the instance of
// the source model by creating an instance of the through model
scopeMethods.create = function create(data, done) {
if (typeof data !== 'object') {
done = data;
@ -80,13 +104,16 @@ Relation.hasMany = function hasMany(anotherClass, params) {
};
}
var self = this;
// First create the target model
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;
// Then create the through model
params.through.create(d, function (e) {
if (e) {
// Undo creation of the target model
ac.destroy(function () {
done(e);
});
@ -96,6 +123,11 @@ Relation.hasMany = function hasMany(anotherClass, params) {
});
});
};
/**
* Add the target model instance to the 'hasMany' relation
* @param {Object|ID) acInst The actual instance or id value
*/
scopeMethods.add = function (acInst, done) {
var data = {};
var query = {};
@ -103,8 +135,14 @@ Relation.hasMany = function hasMany(anotherClass, params) {
data[params.through.relationNameFor(fk)] = this;
query[fk2] = acInst[idName] || acInst;
data[params.through.relationNameFor(fk2)] = acInst;
// Create an instance of the through model
params.through.findOrCreate({where: query}, data, done);
};
/**
* Remove the target model instance from the 'hasMany' relation
* @param {Object|ID) acInst The actual instance or id value
*/
scopeMethods.remove = function (acInst, done) {
var q = {};
q[fk2] = acInst[idName] || acInst;
@ -118,8 +156,12 @@ Relation.hasMany = function hasMany(anotherClass, params) {
d.destroy(done);
});
};
// No destroy method will be injected
delete scopeMethods.destroy;
}
// Mix the property and scoped methods into the prototype class
defineScope(this.prototype, params.through || anotherClass, methodName, function () {
var filter = {};
filter.where = {};
@ -136,7 +178,8 @@ Relation.hasMany = function hasMany(anotherClass, params) {
anotherClass.dataSource.defineForeignKey(anotherClass.modelName, fk, this.modelName);
}
function find(id, cb) {
// Find the target model instance by id
function findById(id, cb) {
anotherClass.findById(id, function (err, inst) {
if (err) {
return cb(err);
@ -144,6 +187,7 @@ Relation.hasMany = function hasMany(anotherClass, params) {
if (!inst) {
return cb(new Error('Not found'));
}
// Check if the foreign key matches the primary key
if (inst[fk] && inst[fk].toString() === this[idName].toString()) {
cb(null, inst);
} else {
@ -152,7 +196,8 @@ Relation.hasMany = function hasMany(anotherClass, params) {
}.bind(this));
}
function destroy(id, cb) {
// Destroy the target model instance by id
function destroyById(id, cb) {
var self = this;
anotherClass.findById(id, function (err, inst) {
if (err) {
@ -161,6 +206,7 @@ Relation.hasMany = function hasMany(anotherClass, params) {
if (!inst) {
return cb(new Error('Not found'));
}
// Check if the foreign key matches the primary key
if (inst[fk] && inst[fk].toString() === self[idName].toString()) {
inst.destroy(cb);
} else {
@ -172,27 +218,34 @@ Relation.hasMany = function hasMany(anotherClass, params) {
};
/**
* Declare belongsTo relation
* Declare "belongsTo" relation.
*
* @param {Class} anotherClass - class to belong
* @param {Object} params - configuration {as: 'propertyName', foreignKey: 'keyName'}
*
* **Usage examples**
* Suppose model Post have a *belongsTo* relationship with User (the author of the post). You could declare it this way:
* **Examples**
*
* Suppose the model Post has a *belongsTo* relationship with User (the author of the post). You could declare it this way:
* ```js
* Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
* ```
*
* When a post is loaded, you can load the related author with:
* ```js
* post.author(function(err, user) {
* // the user variable is your user object
* });
* ```
*
* The related object is cached, so if later you try to get again the author, no additional request will be made.
* But there is an optional boolean parameter in first position that set whether or not you want to reload the cache:
* ```js
* post.author(true, function(err, user) {
* // The user is reloaded, even if it was already cached.
* });
*
* ```
* This optional parameter default value is false, so the related object will be loaded from cache if available.
*
* @param {Class} anotherClass Class to belong
* @param {Object} params Configuration {as: 'propertyName', foreignKey: 'keyName'}
*
*/
Relation.belongsTo = function (anotherClass, params) {
params = params || {};
@ -221,6 +274,8 @@ Relation.belongsTo = function (anotherClass, params) {
this.dataSource.defineForeignKey(this.modelName, fk, anotherClass.modelName);
this.prototype.__finders__ = this.prototype.__finders__ || {};
// Set up a finder to find by id and make sure the foreign key of the declaring
// model matches the primary key of the target model
this.prototype.__finders__[methodName] = function (id, cb) {
if (id === null) {
cb(null, null);
@ -233,6 +288,7 @@ Relation.belongsTo = function (anotherClass, params) {
if (!inst) {
return cb(null, null);
}
// Check if the foreign key matches the primary key
if (inst[idName] === this[fk]) {
cb(null, inst);
} else {
@ -241,7 +297,12 @@ Relation.belongsTo = function (anotherClass, params) {
}.bind(this));
};
this.prototype[methodName] = function (refresh, p) {
// Define the method for the belongsTo relation itself
// It will support one of the following styles:
// - order.customer(refresh, callback): Load the target model instance asynchronously
// - order.customer(customer): Synchronous setter of the target model instance
// - order.customer(): Synchronous getter of the target model instance
var relationMethod = function (refresh, p) {
if (arguments.length === 1) {
p = refresh;
refresh = false;
@ -277,21 +338,54 @@ Relation.belongsTo = function (anotherClass, params) {
}
};
// Set the remoting metadata so that it can be accessed as /api/<model>/<id>/<belongsToRelationName>
// For example, /api/orders/1/customer
var fn = this.prototype[methodName];
fn.shared = true;
fn.http = {verb: 'get', path: '/' + methodName};
fn.accepts = {arg: 'refresh', type: 'boolean', http: {source: 'query'}};
fn.description = 'Fetches belongsTo relation ' + methodName;
fn.returns = {arg: methodName, type: 'object', root: true};
// Define a property for the scope so that we have 'this' for the scoped methods
Object.defineProperty(this.prototype, methodName, {
enumerable: false,
configurable: true,
get: function () {
var fn = relationMethod.bind(this);
// Set the remoting metadata so that it can be accessed as /api/<model>/<id>/<belongsToRelationName>
// For example, /api/orders/1/customer
fn.shared = true;
fn.http = {verb: 'get', path: '/' + methodName};
fn.accepts = {arg: 'refresh', type: 'boolean', http: {source: 'query'}};
fn.description = 'Fetches belongsTo relation ' + methodName;
fn.returns = {arg: methodName, type: 'object', root: true};
// Create an instance of the target model and set the foreign key of the
// declaring model instance to the id of the target instance
fn.create = function(targetModelData, cb) {
var self = this;
anotherClass.create(targetModelData, function(err, targetModel) {
if(!err) {
self[fk] = targetModel[idName];
cb && cb(err, targetModel);
} else {
cb && cb(err);
}
});
}.bind(this);
// Build an instance of the target model
fn.build = function(targetModelData) {
return new anotherClass(targetModelData);
}.bind(this);
return fn;
}});
};
/**
* Many-to-many relation
*
* Post.hasAndBelongsToMany('tags'); creates connection model 'PostTag'
* For example, this creates connection model 'PostTag':
* ```js
* Post.hasAndBelongsToMany('tags');
* ```
* @param {String|Function} anotherClass - target class to hasAndBelongsToMany or name of
* the relation
* @param {Object} params - configuration {as: String, foreignKey: *, model: ModelClass}
*/
Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params) {
params = params || {};

View File

@ -5,6 +5,15 @@ var defineCachedRelations = utils.defineCachedRelations;
*/
exports.defineScope = defineScope;
/**
* Define a scope to the class
* @param {Model} cls The class where the scope method is added
* @param {Model} targetClass The class that a query to run against
* @param {String} name The name of the scope
* @param {Object|Function} params The parameters object for the query or a function
* to return the query object
* @param methods An object of methods keyed by the method name to be bound to the class
*/
function defineScope(cls, targetClass, name, params, methods) {
// collect meta info about scope
@ -38,6 +47,7 @@ function defineScope(cls, targetClass, name, params, methods) {
*
*/
get: function () {
var self = this;
var f = function caller(condOrRefresh, cb) {
var actualCond = {};
var actualRefresh = false;
@ -56,8 +66,9 @@ function defineScope(cls, targetClass, name, params, methods) {
throw new Error('Method can be only called with one or two arguments');
}
if (!this.__cachedRelations || (this.__cachedRelations[name] === undefined) || actualRefresh) {
var self = this;
if (!self.__cachedRelations || self.__cachedRelations[name] === undefined
|| actualRefresh) {
// It either doesn't hit the cache or reresh is required
var params = mergeParams(actualCond, caller._scope);
return targetClass.find(params, function (err, data) {
if (!err && saveOnCache) {
@ -67,7 +78,8 @@ function defineScope(cls, targetClass, name, params, methods) {
cb(err, data);
});
} else {
cb(null, this.__cachedRelations[name]);
// Return from cache
cb(null, self.__cachedRelations[name]);
}
};
f._scope = typeof params === 'function' ? params.call(this) : params;

View File

@ -5,7 +5,7 @@ module.exports = BaseSQL;
/**
* Base class for connectors that are backed by relational databases/SQL
* @constructor
* @class
*/
function BaseSQL() {
Connector.apply(this, [].slice.call(arguments));
@ -21,7 +21,7 @@ BaseSQL.prototype.relational = true;
/**
* Get types associated with the connector
* @returns {String[]} The types for the connector
* Returns {String[]} The types for the connector
*/
BaseSQL.prototype.getTypes = function() {
return ['db', 'rdbms', 'sql'];
@ -29,7 +29,7 @@ BaseSQL.prototype.relational = true;
/*!
* Get the default data type for ID
* @returns {Function}
* Returns {Function}
*/
BaseSQL.prototype.getDefaultIdType = function() {
return Number;
@ -51,9 +51,9 @@ BaseSQL.prototype.queryOne = function (sql, callback) {
};
/**
* Get the table name for a given model
* Get the table name for a given model.
* Returns the table name (String).
* @param {String} model The model name
* @returns {String} The table name
*/
BaseSQL.prototype.table = function (model) {
var name = this.getDataSource(model).tableName(model);
@ -194,8 +194,11 @@ BaseSQL.prototype.exists = function (model, id, callback) {
* @param {Function} callback The callback function
*/
BaseSQL.prototype.find = function find(model, id, callback) {
var idQuery = (id === null || id === undefined)
? this.idColumnEscaped(model) + ' IS NULL'
: this.idColumnEscaped(model) + ' = ' + id;
var sql = 'SELECT * FROM ' +
this.tableEscaped(model) + ' WHERE ' + this.idColumnEscaped(model) + ' = ' + id + ' LIMIT 1';
this.tableEscaped(model) + ' WHERE ' + idQuery + ' LIMIT 1';
this.query(sql, function (err, data) {
if (data && data.length === 1) {

View File

@ -1,15 +1,15 @@
var util = require('util');
/**
/*!
* Module exports
*/
exports.ValidationError = ValidationError;
exports.Validatable = Validatable;
/**
* Validation mixins for model.js
* Validation mixins for LoopBack models.
*
* Basically validation configurators is just class methods, which adds validations
* configs to AbstractClass._validations. Each of this validations run when
* `obj.isValid()` method called.
* This class provides methods that add validation cababilities to models.
* Each of this validations run when `obj.isValid()` method called.
*
* Each configurator can accept n params (n-1 field names and one config). Config
* is {Object} depends on specific validation, but all of them has one common part:
@ -18,9 +18,8 @@ exports.ValidationError = ValidationError;
*
* In more complicated cases it can be {Hash} of messages (for each case):
* `User.validatesLengthOf('password', { min: 6, max: 20, message: {min: 'too short', max: 'too long'}});`
* @class Validatable
*/
exports.Validatable = Validatable;
function Validatable() {
}
@ -29,19 +28,15 @@ function Validatable() {
*
* Default error message "can't be blank"
*
* @example presence of title
* For example, validate presence of title
* ```
* Post.validatesPresenceOf('title');
* ```
* @example with custom message
*Example with custom message
* ```
* Post.validatesPresenceOf('title', {message: 'Can not be blank'});
* Post.validatesPresenceOf('title', {message: 'Cannot be blank'});
* ```
*
* @sync
*
* @nocode
* @see helper/validatePresence
*/
Validatable.validatesPresenceOf = getConfigurator('presence');
@ -54,29 +49,25 @@ Validatable.validatesPresenceOf = getConfigurator('presence');
* - max: too long
* - is: length is wrong
*
* @example length validations
* Example: length validations
* ```
* User.validatesLengthOf('password', {min: 7});
* User.validatesLengthOf('email', {max: 100});
* User.validatesLengthOf('state', {is: 2});
* User.validatesLengthOf('nick', {min: 3, max: 15});
* ```
* @example length validations with custom error messages
* Example: length validations with custom error messages
* ```
* User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}});
* User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}});
* ```
*
* @sync
* @nocode
* @see helper/validateLength
*/
Validatable.validatesLengthOf = getConfigurator('length');
/**
* Validate numericality.
*
* @example
* Example
* ```
* User.validatesNumericalityOf('age', { message: { number: '...' }});
* User.validatesNumericalityOf('age', {int: true, message: { int: '...' }});
@ -96,7 +87,7 @@ Validatable.validatesNumericalityOf = getConfigurator('numericality');
/**
* Validate inclusion in set
*
* @example
* Example:
* ```
* User.validatesInclusionOf('gender', {in: ['male', 'female']});
* User.validatesInclusionOf('role', {
@ -115,7 +106,7 @@ Validatable.validatesInclusionOf = getConfigurator('inclusion');
/**
* Validate exclusion
*
* @example `Company.validatesExclusionOf('domain', {in: ['www', 'admin']});`
* Example: `Company.validatesExclusionOf('domain', {in: ['www', 'admin']});`
*
* Default error message: is reserved
*
@ -161,7 +152,7 @@ Validatable.validate = getConfigurator('custom');
* Default error message: is invalid
*
* Example:
*
*```js
* User.validateAsync('name', customValidator, {message: 'Bad name'});
* function customValidator(err, done) {
* process.nextTick(function () {
@ -179,7 +170,7 @@ Validatable.validate = getConfigurator('custom');
* user.isValid(function (isValid) {
* isValid; // false
* })
*
*```
* @async
* @nocode
* @see helper/validateCustom
@ -329,16 +320,16 @@ function getConfigurator(name, opts) {
* @warning This method can be called as sync only when no async validation
* configured. It's strongly recommended to run all validations as asyncronous.
*
* @param {Function} callback called with (valid)
* @return {Boolean} true if no async validation configured and all passed
* Returns true if no async validation configured and all passed
*
* @example ExpressJS controller: render user if valid, show flash otherwise
* Example: ExpressJS controller: render user if valid, show flash otherwise
* ```
* user.isValid(function (valid) {
* if (valid) res.render({user: user});
* else res.flash('error', 'User is not valid'), console.log(user.errors), res.redirect('/users');
* });
* ```
* @param {Function} callback called with (valid)
*/
Validatable.prototype.isValid = function (callback, data) {
var valid = true, inst = this, wait = 0, async = false;
@ -535,7 +526,7 @@ function nullCheck(attr, conf, err) {
* otherwise returns false
*
* @param {Mix} v
* @returns {Boolean} whether `v` blank or not
* Returns true if `v` is blank.
*/
function blank(v) {
if (typeof v === 'undefined') return true;

View File

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

View File

@ -1,15 +1,14 @@
// This test written in mocha+should.js
var should = require('./init.js');
var db, User, Post, Passport, City, Street, Building;
var nbSchemaRequests = 0;
var db, User, Post, Passport, City, Street, Building, Assembly, Part;
describe('include', function () {
before(setup);
it('should fetch belongsTo relation', function (done) {
Passport.all({include: 'owner'}, function (err, passports) {
Passport.find({include: 'owner'}, function (err, passports) {
passports.length.should.be.ok;
passports.forEach(function (p) {
p.__cachedRelations.should.have.property('owner');
@ -32,7 +31,7 @@ describe('include', function () {
});
it('should fetch hasMany relation', function (done) {
User.all({include: 'posts'}, function (err, users) {
User.find({include: 'posts'}, function (err, users) {
should.not.exist(err);
should.exist(users);
users.length.should.be.ok;
@ -52,7 +51,7 @@ describe('include', function () {
});
it('should fetch Passport - Owner - Posts', function (done) {
Passport.all({include: {owner: 'posts'}}, function (err, passports) {
Passport.find({include: {owner: 'posts'}}, function (err, passports) {
should.not.exist(err);
should.exist(passports);
passports.length.should.be.ok;
@ -81,7 +80,7 @@ describe('include', function () {
});
it('should fetch Passports - User - Posts - User', function (done) {
Passport.all({
Passport.find({
include: {owner: {posts: 'author'}}
}, function (err, passports) {
should.not.exist(err);
@ -109,7 +108,7 @@ describe('include', function () {
});
it('should fetch User - Posts AND Passports', function (done) {
User.all({include: ['posts', 'passports']}, function (err, users) {
User.find({include: ['posts', 'passports']}, function (err, users) {
should.not.exist(err);
should.exist(users);
users.length.should.be.ok;
@ -140,6 +139,39 @@ describe('include', function () {
});
});
it('should support hasAndBelongsToMany', function (done) {
Assembly.destroyAll(function(err) {
Part.destroyAll(function(err) {
Assembly.relations.parts.modelThrough.destroyAll(function(err) {
Assembly.create({name: 'car'}, function (err, assembly) {
Part.create({partNumber: 'engine'}, function (err, part) {
assembly.parts.add(part, function (err, data) {
assembly.parts(function (err, parts) {
should.not.exist(err);
should.exists(parts);
parts.length.should.equal(1);
parts[0].partNumber.should.equal('engine');
// Create a part
assembly.parts.create({partNumber: 'door'}, function (err, part4) {
Assembly.find({include: 'parts'}, function (err, assemblies) {
assemblies.length.should.equal(1);
assemblies[0].parts.length.should.equal(2);
done();
});
});
});
});
});
});
});
});
});
});
});
function setup(done) {
@ -163,6 +195,17 @@ function setup(done) {
User.hasMany('posts', {foreignKey: 'userId'});
Post.belongsTo('author', {model: User, foreignKey: 'userId'});
Assembly = db.define('Assembly', {
name: String
});
Part = db.define('Part', {
partNumber: String
});
Assembly.hasAndBelongsToMany(Part);
Part.hasAndBelongsToMany(Assembly);
db.automigrate(function () {
var createdUsers = [];
var createdPassports = [];

View File

@ -615,9 +615,17 @@ describe('DataSource connector types', function() {
describe('DataSource constructor', function () {
// Mocked require
var loader = function (name) {
if (name.indexOf('./connectors/') !== -1) {
// ./connectors/<name> doesn't exist
return null;
}
if (name === 'loopback-connector-abc') {
// Assume loopback-connector-abc doesn't exist
return null;
}
return {
name: name
}
};
};
it('should resolve connector by path', function () {
@ -632,9 +640,20 @@ describe('DataSource constructor', function () {
var connector = DataSource._resolveConnector('loopback-connector-xyz', loader);
assert(connector.connector);
});
it('should try to resolve connector by short module name', function () {
it('should try to resolve connector by short module name with full name first', function () {
var connector = DataSource._resolveConnector('xyz', loader);
assert(connector.connector);
assert.equal(connector.connector.name, 'loopback-connector-xyz');
});
it('should try to resolve connector by short module name', function () {
var connector = DataSource._resolveConnector('abc', loader);
assert(connector.connector);
assert.equal(connector.connector.name, 'abc');
});
it('should try to resolve connector by short module name for known connectors', function () {
var connector = DataSource._resolveConnector('oracle', loader);
assert(connector.connector);
assert.equal(connector.connector.name, 'loopback-connector-oracle');
});
it('should try to resolve connector by full module name', function () {
var connector = DataSource._resolveConnector('loopback-xyz', loader);