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

Commit c7104a4c by Mick Hansen

feat(model/instance): nested creation support, fixes/changes to the excellent work of @mbroadst

1 parent b284b53c
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
var Utils = require('./utils') var Utils = require('./utils')
, Mixin = require('./associations/mixin') , Mixin = require('./associations/mixin')
, BelongsTo = require('./associations/belongs-to')
, HasOne = require('./associations/has-one')
, HasMany = require('./associations/has-many')
, BelongsToMany = require('./associations/belongs-to-many')
, InstanceValidator = require('./instance-validator') , InstanceValidator = require('./instance-validator')
, DataTypes = require('./data-types') , DataTypes = require('./data-types')
, Promise = require("./promise") , Promise = require("./promise")
...@@ -431,7 +435,7 @@ module.exports = (function() { ...@@ -431,7 +435,7 @@ module.exports = (function() {
if (!isEmpty) { if (!isEmpty) {
childOptions = { childOptions = {
isNewRecord: false, isNewRecord: this.isNewRecord,
isDirty: false, isDirty: false,
include: include.include, include: include.include,
includeNames: include.includeNames, includeNames: include.includeNames,
...@@ -501,7 +505,8 @@ module.exports = (function() { ...@@ -501,7 +505,8 @@ module.exports = (function() {
var self = this var self = this
, updatedAtAttr = this.Model._timestampAttributes.updatedAt , updatedAtAttr = this.Model._timestampAttributes.updatedAt
, createdAtAttr = this.Model._timestampAttributes.createdAt , createdAtAttr = this.Model._timestampAttributes.createdAt
, hook = self.isNewRecord ? 'Create' : 'Update'; , hook = self.isNewRecord ? 'Create' : 'Update'
, wasNewRecord = this.isNewRecord;
if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) === -1) { if (updatedAtAttr && options.fields.indexOf(updatedAtAttr) === -1) {
options.fields.push(updatedAtAttr); options.fields.push(updatedAtAttr);
...@@ -579,31 +584,20 @@ module.exports = (function() { ...@@ -579,31 +584,20 @@ module.exports = (function() {
} }
}).then(function() { }).then(function() {
if (!options.fields.length) return this; if (!options.fields.length) return this;
if (!this.isNewRecord) return this;
// create relational data for BelongsTo if (!this.options.include || !this.options.include.length) return this;
var relationPromises = [];
Object.keys(this.dataValues).forEach(function(dataValue) { // Nested creation for BelongsTo relations
if (_.has(self.options.includeMap, dataValue)) { return Promise.map(this.options.include.filter(function (include) {
var includeModel = self.options.includeMap[dataValue].model; return include.association instanceof BelongsTo;
var includeAssociation = self.options.includeMap[dataValue].association; }), function (include) {
var instance = self.get(include.as);
if (includeAssociation.associationType === 'BelongsTo') { if (!instance) return Promise.resolve();
var includeData = self.dataValues[dataValue].dataValues;
var relationPromise = includeModel.create(includeData).then(function(result) { return instance.save({
result._originalDataValue = dataValue; transaction: options.transaction
result._identifierField = includeAssociation.identifierField; }).then(function () {
return result; return self[include.association.accessors.set](instance, {save: false});
});
relationPromises.push(relationPromise);
}
}
});
return Promise.all(relationPromises).then(function(instances) {
instances.forEach(function(instance) {
delete self.dataValues[instance._originalDataValue];
self.dataValues[instance._identifierField] = instance.id;
}); });
}); });
}) })
...@@ -670,44 +664,38 @@ module.exports = (function() { ...@@ -670,44 +664,38 @@ module.exports = (function() {
return result; return result;
}) })
.tap(function(result) { .tap(function(result) {
// handle HasOne/HasMany/BelongsToMany relations if (!wasNewRecord) return;
var relationPromises = []; if (!self.options.include || !self.options.include.length) return;
Object.keys(self.dataValues).forEach(function(dataValue) {
if (_.has(self.options.includeMap, dataValue)) { // Nested creation for HasOne/HasMany/BelongsToMany relations
var includeModel = self.options.includeMap[dataValue].model; return Promise.map(self.options.include.filter(function (include) {
var includeAssociation = self.options.includeMap[dataValue].association; return !(include.association instanceof BelongsTo);
}), function (include) {
if (includeAssociation.associationType === 'HasOne') { var instances = self.get(include.as)
var includeData = self.dataValues[dataValue].dataValues; , instance = instances;
includeData[includeAssociation.identifierField] = result.id;
relationPromises.push(includeModel.create(includeData)); if (!instances) return Promise.resolve();
} else if (includeAssociation.associationType === 'HasMany') { if (Array.isArray(instances) && !instances.length) return Promise.resolve();
self.dataValues[dataValue].forEach(function(record) {
var includeData = record.dataValues; if (Array.isArray(instances)) {
includeData[includeAssociation.identifierField] = result.id; return Promise.map(instances, function (instance) {
relationPromises.push(includeModel.create(includeData)); if (include.association instanceof BelongsToMany) {
}); return instance.save({transaction: options.transaction}).then(function () {
} else if (includeAssociation.associationType === 'BelongsToMany') { var values = {};
var throughModel = includeAssociation.throughModel; values[include.association.foreignKey] = self.get(self.Model.primaryKeyAttribute, {raw: true});
var identifierField = includeAssociation.identifierField; values[include.association.otherKey] = instance.get(instance.Model.primaryKeyAttribute, {raw: true});
var foreignIdentifierField = includeAssociation.foreignIdentifierField; return include.association.throughModel.create(values);
self.dataValues[dataValue].forEach(function(record) {
var includeData = record.dataValues;
var promise = includeModel.create(includeData).then(function(associatedRecord) {
var throughData = {};
throughData[identifierField] = result.id;
throughData[foreignIdentifierField] = associatedRecord.id;
return throughModel.create(throughData);
}); });
} else {
relationPromises.push(promise); instance.set(include.association.identifier, self.get(self.Model.primaryKeyAttribute, {raw: true}));
}); return instance.save({transaction: options.transaction});
}
} });
} else {
instance.set(include.association.identifier, self.get(self.Model.primaryKeyAttribute, {raw: true}));
return instance.save({transaction: options.transaction});
} }
}); });
return Promise.all(relationPromises);
}); });
}); });
}); });
......
'use strict'; 'use strict';
var chai = require('chai') var chai = require('chai')
, Sequelize = require('../../../index') , Sequelize = require('../../../../index')
, expect = chai.expect , expect = chai.expect
, Support = require(__dirname + '/../support') , Support = require(__dirname + '/../../support')
, dialect = Support.getTestDialect() , dialect = Support.getTestDialect()
, DataTypes = require(__dirname + '/../../../lib/data-types') , DataTypes = require(__dirname + '/../../../../lib/data-types')
, Promise = Sequelize.Promise; , Promise = Sequelize.Promise;
chai.config.includeStack = true; chai.config.includeStack = true;
describe(Support.getTestDialectTeaser('Instance'), function() { describe(Support.getTestDialectTeaser('Model'), function() {
describe('save', function() { describe('create', function() {
describe('include', function() { describe('include', function() {
it('should save data for BelongsTo relations', function() { it('should create data for BelongsTo relations', function() {
var Product = this.sequelize.define('Product', { var Product = this.sequelize.define('Product', {
title: Sequelize.STRING title: Sequelize.STRING
}); });
...@@ -24,31 +24,62 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -24,31 +24,62 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
Product.belongsTo(User); Product.belongsTo(User);
var product = Product.build({
id: 1,
title: 'Chair',
User: {
id: 1,
first_name: 'Mick',
last_name: 'Hansen'
}
}, {
include: [ User ]
});
return this.sequelize.sync({ force: true }).then(function() { return this.sequelize.sync({ force: true }).then(function() {
return product.save().then(function(savedProduct) { return Product.create({
return Product.find({ title: 'Chair',
User: {
first_name: 'Mick',
last_name: 'Broadstone'
}
}, {
include: [ User ]
}).then(function(savedProduct) {
return Product.findOne({
where: { id: savedProduct.id }, where: { id: savedProduct.id },
include: [ User ] include: [ User ]
}).then(function(persistedProduct) { }).then(function(persistedProduct) {
expect(persistedProduct.User).to.be.ok; expect(persistedProduct.User).to.be.ok;
expect(persistedProduct.User.first_name).to.be.equal('Mick');
expect(persistedProduct.User.last_name).to.be.equal('Broadstone');
});
});
});
});
it('should create data for BelongsTo relations with alias', function() {
var Product = this.sequelize.define('Product', {
title: Sequelize.STRING
});
var User = this.sequelize.define('User', {
first_name: Sequelize.STRING,
last_name: Sequelize.STRING
});
var Creator = Product.belongsTo(User, {as: 'creator'});
return this.sequelize.sync({ force: true }).then(function() {
return Product.create({
title: 'Chair',
creator: {
first_name: 'Matt',
last_name: 'Hansen'
}
}, {
include: [ Creator ]
}).then(function(savedProduct) {
return Product.findOne({
where: { id: savedProduct.id },
include: [ Creator ]
}).then(function(persistedProduct) {
expect(persistedProduct.creator).to.be.ok;
expect(persistedProduct.creator.first_name).to.be.equal('Matt');
expect(persistedProduct.creator.last_name).to.be.equal('Hansen');
}); });
}); });
}); });
}); });
it('should save data for HasMany relations', function() { it('should create data for HasMany relations', function() {
var Product = this.sequelize.define('Product', { var Product = this.sequelize.define('Product', {
title: Sequelize.STRING title: Sequelize.STRING
}); });
...@@ -58,19 +89,17 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -58,19 +89,17 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
Product.hasMany(Tag); Product.hasMany(Tag);
var product = Product.build({
id: 1,
title: 'Chair',
Tags: [
{id: 1, name: 'Alpha'},
{id: 2, name: 'Beta'}
]
}, {
include: [ Tag ]
});
return this.sequelize.sync({ force: true }).then(function() { return this.sequelize.sync({ force: true }).then(function() {
return product.save().then(function(savedProduct) { return Product.create({
id: 1,
title: 'Chair',
Tags: [
{id: 1, name: 'Alpha'},
{id: 2, name: 'Beta'}
]
}, {
include: [ Tag ]
}).then(function(savedProduct) {
return Product.find({ return Product.find({
where: { id: savedProduct.id }, where: { id: savedProduct.id },
include: [ Tag ] include: [ Tag ]
...@@ -82,7 +111,39 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -82,7 +111,39 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
}); });
}); });
it('should save data for HasOne relations', function() { it('should create data for HasMany relations with alias', function() {
var Product = this.sequelize.define('Product', {
title: Sequelize.STRING
});
var Tag = this.sequelize.define('Tag', {
name: Sequelize.STRING
});
var Categories = Product.hasMany(Tag, {as: 'categories'});
return this.sequelize.sync({ force: true }).then(function() {
return Product.create({
id: 1,
title: 'Chair',
categories: [
{id: 1, name: 'Alpha'},
{id: 2, name: 'Beta'}
]
}, {
include: [ Categories ]
}).then(function(savedProduct) {
return Product.find({
where: { id: savedProduct.id },
include: [ Categories ]
}).then(function(persistedProduct) {
expect(persistedProduct.categories).to.be.ok;
expect(persistedProduct.categories.length).to.equal(2);
});
});
});
});
it('should create data for HasOne relations', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
username: Sequelize.STRING username: Sequelize.STRING
}); });
...@@ -93,17 +154,15 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -93,17 +154,15 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
User.hasOne(Task); User.hasOne(Task);
var user = User.build({
username: 'Muzzy',
Task: {
title: 'Eat Clocks'
}
}, {
include: [ Task ]
});
return this.sequelize.sync({ force: true }).then(function() { return this.sequelize.sync({ force: true }).then(function() {
return user.save().then(function(savedUser) { return User.create({
username: 'Muzzy',
Task: {
title: 'Eat Clocks'
}
}, {
include: [ Task ]
}).then(function(savedUser) {
return User.find({ return User.find({
where: { id: savedUser.id }, where: { id: savedUser.id },
include: [ Task ] include: [ Task ]
...@@ -114,7 +173,39 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -114,7 +173,39 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
}); });
}); });
it('should save data for BelongsToMany relations', function() { it('should create data for HasOne relations with alias', function() {
var User = this.sequelize.define('User', {
username: Sequelize.STRING
});
var Task = this.sequelize.define('Task', {
title: Sequelize.STRING
});
var Job = User.hasOne(Task, {as: 'job'});
return this.sequelize.sync({ force: true }).then(function() {
return User.create({
username: 'Muzzy',
job: {
title: 'Eat Clocks'
}
}, {
include: [ Job ]
}).then(function(savedUser) {
return User.find({
where: { id: savedUser.id },
include: [ Job ]
}).then(function(persistedUser) {
expect(persistedUser.job).to.be.ok;
});
});
});
});
it('should create data for BelongsToMany relations', function() {
var User = this.sequelize.define('User', { var User = this.sequelize.define('User', {
username: DataTypes.STRING username: DataTypes.STRING
}); });
...@@ -124,22 +215,19 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -124,22 +215,19 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
active: DataTypes.BOOLEAN active: DataTypes.BOOLEAN
}); });
User.belongsToMany(Task); User.belongsToMany(Task, {through: 'user_task'});
Task.belongsToMany(User); Task.belongsToMany(User, {through: 'user_task'});
var user = User.build({
username: 'John',
Tasks: [
{ title: 'Get rich', active: true },
{ title: 'Die trying', active: false }
]
}, {
include: [ Task ]
});
var tasks = [];
return this.sequelize.sync({ force: true }).then(function() { return this.sequelize.sync({ force: true }).then(function() {
return user.save().then(function(savedUser) { return User.create({
username: 'John',
Tasks: [
{ title: 'Get rich', active: true },
{ title: 'Die trying', active: false }
]
}, {
include: [ Task ]
}).then(function(savedUser) {
return User.find({ return User.find({
where: { id: savedUser.id }, where: { id: savedUser.id },
include: [ Task ] include: [ Task ]
...@@ -151,6 +239,39 @@ describe(Support.getTestDialectTeaser('Instance'), function() { ...@@ -151,6 +239,39 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
}); });
}); });
it('should create data for BelongsToMany relations with alias', function() {
var User = this.sequelize.define('User', {
username: DataTypes.STRING
});
var Task = this.sequelize.define('Task', {
title: DataTypes.STRING,
active: DataTypes.BOOLEAN
});
var Jobs = User.belongsToMany(Task, {through: 'user_job', as: 'jobs'});
Task.belongsToMany(User, {through: 'user_job'});
return this.sequelize.sync({ force: true }).then(function() {
return User.create({
username: 'John',
jobs: [
{ title: 'Get rich', active: true },
{ title: 'Die trying', active: false }
]
}, {
include: [ Jobs ]
}).then(function(savedUser) {
return User.find({
where: { id: savedUser.id },
include: [ Jobs ]
}).then(function(persistedUser) {
expect(persistedUser.jobs).to.be.ok;
expect(persistedUser.jobs.length).to.equal(2);
});
});
});
});
}); });
}); });
}); });
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!