Merge pull request #268 from fabien/feature/transient-model
Transient connector implementation
This commit is contained in:
commit
97827db59d
|
@ -6,7 +6,7 @@ var fs = require('fs');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the Oracle connector against the given data source
|
* Initialize the Memory connector against the given data source
|
||||||
*
|
*
|
||||||
* @param {DataSource} dataSource The loopback-datasource-juggler dataSource
|
* @param {DataSource} dataSource The loopback-datasource-juggler dataSource
|
||||||
* @param {Function} [callback] The callback function
|
* @param {Function} [callback] The callback function
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
var util = require('util');
|
||||||
|
var Connector = require('loopback-connector').Connector;
|
||||||
|
var utils = require('../utils');
|
||||||
|
var crypto = require('crypto');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Transient connector against the given data source
|
||||||
|
*
|
||||||
|
* @param {DataSource} dataSource The loopback-datasource-juggler dataSource
|
||||||
|
* @param {Function} [callback] The callback function
|
||||||
|
*/
|
||||||
|
exports.initialize = function initializeDataSource(dataSource, callback) {
|
||||||
|
dataSource.connector = new Transient(null, dataSource.settings);
|
||||||
|
dataSource.connector.connect(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Transient = Transient;
|
||||||
|
|
||||||
|
function Transient(m, settings) {
|
||||||
|
settings = settings || {};
|
||||||
|
if (typeof settings.generateId === 'function') {
|
||||||
|
this.generateId = settings.generateId.bind(this);
|
||||||
|
}
|
||||||
|
this.defaultIdType = settings.defaultIdType || String;
|
||||||
|
if (m instanceof Transient) {
|
||||||
|
this.isTransaction = true;
|
||||||
|
this.constructor.super_.call(this, 'transient', settings);
|
||||||
|
this._models = m._models;
|
||||||
|
} else {
|
||||||
|
this.isTransaction = false;
|
||||||
|
this.constructor.super_.call(this, 'transient', settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(Transient, Connector);
|
||||||
|
|
||||||
|
Transient.prototype.getDefaultIdType = function() {
|
||||||
|
return this.defaultIdType;
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.getTypes = function() {
|
||||||
|
return ['db', 'nosql', 'transient'];
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.connect = function (callback) {
|
||||||
|
if (this.isTransaction) {
|
||||||
|
this.onTransactionExec = callback;
|
||||||
|
} else {
|
||||||
|
process.nextTick(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.generateId = function(model, data, idName) {
|
||||||
|
var idType;
|
||||||
|
var props = this._models[model].properties;
|
||||||
|
if (idName) idType = props[idName] && props[idName].type;
|
||||||
|
idType = idType || this.getDefaultIdType();
|
||||||
|
if (idType === Number) {
|
||||||
|
return Math.floor(Math.random() * 10000); // max. 4 digits
|
||||||
|
} else {
|
||||||
|
return crypto.randomBytes(Math.ceil(24/2))
|
||||||
|
.toString('hex') // convert to hexadecimal format
|
||||||
|
.slice(0, 24); // return required number of characters
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.exists = function exists(model, id, callback) {
|
||||||
|
process.nextTick(function () { callback(null, false); }.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.find = function find(model, id, callback) {
|
||||||
|
process.nextTick(function () { callback(null, null); }.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.all = function all(model, filter, callback) {
|
||||||
|
process.nextTick(function () { callback(null, []); });
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.count = function count(model, callback, where) {
|
||||||
|
process.nextTick(function () { callback(null, 0); });
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.create = function create(model, data, callback) {
|
||||||
|
var props = this._models[model].properties;
|
||||||
|
var idName = this.idName(model);
|
||||||
|
if (idName && props[idName]) {
|
||||||
|
var id = this.getIdValue(model, data) || this.generateId(model, data, idName);
|
||||||
|
id = (props[idName] && props[idName].type && props[idName].type(id)) || id;
|
||||||
|
this.setIdValue(model, data, id);
|
||||||
|
}
|
||||||
|
this.flush('create', id, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.save = function save(model, data, callback) {
|
||||||
|
this.flush('save', data, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.update =
|
||||||
|
Transient.prototype.updateAll = function updateAll(model, where, data, cb) {
|
||||||
|
this.flush('update', null, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.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);
|
||||||
|
this.save(model, data, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.destroy = function destroy(model, id, callback) {
|
||||||
|
this.flush('destroy', null, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.destroyAll = function destroyAll(model, where, callback) {
|
||||||
|
if (!callback && 'function' === typeof where) {
|
||||||
|
callback = where;
|
||||||
|
where = undefined;
|
||||||
|
}
|
||||||
|
this.flush('destroyAll', null, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Flush the cache - noop.
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
Transient.prototype.flush = function (action, result, callback) {
|
||||||
|
process.nextTick(function () { callback && callback(null, result); });
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.transaction = function () {
|
||||||
|
return new Transient(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transient.prototype.exec = function (callback) {
|
||||||
|
this.onTransactionExec();
|
||||||
|
setTimeout(callback, 50);
|
||||||
|
};
|
|
@ -1842,8 +1842,9 @@ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelTo, params)
|
||||||
type: [modelTo], default: function() { return []; }
|
type: [modelTo], default: function() { return []; }
|
||||||
});
|
});
|
||||||
|
|
||||||
// unique id is required
|
if (typeof modelTo.dataSource.connector.generateId !== 'function') {
|
||||||
modelTo.validatesPresenceOf(idName);
|
modelTo.validatesPresenceOf(idName); // unique id is required
|
||||||
|
}
|
||||||
|
|
||||||
if (!params.polymorphic) {
|
if (!params.polymorphic) {
|
||||||
modelFrom.validate(propertyName, function(err) {
|
modelFrom.validate(propertyName, function(err) {
|
||||||
|
@ -2143,7 +2144,6 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var propertyName = this.definition.keyFrom;
|
var propertyName = this.definition.keyFrom;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
var autoId = this.definition.options.autoId !== false;
|
|
||||||
|
|
||||||
if (typeof targetModelData === 'function' && !cb) {
|
if (typeof targetModelData === 'function' && !cb) {
|
||||||
cb = targetModelData;
|
cb = targetModelData;
|
||||||
|
@ -2170,16 +2170,25 @@ EmbedsMany.prototype.create = function (targetModelData, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbedsMany.prototype.build = function(targetModelData) {
|
EmbedsMany.prototype.build = function(targetModelData) {
|
||||||
var pk = this.definition.keyTo;
|
|
||||||
var modelTo = this.definition.modelTo;
|
var modelTo = this.definition.modelTo;
|
||||||
var modelInstance = this.modelInstance;
|
var modelInstance = this.modelInstance;
|
||||||
var autoId = this.definition.options.autoId !== false;
|
var forceId = this.definition.options.forceId;
|
||||||
|
var connector = modelTo.dataSource.connector;
|
||||||
|
|
||||||
|
var pk = this.definition.keyTo;
|
||||||
|
var pkProp = modelTo.definition.properties[pk]
|
||||||
|
var pkType = pkProp && pkProp.type;
|
||||||
|
|
||||||
var embeddedList = this.embeddedList();
|
var embeddedList = this.embeddedList();
|
||||||
|
|
||||||
targetModelData = targetModelData || {};
|
targetModelData = targetModelData || {};
|
||||||
|
|
||||||
if (typeof targetModelData[pk] !== 'number' && autoId) {
|
var assignId = (forceId || targetModelData[pk] === undefined);
|
||||||
|
|
||||||
|
if (assignId && typeof connector.generateId === 'function') {
|
||||||
|
var id = connector.generateId(modelTo.modelName, targetModelData, pk);
|
||||||
|
targetModelData[pk] = id;
|
||||||
|
} else if (assignId && pkType === Number) {
|
||||||
var ids = embeddedList.map(function(m) {
|
var ids = embeddedList.map(function(m) {
|
||||||
return (typeof m[pk] === 'number' ? m[pk] : 0);
|
return (typeof m[pk] === 'number' ? m[pk] : 0);
|
||||||
});
|
});
|
||||||
|
|
11
test/init.js
11
test/init.js
|
@ -12,10 +12,17 @@ module.exports = require('should');
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var ModelBuilder = require('../').ModelBuilder;
|
||||||
var Schema = require('../').Schema;
|
var Schema = require('../').Schema;
|
||||||
|
|
||||||
if (!('getSchema' in global)) {
|
if (!('getSchema' in global)) {
|
||||||
global.getSchema = function () {
|
global.getSchema = function (connector, settings) {
|
||||||
return new Schema('memory');
|
return new Schema(connector || 'memory', settings);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!('getModelBuilder' in global)) {
|
||||||
|
global.getModelBuilder = function () {
|
||||||
|
return new ModelBuilder();
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
// This test written in mocha+should.js
|
// This test written in mocha+should.js
|
||||||
var should = require('./init.js');
|
var should = require('./init.js');
|
||||||
|
|
||||||
var db, Book, Chapter, Author, Reader;
|
var db, tmp, Book, Chapter, Author, Reader;
|
||||||
var Category, Job;
|
var Category, Job;
|
||||||
var Picture, PictureLink;
|
var Picture, PictureLink;
|
||||||
var Person, Address;
|
var Person, Address;
|
||||||
|
@ -1486,9 +1486,10 @@ describe('relations', function () {
|
||||||
var Other;
|
var Other;
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
|
tmp = getSchema('transient');
|
||||||
db = getSchema();
|
db = getSchema();
|
||||||
Person = db.define('Person', {name: String});
|
Person = db.define('Person', {name: String});
|
||||||
Passport = db.define('Passport',
|
Passport = tmp.define('Passport',
|
||||||
{name:{type:'string', required: true}},
|
{name:{type:'string', required: true}},
|
||||||
{idInjection: false}
|
{idInjection: false}
|
||||||
);
|
);
|
||||||
|
@ -1634,9 +1635,10 @@ describe('relations', function () {
|
||||||
var address1, address2;
|
var address1, address2;
|
||||||
|
|
||||||
before(function (done) {
|
before(function (done) {
|
||||||
|
tmp = getSchema('transient', {defaultIdType: Number});
|
||||||
db = getSchema();
|
db = getSchema();
|
||||||
Person = db.define('Person', {name: String});
|
Person = db.define('Person', {name: String});
|
||||||
Address = db.define('Address', {street: String});
|
Address = tmp.define('Address', {street: String});
|
||||||
Address.validatesPresenceOf('street');
|
Address.validatesPresenceOf('street');
|
||||||
|
|
||||||
db.automigrate(function () {
|
db.automigrate(function () {
|
||||||
|
@ -1813,9 +1815,10 @@ describe('relations', function () {
|
||||||
|
|
||||||
describe('embedsMany - explicit ids', function () {
|
describe('embedsMany - explicit ids', function () {
|
||||||
before(function (done) {
|
before(function (done) {
|
||||||
|
tmp = getSchema('transient');
|
||||||
db = getSchema();
|
db = getSchema();
|
||||||
Person = db.define('Person', {name: String});
|
Person = db.define('Person', {name: String});
|
||||||
Address = db.define('Address', {id: { type: String, id: true }, street: String});
|
Address = tmp.define('Address', {street: String});
|
||||||
Address.validatesPresenceOf('street');
|
Address.validatesPresenceOf('street');
|
||||||
|
|
||||||
db.automigrate(function () {
|
db.automigrate(function () {
|
||||||
|
@ -1824,13 +1827,13 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be declared', function (done) {
|
it('can be declared', function (done) {
|
||||||
Person.embedsMany(Address, { options: { autoId: false } });
|
Person.embedsMany(Address);
|
||||||
db.automigrate(done);
|
db.automigrate(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create embedded items on scope', function(done) {
|
it('should create embedded items on scope', function(done) {
|
||||||
Person.create({ name: 'Fred' }, function(err, p) {
|
Person.create({ name: 'Fred' }, function(err, p) {
|
||||||
p.addressList.create({ id: 'home', street: 'Street 1' }, function(err, addresses) {
|
p.addressList.create({ id: 'home', street: 'Street 1' }, function(err, address) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
p.addressList.create({ id: 'work', street: 'Work Street 2' }, function(err, address) {
|
p.addressList.create({ id: 'work', street: 'Work Street 2' }, function(err, address) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -1968,6 +1971,17 @@ describe('relations', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create embedded items with auto-generated id', function(done) {
|
||||||
|
Person.create({ name: 'Wilma' }, function(err, p) {
|
||||||
|
p.addressList.create({ street: 'Home Street 1' }, function(err, address) {
|
||||||
|
should.not.exist(err);
|
||||||
|
address.id.should.match(/^[0-9a-fA-F]{24}$/);
|
||||||
|
address.street.should.equal('Home Street 1');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('embedsMany - relations, scope and properties', function () {
|
describe('embedsMany - relations, scope and properties', function () {
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
var jdb = require('../');
|
||||||
|
var DataSource = jdb.DataSource;
|
||||||
|
var assert = require('assert');
|
||||||
|
var async = require('async');
|
||||||
|
var should = require('./init.js');
|
||||||
|
|
||||||
|
var db, TransientModel, Person, Widget, Item;
|
||||||
|
|
||||||
|
describe('Transient connector', function () {
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
db = getSchema('transient');
|
||||||
|
TransientModel = db.define('TransientModel', {}, { idInjection: false });
|
||||||
|
|
||||||
|
Person = TransientModel.extend('Person', {name: String});
|
||||||
|
Person.attachTo(db);
|
||||||
|
|
||||||
|
Widget = db.define('Widget', {name: String});
|
||||||
|
Item = db.define('Item', {
|
||||||
|
id: {type: Number, id: true}, name: String
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect idInjection being false', function(done) {
|
||||||
|
should.not.exist(Person.definition.properties.id);
|
||||||
|
should.exist(Person.definition.properties.name);
|
||||||
|
|
||||||
|
Person.create({ name: 'Wilma' }, function(err, inst) {
|
||||||
|
should.not.exist(err);
|
||||||
|
inst.toObject().should.eql({ name: 'Wilma' });
|
||||||
|
|
||||||
|
Person.count(function(err, count) {
|
||||||
|
should.not.exist(err);
|
||||||
|
count.should.equal(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a random string id', function(done) {
|
||||||
|
should.exist(Widget.definition.properties.id);
|
||||||
|
should.exist(Widget.definition.properties.name);
|
||||||
|
|
||||||
|
Widget.definition.properties.id.type.should.equal(String);
|
||||||
|
|
||||||
|
Widget.create({ name: 'Thing' }, function(err, inst) {
|
||||||
|
should.not.exist(err);
|
||||||
|
inst.id.should.match(/^[0-9a-fA-F]{24}$/);
|
||||||
|
inst.name.should.equal('Thing');
|
||||||
|
|
||||||
|
Widget.findById(inst.id, function(err, widget) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.not.exist(widget);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a random number id', function(done) {
|
||||||
|
should.exist(Item.definition.properties.id);
|
||||||
|
should.exist(Item.definition.properties.name);
|
||||||
|
|
||||||
|
Item.definition.properties.id.type.should.equal(Number);
|
||||||
|
|
||||||
|
Item.create({ name: 'Example' }, function(err, inst) {
|
||||||
|
should.not.exist(err);
|
||||||
|
inst.name.should.equal('Example');
|
||||||
|
|
||||||
|
Item.count(function(err, count) {
|
||||||
|
should.not.exist(err);
|
||||||
|
count.should.equal(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue