不要怂,就是干,撸起袖子干!

Commit eca67e85 by Mick Hansen Committed by Jan Aagaard Meier

One to one association batching (#5879)

* add(associations): make belongsTo getter batch friendly

* add(associations): make belongsTo getter batch friendly

* add(associations): make hasOne getter batch friendly

* variable

* readd scope for has one

* reintroduce options.limit = null to fix mssql
1 parent 80e1774d
...@@ -62,6 +62,7 @@ var BelongsTo = function(source, target, options) { ...@@ -62,6 +62,7 @@ var BelongsTo = function(source, target, options) {
this.targetKey = this.options.targetKey || this.target.primaryKeyAttribute; this.targetKey = this.options.targetKey || this.target.primaryKeyAttribute;
this.targetKeyField = this.target.rawAttributes[this.targetKey].field || this.targetKey; this.targetKeyField = this.target.rawAttributes[this.targetKey].field || this.targetKey;
this.targetKeyIsPrimary = this.targetKey === this.target.primaryKeyAttribute;
this.targetIdentifier = this.targetKey; this.targetIdentifier = this.targetKey;
this.associationAccessor = this.as; this.associationAccessor = this.as;
...@@ -132,42 +133,76 @@ BelongsTo.prototype.injectAttributes = function() { ...@@ -132,42 +133,76 @@ BelongsTo.prototype.injectAttributes = function() {
return this; return this;
}; };
// Add getAssociation method to the prototype of the model instance BelongsTo.prototype.mixin = function(obj) {
BelongsTo.prototype.injectGetter = function(instancePrototype) {
var association = this; var association = this;
instancePrototype[this.accessors.get] = function(options) { obj[this.accessors.get] = function(options) {
var where = {}; return association.get(this, options);
where[association.targetKey] = this.get(association.foreignKey); };
options = association.target.$optClone(options) || {}; association.injectSetter(obj);
association.injectCreator(obj);
};
options.where = { BelongsTo.prototype.get = function(instances, options) {
$and: [ var association = this
options.where, , Target = association.target
where , instance
] , where = {};
};
if (options.limit === undefined) options.limit = null; options = association.target.$optClone(options) || {};
var model = association.target; if (options.hasOwnProperty('scope')) {
if (options.hasOwnProperty('scope')) { if (!options.scope) {
if (!options.scope) { Target = Target.unscoped();
model = model.unscoped(); } else {
} else { Target = Target.scope(options.scope);
model = model.scope(options.scope);
}
} }
}
if (options.hasOwnProperty('schema')) { if (options.hasOwnProperty('schema')) {
model = model.schema(options.schema, options.schemaDelimiter); Target = Target.schema(options.schema, options.schemaDelimiter);
}
if (!Array.isArray(instances)) {
instance = instances;
instances = undefined;
}
if (instances) {
where[association.targetKey] = {
$in: instances.map(function (instance) {
return instance.get(association.foreignKey);
})
};
} else {
if (association.targetKeyIsPrimary && !options.where) {
return Target.findById(instance.get(association.foreignKey), options);
} else {
where[association.targetKey] = instance.get(association.foreignKey);
options.limit = null;
} }
}
return model.find(options); options.where = options.where ?
}; {$and: [where, options.where]} :
where;
return this; if (instances) {
return Target.findAll(options).then(function (results) {
var result = {};
instances.forEach(function (instance) {
result[instance.get(association.foreignKey, {raw: true})] = null;
});
results.forEach(function (instance) {
result[instance.get(association.targetKey, {raw: true})] = instance;
});
return result;
});
}
return Target.findOne(options);
}; };
// Add setAssociaton method to the prototype of the model instance // Add setAssociaton method to the prototype of the model instance
......
...@@ -55,6 +55,9 @@ var HasOne = function(srcModel, targetModel, options) { ...@@ -55,6 +55,9 @@ var HasOne = function(srcModel, targetModel, options) {
} }
this.sourceIdentifier = this.source.primaryKeyAttribute; this.sourceIdentifier = this.source.primaryKeyAttribute;
this.sourceKey = this.source.primaryKeyAttribute;
this.sourceKeyIsPrimary = this.sourceKey === this.source.primaryKeyAttribute;
this.associationAccessor = this.as; this.associationAccessor = this.as;
this.options.useHooks = options.useHooks; this.options.useHooks = options.useHooks;
...@@ -128,45 +131,75 @@ HasOne.prototype.injectAttributes = function() { ...@@ -128,45 +131,75 @@ HasOne.prototype.injectAttributes = function() {
return this; return this;
}; };
HasOne.prototype.injectGetter = function(instancePrototype) { HasOne.prototype.mixin = function(obj) {
var association = this; var association = this;
instancePrototype[this.accessors.get] = function(options) { obj[this.accessors.get] = function(options) {
var where = {}; return association.get(this, options);
where[association.foreignKey] = this.get(association.sourceIdentifier); };
if (association.scope) { association.injectSetter(obj);
_.assign(where, association.scope); association.injectCreator(obj);
};
HasOne.prototype.get = function(instances, options) {
var association = this
, Target = association.target
, instance
, where = {};
options = association.target.$optClone(options) || {};
if (options.hasOwnProperty('scope')) {
if (!options.scope) {
Target = Target.unscoped();
} else {
Target = Target.scope(options.scope);
} }
}
if (options.hasOwnProperty('schema')) {
Target = Target.schema(options.schema, options.schemaDelimiter);
}
options = association.target.__optClone(options) || {}; if (!Array.isArray(instances)) {
instance = instances;
instances = undefined;
}
options.where = { if (instances) {
$and: [ where[association.foreignKey] = {
options.where, $in: instances.map(function (instance) {
where return instance.get(association.sourceKey);
] })
}; };
} else {
where[association.foreignKey] = instance.get(association.sourceKey);
}
if (options.limit === undefined) options.limit = null; if (association.scope) {
_.assign(where, association.scope);
}
var model = association.target; options.where = options.where ?
if (options.hasOwnProperty('scope')) { {$and: [where, options.where]} :
if (!options.scope) { where;
model = model.unscoped();
} else {
model = model.scope(options.scope);
}
}
if (options.hasOwnProperty('schema')) { if (instances) {
model = model.schema(options.schema, options.schemaDelimiter); return Target.findAll(options).then(function (results) {
} var result = {};
instances.forEach(function (instance) {
result[instance.get(association.sourceKey, {raw: true})] = null;
});
return model.find(options); results.forEach(function (instance) {
}; result[instance.get(association.foreignKey, {raw: true})] = instance;
});
return this; return result;
});
}
return Target.findOne(options);
}; };
HasOne.prototype.injectSetter = function(instancePrototype) { HasOne.prototype.injectSetter = function(instancePrototype) {
......
...@@ -90,12 +90,12 @@ var Mixin = module.exports = function() {}; ...@@ -90,12 +90,12 @@ var Mixin = module.exports = function() {};
// The logic for hasOne and belongsTo is exactly the same // The logic for hasOne and belongsTo is exactly the same
var singleLinked = function (Type) { var singleLinked = function (Type) {
return function(targetModel, options) { // testhint options:none return function(target, options) { // testhint options:none
if (!(targetModel instanceof this.sequelize.Model)) { if (!(target instanceof this.sequelize.Model)) {
throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not an instance of Sequelize.Model'); throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not an instance of Sequelize.Model');
} }
var sourceModel = this; var source = this;
// Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option) // Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
options = options || {}; options = options || {};
...@@ -103,12 +103,16 @@ var singleLinked = function (Type) { ...@@ -103,12 +103,16 @@ var singleLinked = function (Type) {
options.useHooks = options.hooks; options.useHooks = options.hooks;
// the id is in the foreign table // the id is in the foreign table
var association = new Type(sourceModel, targetModel, _.extend(options, sourceModel.options)); var association = new Type(source, target, _.extend(options, source.options));
sourceModel.associations[association.associationAccessor] = association.injectAttributes(); source.associations[association.associationAccessor] = association.injectAttributes();
association.injectGetter(sourceModel.Instance.prototype); if (association.mixin) {
association.injectSetter(sourceModel.Instance.prototype); association.mixin(source.Instance.prototype);
association.injectCreator(sourceModel.Instance.prototype); } else {
association.injectGetter(source.Instance.prototype);
association.injectSetter(source.Instance.prototype);
association.injectCreator(source.Instance.prototype);
}
return association; return association;
}; };
......
...@@ -24,6 +24,43 @@ describe(Support.getTestDialectTeaser('BelongsTo'), function() { ...@@ -24,6 +24,43 @@ describe(Support.getTestDialectTeaser('BelongsTo'), function() {
}); });
}); });
describe('get', function () {
describe('multiple', function () {
it('should fetch associations for multiple instances', function () {
var User = this.sequelize.define('User', {})
, Task = this.sequelize.define('Task', {});
Task.User = Task.belongsTo(User, {as: 'user'});
return this.sequelize.sync({force: true}).then(function () {
return Promise.join(
Task.create({
id: 1,
user: {}
}, {
include: [Task.User]
}),
Task.create({
id: 2,
user: {}
}, {
include: [Task.User]
}),
Task.create({
id: 3
})
);
}).then(function (tasks) {
return Task.User.get(tasks).then(function (result) {
expect(result[tasks[0].id].id).to.equal(tasks[0].user.id);
expect(result[tasks[1].id].id).to.equal(tasks[1].user.id);
expect(result[tasks[2].id]).to.be.undefined;
});
});
});
});
});
describe('getAssociation', function() { describe('getAssociation', function() {
if (current.dialect.supports.transactions) { if (current.dialect.supports.transactions) {
...@@ -109,25 +146,6 @@ describe(Support.getTestDialectTeaser('BelongsTo'), function() { ...@@ -109,25 +146,6 @@ describe(Support.getTestDialectTeaser('BelongsTo'), function() {
expect(user).to.be.ok; expect(user).to.be.ok;
}); });
}); });
it('should support logging', function () {
var spy = sinon.spy();
var User = this.sequelize.define('user', {})
, Project = this.sequelize.define('project', {});
User.belongsTo(Project);
return this.sequelize.sync({ force: true }).bind(this).then(function() {
return User.create({});
}).then(function(user) {
return user.getProject({
logging: spy
});
}).then(function() {
expect(spy.called).to.be.ok;
});
});
}); });
describe('setAssociation', function() { describe('setAssociation', function() {
......
...@@ -22,6 +22,44 @@ describe(Support.getTestDialectTeaser('HasOne'), function() { ...@@ -22,6 +22,44 @@ describe(Support.getTestDialectTeaser('HasOne'), function() {
}); });
}); });
describe('get', function () {
describe('multiple', function () {
it('should fetch associations for multiple instances', function () {
var User = this.sequelize.define('User', {})
, Player = this.sequelize.define('Player', {});
Player.User = Player.hasOne(User, {as: 'user'});
return this.sequelize.sync({force: true}).then(function () {
return Promise.join(
Player.create({
id: 1,
user: {}
}, {
include: [Player.User]
}),
Player.create({
id: 2,
user: {}
}, {
include: [Player.User]
}),
Player.create({
id: 3
})
);
}).then(function (players) {
return Player.User.get(players).then(function (result) {
expect(result[players[0].id].id).to.equal(players[0].user.id);
expect(result[players[1].id].id).to.equal(players[1].user.id);
expect(result[players[2].id]).to.equal(null);
});
});
});
});
});
describe('getAssocation', function() { describe('getAssocation', function() {
if (current.dialect.supports.transactions) { if (current.dialect.supports.transactions) {
it('supports transactions', function() { it('supports transactions', function() {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!