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

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 @@
var Utils = require('./utils')
, 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')
, DataTypes = require('./data-types')
, Promise = require("./promise")
......@@ -431,7 +435,7 @@ module.exports = (function() {
if (!isEmpty) {
childOptions = {
isNewRecord: false,
isNewRecord: this.isNewRecord,
isDirty: false,
include: include.include,
includeNames: include.includeNames,
......@@ -501,7 +505,8 @@ module.exports = (function() {
var self = this
, updatedAtAttr = this.Model._timestampAttributes.updatedAt
, 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) {
options.fields.push(updatedAtAttr);
......@@ -579,31 +584,20 @@ module.exports = (function() {
}
}).then(function() {
if (!options.fields.length) return this;
// create relational data for BelongsTo
var relationPromises = [];
Object.keys(this.dataValues).forEach(function(dataValue) {
if (_.has(self.options.includeMap, dataValue)) {
var includeModel = self.options.includeMap[dataValue].model;
var includeAssociation = self.options.includeMap[dataValue].association;
if (includeAssociation.associationType === 'BelongsTo') {
var includeData = self.dataValues[dataValue].dataValues;
var relationPromise = includeModel.create(includeData).then(function(result) {
result._originalDataValue = dataValue;
result._identifierField = includeAssociation.identifierField;
return result;
});
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;
if (!this.isNewRecord) return this;
if (!this.options.include || !this.options.include.length) return this;
// Nested creation for BelongsTo relations
return Promise.map(this.options.include.filter(function (include) {
return include.association instanceof BelongsTo;
}), function (include) {
var instance = self.get(include.as);
if (!instance) return Promise.resolve();
return instance.save({
transaction: options.transaction
}).then(function () {
return self[include.association.accessors.set](instance, {save: false});
});
});
})
......@@ -670,44 +664,38 @@ module.exports = (function() {
return result;
})
.tap(function(result) {
// handle HasOne/HasMany/BelongsToMany relations
var relationPromises = [];
Object.keys(self.dataValues).forEach(function(dataValue) {
if (_.has(self.options.includeMap, dataValue)) {
var includeModel = self.options.includeMap[dataValue].model;
var includeAssociation = self.options.includeMap[dataValue].association;
if (includeAssociation.associationType === 'HasOne') {
var includeData = self.dataValues[dataValue].dataValues;
includeData[includeAssociation.identifierField] = result.id;
relationPromises.push(includeModel.create(includeData));
} else if (includeAssociation.associationType === 'HasMany') {
self.dataValues[dataValue].forEach(function(record) {
var includeData = record.dataValues;
includeData[includeAssociation.identifierField] = result.id;
relationPromises.push(includeModel.create(includeData));
});
} else if (includeAssociation.associationType === 'BelongsToMany') {
var throughModel = includeAssociation.throughModel;
var identifierField = includeAssociation.identifierField;
var foreignIdentifierField = includeAssociation.foreignIdentifierField;
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);
if (!wasNewRecord) return;
if (!self.options.include || !self.options.include.length) return;
// Nested creation for HasOne/HasMany/BelongsToMany relations
return Promise.map(self.options.include.filter(function (include) {
return !(include.association instanceof BelongsTo);
}), function (include) {
var instances = self.get(include.as)
, instance = instances;
if (!instances) return Promise.resolve();
if (Array.isArray(instances) && !instances.length) return Promise.resolve();
if (Array.isArray(instances)) {
return Promise.map(instances, function (instance) {
if (include.association instanceof BelongsToMany) {
return instance.save({transaction: options.transaction}).then(function () {
var values = {};
values[include.association.foreignKey] = self.get(self.Model.primaryKeyAttribute, {raw: true});
values[include.association.otherKey] = instance.get(instance.Model.primaryKeyAttribute, {raw: true});
return include.association.throughModel.create(values);
});
relationPromises.push(promise);
});
}
} else {
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';
var chai = require('chai')
, Sequelize = require('../../../index')
, Sequelize = require('../../../../index')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, Support = require(__dirname + '/../../support')
, dialect = Support.getTestDialect()
, DataTypes = require(__dirname + '/../../../lib/data-types')
, DataTypes = require(__dirname + '/../../../../lib/data-types')
, Promise = Sequelize.Promise;
chai.config.includeStack = true;
describe(Support.getTestDialectTeaser('Instance'), function() {
describe('save', function() {
describe(Support.getTestDialectTeaser('Model'), function() {
describe('create', 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', {
title: Sequelize.STRING
});
......@@ -24,31 +24,62 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
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 product.save().then(function(savedProduct) {
return Product.find({
return Product.create({
title: 'Chair',
User: {
first_name: 'Mick',
last_name: 'Broadstone'
}
}, {
include: [ User ]
}).then(function(savedProduct) {
return Product.findOne({
where: { id: savedProduct.id },
include: [ User ]
}).then(function(persistedProduct) {
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', {
title: Sequelize.STRING
});
......@@ -58,19 +89,17 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
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 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({
where: { id: savedProduct.id },
include: [ Tag ]
......@@ -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', {
username: Sequelize.STRING
});
......@@ -93,17 +154,15 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
User.hasOne(Task);
var user = User.build({
username: 'Muzzy',
Task: {
title: 'Eat Clocks'
}
}, {
include: [ Task ]
});
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({
where: { id: savedUser.id },
include: [ Task ]
......@@ -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', {
username: DataTypes.STRING
});
......@@ -124,22 +215,19 @@ describe(Support.getTestDialectTeaser('Instance'), function() {
active: DataTypes.BOOLEAN
});
User.belongsToMany(Task);
Task.belongsToMany(User);
var user = User.build({
username: 'John',
Tasks: [
{ title: 'Get rich', active: true },
{ title: 'Die trying', active: false }
]
}, {
include: [ Task ]
});
User.belongsToMany(Task, {through: 'user_task'});
Task.belongsToMany(User, {through: 'user_task'});
var tasks = [];
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({
where: { id: savedUser.id },
include: [ Task ]
......@@ -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!