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

Commit d206cde1 by Mick Hansen

feat(associations/scope): add support for scope on N:M through model

1 parent e3e4b31f
......@@ -15,65 +15,51 @@ module.exports = (function() {
HasManyDoubleLinked.prototype.injectGetter = function(options, queryOptions) {
var self = this
, through = self.association.through
, targetAssociation = self.association.targetAssociation;
, scopeWhere = null
, throughScopeWhere = null;
//fully qualify
var instancePrimaryKey = self.instance.Model.primaryKeyAttribute
, foreignPrimaryKey = self.association.target.primaryKeyAttribute;
var scopeWhere = this.association.scope ? {} : null;
if (this.association.scope) {
scopeWhere = {};
Object.keys(this.association.scope).forEach(function (attribute) {
scopeWhere[attribute] = this.association.scope[attribute];
}.bind(this));
}
options.where = new Utils.and([
new Utils.where(
through.model.rawAttributes[self.association.identifier],
self.instance[instancePrimaryKey]
),
new Utils.where(
through.model.rawAttributes[self.association.foreignIdentifier],
{
join: new Utils.literal([
self.QueryInterface.quoteTable(self.association.target.name),
self.QueryInterface.quoteIdentifier(foreignPrimaryKey)
].join('.'))
}
),
scopeWhere,
options.where
]);
if (Object(targetAssociation.through.model) === targetAssociation.through.model) {
queryOptions.hasJoinTableModel = true;
queryOptions.joinTableModel = through.model;
if (!options.attributes) {
options.attributes = [
self.QueryInterface.quoteTable(self.association.target.name) + '.*'
];
if (Object(through.model) === through.model) {
if (through && through.scope) {
throughScopeWhere = {};
Object.keys(through.scope).forEach(function (attribute) {
throughScopeWhere[attribute] = through.scope[attribute];
}.bind(this));
}
if (options.joinTableAttributes) {
options.joinTableAttributes.forEach(function(elem) {
options.attributes.push(
self.QueryInterface.quoteTable(through.model.name) + '.' + self.QueryInterface.quoteIdentifier(elem) + ' as ' +
self.QueryInterface.quoteIdentifier(through.model.name + '.' + elem, true)
);
});
} else {
Utils._.forOwn(through.model.rawAttributes, function(elem, key) {
options.attributes.push(
self.QueryInterface.quoteTable(through.model.name) + '.' + self.QueryInterface.quoteIdentifier(key) + ' as ' +
self.QueryInterface.quoteIdentifier(through.model.name + '.' + key, true)
);
options.include = options.include || [];
options.include.push({
model: through.model,
as: Utils.singularize(through.model.tableName),
association: {
isSingleAssociation: true,
source: self.association.source,
target: self.association.target,
identifier: self.association.foreignIdentifier
},
required: true,
where: new Utils.and([
new Utils.where(
through.model.rawAttributes[self.association.identifier],
self.instance.get(self.association.source.primaryKeyAttribute)
),
throughScopeWhere
]),
_pseudo: true
});
}
}
return self.association.target.findAllJoin([through.model.getTableName(), through.model.name], options, queryOptions);
return self.association.target.findAll(options, queryOptions);
};
HasManyDoubleLinked.prototype.injectSetter = function(oldAssociations, newAssociations, defaultAttributes) {
......@@ -150,8 +136,14 @@ module.exports = (function() {
attributes = Utils._.defaults(attributes, unassociatedObject[targetAssociation.through.model.name], defaultAttributes);
}
if (this.association.through.scope) {
Object.keys(this.association.through.scope).forEach(function (attribute) {
attributes[attribute] = this.association.through.scope[attribute];
}.bind(this));
}
return attributes;
});
}.bind(this));
promises.push(self.association.through.model.bulkCreate(bulk, options));
}
......@@ -192,6 +184,11 @@ module.exports = (function() {
}
} else {
attributes = Utils._.defaults(attributes, newAssociation[targetAssociation.through.model.name], additionalAttributes);
if (this.association.through.scope) {
Object.keys(this.association.through.scope).forEach(function (attribute) {
attributes[attribute] = this.association.through.scope[attribute];
}.bind(this));
}
return this.association.through.model.create(attributes, options);
}
......
......@@ -406,6 +406,13 @@ module.exports = (function() {
var instance = this
, primaryKeyAttribute = association.target.primaryKeyAttribute;
additionalAttributes = additionalAttributes || {};
if (association.through && association.through.scope) {
Object.keys(association.through.scope).forEach(function (attribute) {
additionalAttributes[attribute] = association.through.scope[attribute];
});
}
if (Array.isArray(newInstance)) {
var newInstances = newInstance.map(function(newInstance) {
if (!(newInstance instanceof association.target.Instance)) {
......
......@@ -910,8 +910,8 @@ module.exports = (function() {
}
}
} else {
var left = association.associationType === 'BelongsTo' ? association.target : include.association.source
, primaryKeysLeft = association.associationType === 'BelongsTo' ? left.primaryKeyAttributes : left.primaryKeyAttributes
var left = association.associationType === 'BelongsTo' ? association.target : association.source
, primaryKeysLeft = left.primaryKeyAttributes
, tableLeft = association.associationType === 'BelongsTo' ? as : parentTable
, attrLeft = primaryKeysLeft[0]
, tableRight = association.associationType === 'BelongsTo' ? parentTable : as
......
......@@ -711,12 +711,12 @@ module.exports = (function() {
//right now, the caller (has-many-double-linked) is in charge of the where clause
Model.prototype.findAllJoin = function(joinTableName, options, queryOptions) {
var optcpy = Utils._.clone(options);
optcpy.attributes = optcpy.attributes || [this.QueryInterface.quoteTable(this.name) + '.*'];
options = optClone(options || {});
options.attributes = options.attributes || [this.QueryInterface.quoteTable(this.name) + '.*'];
// whereCollection is used for non-primary key updates
this.options.whereCollection = optcpy.where || null;
this.options.whereCollection = options.where || null;
return this.QueryInterface.select(this, [[this.getTableName(), this.name], joinTableName], optcpy, Utils._.defaults({
return this.QueryInterface.select(this, [[this.getTableName(), this.name], joinTableName], options, Utils._.defaults({
type: QueryTypes.SELECT
}, queryOptions, { transaction: (options || {}).transaction }));
};
......
......@@ -28,11 +28,6 @@ describe(Support.getTestDialectTeaser("associations"), function() {
}
}
});
this.Tag = this.sequelize.define('tag', {
name: Sequelize.STRING,
taggable: Sequelize.STRING,
taggable_id: Sequelize.INTEGER
});
this.Post.hasMany(this.Comment, {
foreignKey: 'commentable_id',
......@@ -42,10 +37,7 @@ describe(Support.getTestDialectTeaser("associations"), function() {
});
this.Comment.belongsTo(this.Post, {
foreignKey: 'commentable_id',
as: 'post',
scope: {
commentable: 'post'
}
as: 'post'
});
this.Image.hasMany(this.Comment, {
......@@ -56,10 +48,7 @@ describe(Support.getTestDialectTeaser("associations"), function() {
});
this.Comment.belongsTo(this.Image, {
foreignKey: 'commentable_id',
as: 'image',
scope: {
commentable: 'image'
}
as: 'image'
});
this.Question.hasMany(this.Comment, {
......@@ -70,10 +59,7 @@ describe(Support.getTestDialectTeaser("associations"), function() {
});
this.Comment.belongsTo(this.Question, {
foreignKey: 'commentable_id',
as: 'question',
scope: {
commentable: 'question'
}
as: 'question'
});
});
......@@ -258,7 +244,6 @@ describe(Support.getTestDialectTeaser("associations"), function() {
})
);
}).spread(function (postA, postB, postC) {
//console.log(postA.get('categories'));
expect(postA.get('categories').length).to.equal(1);
expect(postA.get('tags').length).to.equal(1);
expect(postB.get('categories').length).to.equal(1);
......@@ -276,9 +261,158 @@ describe(Support.getTestDialectTeaser("associations"), function() {
});
});
describe.skip('on the through model', function () {
describe('on the through model', function () {
beforeEach(function () {
this.Post = this.sequelize.define('post', {});
this.Image = this.sequelize.define('image', {});
this.Question = this.sequelize.define('question', {});
this.ItemTag = this.sequelize.define('item_tag', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
tag_id: {
type: DataTypes.INTEGER,
unique: 'item_tag_taggable'
},
taggable: {
type: DataTypes.STRING,
unique: 'item_tag_taggable'
},
taggable_id: {
type: DataTypes.INTEGER,
unique: 'item_tag_taggable',
references: null
}
});
this.Tag = this.sequelize.define('tag', {
name: DataTypes.STRING
});
this.Post.hasMany(this.Tag, {
through: {
model: this.ItemTag,
unique: false,
scope: {
taggable: 'post'
}
},
foreignKey: 'taggable_id',
constraints: false
});
this.Tag.hasMany(this.Post, {
through: {
model: this.ItemTag,
unique: false
},
foreignKey: 'tag_id'
});
this.Image.hasMany(this.Tag, {
through: {
model: this.ItemTag,
unique: false,
scope: {
taggable: 'image'
}
},
foreignKey: 'taggable_id',
constraints: false
});
this.Tag.hasMany(this.Image, {
through: {
model: this.ItemTag,
unique: false
},
foreignKey: 'tag_id'
});
this.Question.hasMany(this.Tag, {
through: {
model: this.ItemTag,
unique: false,
scope: {
taggable: 'question'
}
},
foreignKey: 'taggable_id',
constraints: false
});
this.Tag.hasMany(this.Question, {
through: {
model: this.ItemTag,
unique: false
},
foreignKey: 'tag_id'
});
});
it('should create, find and include associations with scope values', function () {
var self = this;
return Promise.join(
this.Post.sync({force: true}),
this.Image.sync({force: true}),
this.Question.sync({force: true}),
this.Tag.sync({force: true})
).bind(this).then(function () {
return this.ItemTag.sync({force: true});
}).then(function () {
return Promise.join(
this.Post.create(),
this.Image.create(),
this.Question.create(),
this.Tag.create({name: 'tagA'}),
this.Tag.create({name: 'tagB'}),
this.Tag.create({name: 'tagC'})
);
}).spread(function (post, image, question, tagA, tagB, tagC) {
this.post = post;
this.image = image;
this.question = question;
return Promise.join(
post.setTags([tagA]).then(function () {
return Promise.join(
post.createTag({name: 'postTag'}),
post.addTag(tagB)
);
}),
image.setTags([tagB]).then(function () {
return Promise.join(
image.createTag({name: 'imageTag'}),
image.addTag(tagC)
);
}),
question.setTags([tagC]).then(function () {
return Promise.join(
question.createTag({name: 'questionTag'}),
question.addTag(tagA)
);
})
);
}).then(function () {
return Promise.join(
this.post.getTags(),
this.image.getTags(),
this.question.getTags()
).spread(function (postTags, imageTags, questionTags) {
expect(postTags.length).to.equal(3);
expect(imageTags.length).to.equal(3);
expect(questionTags.length).to.equal(3);
expect(postTags.map(function (tag) {
return tag.name;
}).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']);
expect(imageTags.map(function (tag) {
return tag.name;
}).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']);
expect(questionTags.map(function (tag) {
return tag.name;
}).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']);
});
});
});
});
});
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!