123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005 |
- 'use strict';
- const chai = require('chai');
- const sinon = require('sinon');
- const expect = chai.expect;
- const Support = require('../support');
- const { DataTypes, Sequelize } = require('@sequelize/core');
- const assert = require('node:assert');
- const current = Support.sequelize;
- const dialect = Support.getTestDialect();
- describe(Support.getTestDialectTeaser('BelongsTo'), () => {
- describe('Model.associations', () => {
- it('should store all associations when associating to the same table multiple times', function () {
- const User = this.sequelize.define('User', {});
- const Group = this.sequelize.define('Group', {});
- Group.belongsTo(User);
- Group.belongsTo(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers' });
- Group.belongsTo(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers' });
- expect(Object.keys(Group.associations)).to.deep.equal([
- 'user',
- 'primaryUsers',
- 'secondaryUsers',
- ]);
- });
- });
- describe('get', () => {
- describe('multiple', () => {
- it('should fetch associations for multiple instances', async function () {
- const User = this.sequelize.define('User', {});
- const Task = this.sequelize.define('Task', {});
- Task.User = Task.belongsTo(User, { as: 'user' });
- await this.sequelize.sync({ force: true });
- const tasks = await Promise.all([
- Task.create(
- {
- id: 1,
- user: { id: 1 },
- },
- {
- include: [Task.User],
- },
- ),
- Task.create(
- {
- id: 2,
- user: { id: 2 },
- },
- {
- include: [Task.User],
- },
- ),
- Task.create({
- id: 3,
- }),
- ]);
- const result = await Task.User.get(tasks);
- expect(result.get(tasks[0].id).id).to.equal(tasks[0].user.id);
- expect(result.get(tasks[1].id).id).to.equal(tasks[1].user.id);
- expect(result.get(tasks[2].id)).to.be.undefined;
- });
- });
- });
- describe('getAssociation', () => {
- if (current.dialect.supports.transactions) {
- it('supports transactions', async function () {
- const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
- this.sequelize,
- );
- const User = sequelize.define('User', { username: DataTypes.STRING });
- const Group = sequelize.define('Group', { name: DataTypes.STRING });
- Group.belongsTo(User);
- await sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const group = await Group.create({ name: 'bar' });
- const t = await sequelize.startUnmanagedTransaction();
- await group.setUser(user, { transaction: t });
- const groups = await Group.findAll();
- const associatedUser = await groups[0].getUser();
- expect(associatedUser).to.be.null;
- const groups0 = await Group.findAll({ transaction: t });
- const associatedUser0 = await groups0[0].getUser({ transaction: t });
- expect(associatedUser0).to.be.not.null;
- await t.rollback();
- });
- }
- it("should be able to handle a where object that's a first class citizen.", async function () {
- const User = this.sequelize.define('UserXYZ', {
- username: DataTypes.STRING,
- gender: DataTypes.STRING,
- });
- const Task = this.sequelize.define('TaskXYZ', {
- title: DataTypes.STRING,
- status: DataTypes.STRING,
- });
- Task.belongsTo(User);
- await User.sync({ force: true });
- // Can't use Promise.all cause of foreign key references
- await Task.sync({ force: true });
- const [userA, , task] = await Promise.all([
- User.create({ username: 'foo', gender: 'male' }),
- User.create({ username: 'bar', gender: 'female' }),
- Task.create({ title: 'task', status: 'inactive' }),
- ]);
- await task.setUserXYZ(userA);
- const user = await task.getUserXYZ({ where: { gender: 'female' } });
- expect(user).to.be.null;
- });
- if (current.dialect.supports.schemas) {
- it('supports schemas', async function () {
- const User = this.sequelize
- .define('UserXYZ', { username: DataTypes.STRING, gender: DataTypes.STRING })
- .withSchema('archive');
- const Task = this.sequelize
- .define('TaskXYZ', { title: DataTypes.STRING, status: DataTypes.STRING })
- .withSchema('archive');
- Task.belongsTo(User);
- await this.sequelize.createSchema('archive');
- await User.sync({ force: true });
- await Task.sync({ force: true });
- const [user0, task] = await Promise.all([
- User.create({ username: 'foo', gender: 'male' }),
- Task.create({ title: 'task', status: 'inactive' }),
- ]);
- await task.setUserXYZ(user0);
- const user = await task.getUserXYZ();
- expect(user).to.be.ok;
- await this.sequelize.queryInterface.dropAllTables({ schema: 'archive' });
- await this.sequelize.dropSchema('archive');
- const schemas = await this.sequelize.queryInterface.listSchemas();
- expect(schemas).to.not.include('archive');
- });
- it('supports schemas when defining custom foreign key attribute #9029', async function () {
- const User = this.sequelize
- .define('UserXYZ', {
- uid: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- autoIncrement: true,
- allowNull: false,
- },
- })
- .withSchema('archive');
- const Task = this.sequelize
- .define('TaskXYZ', {
- user_id: {
- type: DataTypes.INTEGER,
- references: { model: User, key: 'uid' },
- },
- })
- .withSchema('archive');
- Task.belongsTo(User, { foreignKey: 'user_id' });
- await this.sequelize.createSchema('archive');
- await User.sync({ force: true });
- await Task.sync({ force: true });
- const user0 = await User.create({});
- const task = await Task.create({});
- await task.setUserXYZ(user0);
- const user = await task.getUserXYZ();
- expect(user).to.be.ok;
- });
- }
- });
- describe('setAssociation', () => {
- if (current.dialect.supports.transactions) {
- it('supports transactions', async function () {
- const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
- this.sequelize,
- );
- const User = sequelize.define('User', { username: DataTypes.STRING });
- const Group = sequelize.define('Group', { name: DataTypes.STRING });
- Group.belongsTo(User);
- await sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const group = await Group.create({ name: 'bar' });
- const t = await sequelize.startUnmanagedTransaction();
- await group.setUser(user, { transaction: t });
- const groups = await Group.findAll();
- const associatedUser = await groups[0].getUser();
- expect(associatedUser).to.be.null;
- await t.rollback();
- });
- }
- it('can set the association with declared primary keys...', async function () {
- const User = this.sequelize.define('UserXYZ', {
- user_id: { type: DataTypes.INTEGER, primaryKey: true },
- username: DataTypes.STRING,
- });
- const Task = this.sequelize.define('TaskXYZ', {
- task_id: { type: DataTypes.INTEGER, primaryKey: true },
- title: DataTypes.STRING,
- });
- Task.belongsTo(User, { foreignKey: 'user_id' });
- await this.sequelize.sync({ force: true });
- const user = await User.create({ user_id: 1, username: 'foo' });
- const task = await Task.create({ task_id: 1, title: 'task' });
- await task.setUserXYZ(user);
- const user1 = await task.getUserXYZ();
- expect(user1).not.to.be.null;
- await task.setUserXYZ(null);
- const user0 = await task.getUserXYZ();
- expect(user0).to.be.null;
- });
- it('clears the association if null is passed', async function () {
- const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
- const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
- Task.belongsTo(User);
- await this.sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const task = await Task.create({ title: 'task' });
- await task.setUserXYZ(user);
- const user1 = await task.getUserXYZ();
- expect(user1).not.to.be.null;
- await task.setUserXYZ(null);
- const user0 = await task.getUserXYZ();
- expect(user0).to.be.null;
- });
- it('should throw a ForeignKeyConstraintError if the associated record does not exist', async function () {
- const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
- const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
- Task.belongsTo(User);
- await this.sequelize.sync({ force: true });
- await expect(Task.create({ title: 'task', userXYZId: 5 })).to.be.rejectedWith(
- Sequelize.ForeignKeyConstraintError,
- );
- const task = await Task.create({ title: 'task' });
- await expect(
- Task.update({ title: 'taskUpdate', userXYZId: 5 }, { where: { id: task.id } }),
- ).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
- });
- it('supports passing the primary key instead of an object', async function () {
- const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
- const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
- Task.belongsTo(User);
- await this.sequelize.sync({ force: true });
- const user = await User.create({ id: 15, username: 'jansemand' });
- const task = await Task.create({});
- await task.setUserXYZ(user.id);
- const user0 = await task.getUserXYZ();
- expect(user0.username).to.equal('jansemand');
- });
- it('should support logging', async function () {
- const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
- const Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING });
- const spy = sinon.spy();
- Task.belongsTo(User);
- await this.sequelize.sync({ force: true });
- const user = await User.create();
- const task = await Task.create({});
- await task.setUserXYZ(user, { logging: spy });
- expect(spy.called).to.be.ok;
- });
- it('should not clobber atributes', async function () {
- const Comment = this.sequelize.define('comment', {
- text: DataTypes.STRING,
- });
- const Post = this.sequelize.define('post', {
- title: DataTypes.STRING,
- });
- Post.hasOne(Comment);
- Comment.belongsTo(Post);
- await this.sequelize.sync();
- const post = await Post.create({
- title: 'Post title',
- });
- const comment = await Comment.create({
- text: 'OLD VALUE',
- });
- comment.text = 'UPDATED VALUE';
- await comment.setPost(post);
- expect(comment.text).to.equal('UPDATED VALUE');
- });
- it('should set the foreign key value without saving when using save: false', async function () {
- const Comment = this.sequelize.define('comment', {
- text: DataTypes.STRING,
- });
- const Post = this.sequelize.define('post', {
- title: DataTypes.STRING,
- });
- Post.hasMany(Comment, { foreignKey: 'post_id' });
- Comment.belongsTo(Post, { foreignKey: 'post_id' });
- await this.sequelize.sync({ force: true });
- const [post, comment] = await Promise.all([Post.create(), Comment.create()]);
- expect(comment.get('post_id')).not.to.be.ok;
- const setter = await comment.setPost(post, { save: false });
- expect(setter).to.be.undefined;
- expect(comment.get('post_id')).to.equal(post.get('id'));
- expect(comment.changed('post_id')).to.be.true;
- });
- it('supports setting same association twice', async function () {
- const Home = this.sequelize.define('home', {});
- const User = this.sequelize.define('user');
- Home.belongsTo(User);
- await this.sequelize.sync({ force: true });
- const [home, user] = await Promise.all([Home.create(), User.create()]);
- await home.setUser(user);
- expect(await home.getUser()).to.have.property('id', user.id);
- });
- });
- describe('createAssociation', () => {
- it('creates an associated model instance', async function () {
- const User = this.sequelize.define('User', { username: DataTypes.STRING });
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- Task.belongsTo(User);
- await this.sequelize.sync({ force: true });
- const task = await Task.create({ title: 'task' });
- const user = await task.createUser({ username: 'bob' });
- expect(user).not.to.be.null;
- expect(user.username).to.equal('bob');
- });
- if (current.dialect.supports.transactions) {
- it('supports transactions', async function () {
- const sequelize = await Support.createSingleTransactionalTestSequelizeInstance(
- this.sequelize,
- );
- const User = sequelize.define('User', { username: DataTypes.STRING });
- const Group = sequelize.define('Group', { name: DataTypes.STRING });
- Group.belongsTo(User);
- await sequelize.sync({ force: true });
- const group = await Group.create({ name: 'bar' });
- const t = await sequelize.startUnmanagedTransaction();
- await group.createUser({ username: 'foo' }, { transaction: t });
- const user = await group.getUser();
- expect(user).to.be.null;
- const user0 = await group.getUser({ transaction: t });
- expect(user0).not.to.be.null;
- await t.rollback();
- });
- }
- });
- describe('foreign key', () => {
- it('should setup underscored field with foreign keys when using underscored', function () {
- const User = this.sequelize.define(
- 'User',
- { username: DataTypes.STRING },
- { underscored: true },
- );
- const Account = this.sequelize.define(
- 'Account',
- { name: DataTypes.STRING },
- { underscored: true },
- );
- User.belongsTo(Account);
- expect(User.getAttributes().accountId).to.exist;
- expect(User.getAttributes().accountId.field).to.equal('account_id');
- });
- it('should use model name when using camelcase', function () {
- const User = this.sequelize.define(
- 'User',
- { username: DataTypes.STRING },
- { underscored: false },
- );
- const Account = this.sequelize.define(
- 'Account',
- { name: DataTypes.STRING },
- { underscored: false },
- );
- User.belongsTo(Account);
- expect(User.getAttributes().accountId).to.exist;
- expect(User.getAttributes().accountId.field).to.equal('accountId');
- });
- it('should support specifying the field of a foreign key', async function () {
- const User = this.sequelize.define(
- 'User',
- { username: DataTypes.STRING },
- { underscored: false },
- );
- const Account = this.sequelize.define(
- 'Account',
- { title: DataTypes.STRING },
- { underscored: false },
- );
- User.belongsTo(Account, {
- foreignKey: {
- name: 'AccountId',
- field: 'account_id',
- },
- });
- expect(User.getAttributes().AccountId).to.exist;
- expect(User.getAttributes().AccountId.field).to.equal('account_id');
- await Account.sync({ force: true });
- // Can't use Promise.all cause of foreign key references
- await User.sync({ force: true });
- const [user1, account] = await Promise.all([
- User.create({ username: 'foo' }),
- Account.create({ title: 'pepsico' }),
- ]);
- await user1.setAccount(account);
- const user0 = await user1.getAccount();
- expect(user0).to.not.be.null;
- const user = await User.findOne({
- where: { username: 'foo' },
- include: [Account],
- });
- // the sql query should correctly look at account_id instead of AccountId
- expect(user.account).to.exist;
- });
- it('should set foreignKey on foreign table', async function () {
- const Mail = this.sequelize.define('mail', {}, { timestamps: false });
- const Entry = this.sequelize.define('entry', {}, { timestamps: false });
- const User = this.sequelize.define('user', {}, { timestamps: false });
- Entry.belongsTo(User, {
- as: 'owner',
- foreignKey: {
- name: 'ownerId',
- allowNull: false,
- },
- });
- Entry.belongsTo(Mail, {
- as: 'mail',
- foreignKey: {
- name: 'mailId',
- allowNull: false,
- },
- });
- Mail.belongsToMany(User, {
- as: 'recipients',
- through: {
- model: 'MailRecipients',
- timestamps: false,
- },
- otherKey: {
- name: 'recipientId',
- allowNull: false,
- },
- foreignKey: {
- name: 'mailId',
- allowNull: false,
- },
- });
- Mail.hasMany(Entry, {
- as: 'entries',
- foreignKey: {
- name: 'mailId',
- allowNull: false,
- },
- });
- User.hasMany(Entry, {
- as: 'entries',
- foreignKey: {
- name: 'ownerId',
- allowNull: false,
- },
- });
- await this.sequelize.sync({ force: true });
- await User.create(dialect === 'db2' ? { id: 1 } : {});
- const mail = await Mail.create(dialect === 'db2' ? { id: 1 } : {});
- await Entry.create({ mailId: mail.id, ownerId: 1 });
- await Entry.create({ mailId: mail.id, ownerId: 1 });
- // set recipients
- await mail.setRecipients([1]);
- const result = await Entry.findAndCountAll({
- offset: 0,
- limit: 10,
- order: [['id', 'DESC']],
- include: [
- {
- association: Entry.associations.mail,
- include: [
- {
- association: Mail.associations.recipients,
- through: {
- where: {
- recipientId: 1,
- },
- },
- required: true,
- },
- ],
- required: true,
- },
- ],
- });
- expect(result.count).to.equal(2);
- expect(result.rows[0].get({ plain: true })).to.deep.equal({
- id: 2,
- ownerId: 1,
- mailId: 1,
- mail: {
- id: 1,
- recipients: [
- {
- id: 1,
- MailRecipients: {
- mailId: 1,
- recipientId: 1,
- },
- },
- ],
- },
- });
- });
- });
- describe('foreign key constraints', () => {
- it('are enabled by default', async function () {
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- const User = this.sequelize.define('User', { username: DataTypes.STRING });
- Task.belongsTo(User); // defaults to SET NULL
- await this.sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const task = await Task.create({ title: 'task' });
- await task.setUser(user);
- await user.destroy();
- await task.reload();
- expect(task.userId).to.equal(null);
- });
- it('should be possible to disable them', async function () {
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- const User = this.sequelize.define('User', { username: DataTypes.STRING });
- Task.belongsTo(User, { foreignKeyConstraints: false });
- await this.sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const task = await Task.create({ title: 'task' });
- await task.setUser(user);
- await user.destroy();
- await task.reload();
- expect(task.userId).to.equal(user.id);
- });
- it('can cascade deletes', async function () {
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- const User = this.sequelize.define('User', { username: DataTypes.STRING });
- Task.belongsTo(User, { foreignKey: { onDelete: 'cascade' } });
- await this.sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const task = await Task.create({ title: 'task' });
- await task.setUser(user);
- await user.destroy();
- const tasks = await Task.findAll();
- expect(tasks).to.have.length(0);
- });
- if (current.dialect.supports.constraints.restrict) {
- it('can restrict deletes', async function () {
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- const User = this.sequelize.define('User', { username: DataTypes.STRING });
- Task.belongsTo(User, { foreignKey: { onDelete: 'restrict' } });
- await this.sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const task = await Task.create({ title: 'task' });
- await task.setUser(user);
- await expect(user.destroy()).to.eventually.be.rejectedWith(
- Sequelize.ForeignKeyConstraintError,
- );
- const tasks = await Task.findAll();
- expect(tasks).to.have.length(1);
- });
- it('can restrict updates', async function () {
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- const User = this.sequelize.define('User', { username: DataTypes.STRING });
- Task.belongsTo(User, { foreignKey: { onUpdate: 'restrict' } });
- await this.sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const task = await Task.create({ title: 'task' });
- await task.setUser(user);
- // Changing the id of a DAO requires a little dance since
- // the `UPDATE` query generated by `save()` uses `id` in the
- // `WHERE` clause
- const tableName = User.table;
- await expect(
- user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id }),
- ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
- // Should fail due to FK restriction
- const tasks = await Task.findAll();
- expect(tasks).to.have.length(1);
- });
- }
- // NOTE: mssql does not support changing an autoincrement primary key
- if (!['mssql', 'db2', 'ibmi'].includes(dialect)) {
- it('can cascade updates', async function () {
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- const User = this.sequelize.define('User', { username: DataTypes.STRING });
- Task.belongsTo(User, { foreignKey: { onUpdate: 'cascade' } });
- await this.sequelize.sync({ force: true });
- const user = await User.create({ username: 'foo' });
- const task = await Task.create({ title: 'task' });
- await task.setUser(user);
- // Changing the id of a DAO requires a little dance since
- // the `UPDATE` query generated by `save()` uses `id` in the
- // `WHERE` clause
- const tableName = User.table;
- await user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id });
- const tasks = await Task.findAll();
- expect(tasks).to.have.length(1);
- expect(tasks[0].userId).to.equal(999);
- });
- }
- });
- describe('association column', () => {
- it('has correct type and name for non-id primary keys with non-integer type', async function () {
- const User = this.sequelize.define('UserPKBT', {
- username: {
- type: DataTypes.STRING,
- },
- });
- const Group = this.sequelize.define('GroupPKBT', {
- name: {
- type: DataTypes.STRING,
- primaryKey: true,
- },
- });
- User.belongsTo(Group);
- await this.sequelize.sync({ force: true });
- expect(User.getAttributes().groupPKBTName.type).to.an.instanceof(DataTypes.STRING);
- });
- it('should support a non-primary key as the association column on a target without a primary key', async function () {
- const User = this.sequelize.define('User', {
- username: { type: DataTypes.STRING, unique: true },
- });
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- User.removeAttribute('id');
- Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });
- await this.sequelize.sync({ force: true });
- const newUser = await User.create({ username: 'bob' });
- const newTask = await Task.create({ title: 'some task' });
- await newTask.setUser(newUser);
- const foundTask = await Task.findOne({ where: { title: 'some task' } });
- const foundUser = await foundTask.getUser();
- await expect(foundUser.username).to.equal('bob');
- const foreignKeysDescriptions = await this.sequelize.queryInterface.showConstraints(Task, {
- constraintType: 'FOREIGN KEY',
- });
- expect(foreignKeysDescriptions[0]).to.deep.include({
- referencedColumnNames: ['username'],
- referencedTableName: 'Users',
- columnNames: ['user_name'],
- });
- });
- it('should support a non-primary unique key as the association column', async function () {
- const User = this.sequelize.define('User', {
- username: {
- type: DataTypes.STRING,
- field: 'user_name',
- unique: true,
- },
- });
- const Task = this.sequelize.define('Task', {
- title: DataTypes.STRING,
- });
- Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });
- await this.sequelize.sync({ force: true });
- const newUser = await User.create({ username: 'bob' });
- const newTask = await Task.create({ title: 'some task' });
- await newTask.setUser(newUser);
- const foundTask = await Task.findOne({ where: { title: 'some task' } });
- const foundUser = await foundTask.getUser();
- await expect(foundUser.username).to.equal('bob');
- const foreignKeysDescriptions = await this.sequelize.queryInterface.showConstraints(Task, {
- constraintType: 'FOREIGN KEY',
- });
- expect(foreignKeysDescriptions[0]).to.deep.include({
- referencedColumnNames: ['user_name'],
- referencedTableName: 'Users',
- columnNames: ['user_name'],
- });
- });
- it('should support a non-primary key as the association column with a field option', async function () {
- const User = this.sequelize.define('User', {
- username: {
- type: DataTypes.STRING,
- field: 'the_user_name_field',
- unique: true,
- },
- });
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- User.removeAttribute('id');
- Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });
- await this.sequelize.sync({ force: true });
- const newUser = await User.create({ username: 'bob' });
- const newTask = await Task.create({ title: 'some task' });
- await newTask.setUser(newUser);
- const foundTask = await Task.findOne({ where: { title: 'some task' } });
- const foundUser = await foundTask.getUser();
- await expect(foundUser.username).to.equal('bob');
- const foreignKeysDescriptions = await this.sequelize.queryInterface.showConstraints(Task, {
- constraintType: 'FOREIGN KEY',
- });
- expect(foreignKeysDescriptions[0]).to.deep.include({
- referencedColumnNames: ['the_user_name_field'],
- referencedTableName: 'Users',
- columnNames: ['user_name'],
- });
- });
- it('should support a non-primary key as the association column in a table with a composite primary key', async function () {
- const User = this.sequelize.define('User', {
- username: {
- type: DataTypes.STRING,
- field: 'the_user_name_field',
- unique: true,
- },
- age: {
- type: DataTypes.INTEGER,
- field: 'the_user_age_field',
- primaryKey: true,
- },
- weight: {
- type: DataTypes.INTEGER,
- field: 'the_user_weight_field',
- primaryKey: true,
- },
- });
- const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
- Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' });
- await this.sequelize.sync({ force: true });
- const newUser = await User.create({ username: 'bob', age: 18, weight: 40 });
- const newTask = await Task.create({ title: 'some task' });
- await newTask.setUser(newUser);
- const foundTask = await Task.findOne({ where: { title: 'some task' } });
- const foundUser = await foundTask.getUser();
- await expect(foundUser.username).to.equal('bob');
- const foreignKeysDescriptions = await this.sequelize.queryInterface.showConstraints(Task, {
- constraintType: 'FOREIGN KEY',
- });
- expect(foreignKeysDescriptions[0]).to.deep.include({
- referencedColumnNames: ['the_user_name_field'],
- referencedTableName: 'Users',
- columnNames: ['user_name'],
- });
- });
- });
- describe('association options', () => {
- it('can specify data type for auto-generated relational keys', async function () {
- const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
- const dataTypes = [DataTypes.INTEGER, DataTypes.STRING];
- const Tasks = {};
- if (current.dialect.supports.dataTypes.BIGINT) {
- dataTypes.push(DataTypes.BIGINT);
- }
- for (const dataType of dataTypes) {
- const tableName = `TaskXYZ_${dataType.getDataTypeId()}`;
- Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING });
- Tasks[dataType].belongsTo(User, {
- foreignKey: { name: 'userId', type: dataType },
- foreignKeyConstraints: false,
- });
- }
- await this.sequelize.sync({ force: true });
- for (const dataType of dataTypes) {
- expect(Tasks[dataType].getAttributes().userId.type).to.be.an.instanceof(dataType);
- }
- });
- describe('allows the user to provide an attribute definition object as foreignKey', () => {
- it('works with a column that hasnt been defined before', function () {
- const Task = this.sequelize.define('task', {});
- const User = this.sequelize.define('user', {});
- Task.belongsTo(User, {
- foreignKey: {
- allowNull: false,
- name: 'uid',
- },
- });
- expect(Task.getAttributes().uid).to.be.ok;
- expect(Task.getAttributes().uid.allowNull).to.be.false;
- const targetTable = Task.getAttributes().uid.references.table;
- assert(typeof targetTable === 'object');
- expect(targetTable).to.deep.equal(User.table);
- expect(Task.getAttributes().uid.references.key).to.equal('id');
- });
- it('works when taking a column directly from the object', function () {
- const User = this.sequelize.define('user', {
- uid: {
- type: DataTypes.INTEGER,
- primaryKey: true,
- },
- });
- const Profile = this.sequelize.define('project', {
- user_id: {
- type: DataTypes.INTEGER,
- allowNull: false,
- },
- });
- Profile.belongsTo(User, { foreignKey: Profile.getAttributes().user_id });
- expect(Profile.getAttributes().user_id).to.be.ok;
- const targetTable = Profile.getAttributes().user_id.references.table;
- assert(typeof targetTable === 'object');
- expect(targetTable).to.deep.equal(User.table);
- expect(Profile.getAttributes().user_id.references.key).to.equal('uid');
- expect(Profile.getAttributes().user_id.allowNull).to.be.false;
- });
- it('works when merging with an existing definition', function () {
- const Task = this.sequelize.define('task', {
- projectId: {
- defaultValue: 42,
- type: DataTypes.INTEGER,
- },
- });
- const Project = this.sequelize.define('project', {});
- Task.belongsTo(Project, { foreignKey: { allowNull: true } });
- expect(Task.getAttributes().projectId).to.be.ok;
- expect(Task.getAttributes().projectId.defaultValue).to.equal(42);
- expect(Task.getAttributes().projectId.allowNull).to.be.ok;
- });
- });
- });
- describe('Eager loading', () => {
- beforeEach(function () {
- this.Individual = this.sequelize.define('individual', {
- name: DataTypes.STRING,
- });
- this.Hat = this.sequelize.define('hat', {
- name: DataTypes.STRING,
- });
- this.Individual.belongsTo(this.Hat, {
- as: 'personwearinghat',
- });
- });
- it('should load with an alias', async function () {
- await this.sequelize.sync({ force: true });
- const [individual1, hat] = await Promise.all([
- this.Individual.create({ name: 'Foo Bar' }),
- this.Hat.create({ name: 'Baz' }),
- ]);
- await individual1.setPersonwearinghat(hat);
- const individual0 = await this.Individual.findOne({
- where: { name: 'Foo Bar' },
- include: [{ model: this.Hat, as: 'personwearinghat' }],
- });
- expect(individual0.name).to.equal('Foo Bar');
- expect(individual0.personwearinghat.name).to.equal('Baz');
- const individual = await this.Individual.findOne({
- where: { name: 'Foo Bar' },
- include: [
- {
- model: this.Hat,
- as: 'personwearinghat',
- },
- ],
- });
- expect(individual.name).to.equal('Foo Bar');
- expect(individual.personwearinghat.name).to.equal('Baz');
- });
- it('should load all', async function () {
- await this.sequelize.sync({ force: true });
- const [individual0, hat] = await Promise.all([
- this.Individual.create({ name: 'Foo Bar' }),
- this.Hat.create({ name: 'Baz' }),
- ]);
- await individual0.setPersonwearinghat(hat);
- const individual = await this.Individual.findOne({
- where: { name: 'Foo Bar' },
- include: [{ all: true }],
- });
- expect(individual.name).to.equal('Foo Bar');
- expect(individual.personwearinghat.name).to.equal('Baz');
- });
- });
- });
|