has-many.test.js 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659
  1. 'use strict';
  2. const range = require('lodash/range');
  3. const { expect } = require('chai');
  4. const { DataTypes, Op, Sequelize } = require('@sequelize/core');
  5. const dayjs = require('dayjs');
  6. const sinon = require('sinon');
  7. const assert = require('node:assert');
  8. const {
  9. allowDeprecationsInSuite,
  10. createSequelizeInstance,
  11. createSingleTransactionalTestSequelizeInstance,
  12. destroySequelizeAfterTest,
  13. getTestDialect,
  14. sequelize: current,
  15. } = require('../support');
  16. const dialectName = getTestDialect();
  17. describe('HasMany', () => {
  18. describe('Model.associations', () => {
  19. it('should store all assocations when associting to the same table multiple times', function () {
  20. const User = this.sequelize.define('User', {});
  21. const Group = this.sequelize.define('Group', {});
  22. Group.hasMany(User);
  23. Group.hasMany(User, {
  24. foreignKey: 'primaryGroupId',
  25. as: 'primaryUsers',
  26. inverse: { as: 'primaryGroup' },
  27. });
  28. Group.hasMany(User, {
  29. foreignKey: 'secondaryGroupId',
  30. as: 'secondaryUsers',
  31. inverse: { as: 'secondaryGroup' },
  32. });
  33. expect(Object.keys(Group.associations)).to.deep.equal([
  34. 'users',
  35. 'primaryUsers',
  36. 'secondaryUsers',
  37. ]);
  38. });
  39. });
  40. describe('count', () => {
  41. it('should not fail due to ambiguous field', async function () {
  42. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  43. const Task = this.sequelize.define('Task', {
  44. title: DataTypes.STRING,
  45. active: DataTypes.BOOLEAN,
  46. });
  47. User.hasMany(Task);
  48. const subtasks = Task.hasMany(Task, { as: 'subtasks', inverse: { as: 'supertask' } });
  49. await this.sequelize.sync({ force: true });
  50. const user0 = await User.create(
  51. {
  52. username: 'John',
  53. tasks: [
  54. {
  55. title: 'Get rich',
  56. active: true,
  57. },
  58. ],
  59. },
  60. {
  61. include: [Task],
  62. },
  63. );
  64. await Promise.all([
  65. user0.get('tasks')[0].createSubtask({ title: 'Make a startup', active: false }),
  66. user0.get('tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }),
  67. ]);
  68. const user = user0;
  69. await expect(
  70. user.countTasks({
  71. attributes: [Task.primaryKeyField, 'title'],
  72. include: [
  73. {
  74. attributes: [],
  75. association: subtasks,
  76. where: {
  77. active: true,
  78. },
  79. },
  80. ],
  81. group: this.sequelize.col(`${Task.name}.${Task.primaryKeyField}`),
  82. }),
  83. ).to.eventually.equal(1);
  84. });
  85. });
  86. describe('get', () => {
  87. if (current.dialect.supports.groupedLimit) {
  88. describe('multiple', () => {
  89. it('should fetch associations for multiple instances', async function () {
  90. const User = this.sequelize.define('User', {});
  91. const Task = this.sequelize.define('Task', {});
  92. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  93. await this.sequelize.sync({ force: true });
  94. const users = await Promise.all([
  95. User.create(
  96. {
  97. id: 1,
  98. tasks: [{}, {}, {}],
  99. },
  100. {
  101. include: [User.Tasks],
  102. },
  103. ),
  104. User.create(
  105. {
  106. id: 2,
  107. tasks: [{}],
  108. },
  109. {
  110. include: [User.Tasks],
  111. },
  112. ),
  113. User.create({
  114. id: 3,
  115. }),
  116. ]);
  117. const result = await User.Tasks.get(users);
  118. expect(result.get(users[0].id).length).to.equal(3);
  119. expect(result.get(users[1].id).length).to.equal(1);
  120. expect(result.get(users[2].id).length).to.equal(0);
  121. });
  122. it('should fetch associations for multiple instances with limit and order', async function () {
  123. const User = this.sequelize.define('User', {});
  124. const Task = this.sequelize.define('Task', {
  125. title: DataTypes.STRING,
  126. });
  127. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  128. await this.sequelize.sync({ force: true });
  129. const users = await Promise.all([
  130. User.create(
  131. {
  132. tasks: [{ title: 'b' }, { title: 'd' }, { title: 'c' }, { title: 'a' }],
  133. },
  134. {
  135. include: [User.Tasks],
  136. },
  137. ),
  138. User.create(
  139. {
  140. tasks: [{ title: 'a' }, { title: 'c' }, { title: 'b' }],
  141. },
  142. {
  143. include: [User.Tasks],
  144. },
  145. ),
  146. ]);
  147. const result = await User.Tasks.get(users, {
  148. limit: 2,
  149. order: [['title', 'ASC']],
  150. });
  151. expect(result.get(users[0].id).length).to.equal(2);
  152. expect(result.get(users[0].id)[0].title).to.equal('a');
  153. expect(result.get(users[0].id)[1].title).to.equal('b');
  154. expect(result.get(users[1].id).length).to.equal(2);
  155. expect(result.get(users[1].id)[0].title).to.equal('a');
  156. expect(result.get(users[1].id)[1].title).to.equal('b');
  157. });
  158. it('should fetch multiple layers of associations with limit and order with separate=true', async function () {
  159. const User = this.sequelize.define('User', {});
  160. const Task = this.sequelize.define('Task', {
  161. title: DataTypes.STRING,
  162. });
  163. const SubTask = this.sequelize.define('SubTask', {
  164. title: DataTypes.STRING,
  165. });
  166. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  167. Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' });
  168. await this.sequelize.sync({ force: true });
  169. await Promise.all([
  170. User.create(
  171. {
  172. id: 1,
  173. tasks: [
  174. {
  175. title: 'b',
  176. subtasks: [{ title: 'c' }, { title: 'a' }],
  177. },
  178. { title: 'd' },
  179. {
  180. title: 'c',
  181. subtasks: [{ title: 'b' }, { title: 'a' }, { title: 'c' }],
  182. },
  183. {
  184. title: 'a',
  185. subtasks: [{ title: 'c' }, { title: 'a' }, { title: 'b' }],
  186. },
  187. ],
  188. },
  189. {
  190. include: [{ association: User.Tasks, include: [Task.SubTasks] }],
  191. },
  192. ),
  193. User.create(
  194. {
  195. id: 2,
  196. tasks: [
  197. {
  198. title: 'a',
  199. subtasks: [{ title: 'b' }, { title: 'a' }, { title: 'c' }],
  200. },
  201. {
  202. title: 'c',
  203. subtasks: [{ title: 'a' }],
  204. },
  205. {
  206. title: 'b',
  207. subtasks: [{ title: 'a' }, { title: 'b' }],
  208. },
  209. ],
  210. },
  211. {
  212. include: [{ association: User.Tasks, include: [Task.SubTasks] }],
  213. },
  214. ),
  215. ]);
  216. const users = await User.findAll({
  217. include: [
  218. {
  219. association: User.Tasks,
  220. limit: 2,
  221. order: [['title', 'ASC']],
  222. separate: true,
  223. as: 'tasks',
  224. include: [
  225. {
  226. association: Task.SubTasks,
  227. order: [['title', 'DESC']],
  228. separate: true,
  229. as: 'subtasks',
  230. },
  231. ],
  232. },
  233. ],
  234. order: [['id', 'ASC']],
  235. });
  236. expect(users[0].tasks.length).to.equal(2);
  237. expect(users[0].tasks[0].title).to.equal('a');
  238. expect(users[0].tasks[0].subtasks.length).to.equal(3);
  239. expect(users[0].tasks[0].subtasks[0].title).to.equal('c');
  240. expect(users[0].tasks[0].subtasks[1].title).to.equal('b');
  241. expect(users[0].tasks[0].subtasks[2].title).to.equal('a');
  242. expect(users[0].tasks[1].title).to.equal('b');
  243. expect(users[0].tasks[1].subtasks.length).to.equal(2);
  244. expect(users[0].tasks[1].subtasks[0].title).to.equal('c');
  245. expect(users[0].tasks[1].subtasks[1].title).to.equal('a');
  246. expect(users[1].tasks.length).to.equal(2);
  247. expect(users[1].tasks[0].title).to.equal('a');
  248. expect(users[1].tasks[0].subtasks.length).to.equal(3);
  249. expect(users[1].tasks[0].subtasks[0].title).to.equal('c');
  250. expect(users[1].tasks[0].subtasks[1].title).to.equal('b');
  251. expect(users[1].tasks[0].subtasks[2].title).to.equal('a');
  252. expect(users[1].tasks[1].title).to.equal('b');
  253. expect(users[1].tasks[1].subtasks.length).to.equal(2);
  254. expect(users[1].tasks[1].subtasks[0].title).to.equal('b');
  255. expect(users[1].tasks[1].subtasks[1].title).to.equal('a');
  256. });
  257. it('should fetch associations for multiple instances with limit and order and a belongsTo relation', async function () {
  258. const User = this.sequelize.define('User', {});
  259. const Task = this.sequelize.define('Task', {
  260. title: DataTypes.STRING,
  261. categoryId: {
  262. type: DataTypes.INTEGER,
  263. field: 'category_id',
  264. },
  265. });
  266. const Category = this.sequelize.define('Category', {});
  267. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  268. Task.Category = Task.belongsTo(Category, { as: 'category', foreignKey: 'categoryId' });
  269. await this.sequelize.sync({ force: true });
  270. const users = await Promise.all([
  271. User.create(
  272. {
  273. tasks: [
  274. { title: 'b', category: {} },
  275. { title: 'd', category: {} },
  276. { title: 'c', category: {} },
  277. { title: 'a', category: {} },
  278. ],
  279. },
  280. {
  281. include: [{ association: User.Tasks, include: [Task.Category] }],
  282. },
  283. ),
  284. User.create(
  285. {
  286. tasks: [
  287. { title: 'a', category: {} },
  288. { title: 'c', category: {} },
  289. { title: 'b', category: {} },
  290. ],
  291. },
  292. {
  293. include: [{ association: User.Tasks, include: [Task.Category] }],
  294. },
  295. ),
  296. ]);
  297. const result = await User.Tasks.get(users, {
  298. limit: 2,
  299. order: [['title', 'ASC']],
  300. include: [Task.Category],
  301. });
  302. expect(result.get(users[0].id).length).to.equal(2);
  303. expect(result.get(users[0].id)[0].title).to.equal('a');
  304. expect(result.get(users[0].id)[0].category).to.be.ok;
  305. expect(result.get(users[0].id)[1].title).to.equal('b');
  306. expect(result.get(users[0].id)[1].category).to.be.ok;
  307. expect(result.get(users[1].id).length).to.equal(2);
  308. expect(result.get(users[1].id)[0].title).to.equal('a');
  309. expect(result.get(users[1].id)[0].category).to.be.ok;
  310. expect(result.get(users[1].id)[1].title).to.equal('b');
  311. expect(result.get(users[1].id)[1].category).to.be.ok;
  312. });
  313. it('supports schemas', async function () {
  314. const User = this.sequelize.define('User', {}).withSchema('work');
  315. const Task = this.sequelize
  316. .define('Task', {
  317. title: DataTypes.STRING,
  318. })
  319. .withSchema('work');
  320. const SubTask = this.sequelize
  321. .define('SubTask', {
  322. title: DataTypes.STRING,
  323. })
  324. .withSchema('work');
  325. User.Tasks = User.hasMany(Task, { as: 'tasks' });
  326. Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' });
  327. await this.sequelize.createSchema('work');
  328. await User.sync({ force: true });
  329. await Task.sync({ force: true });
  330. await SubTask.sync({ force: true });
  331. await Promise.all([
  332. User.create(
  333. {
  334. id: 1,
  335. tasks: [
  336. {
  337. title: 'b',
  338. subtasks: [{ title: 'c' }, { title: 'a' }],
  339. },
  340. { title: 'd' },
  341. {
  342. title: 'c',
  343. subtasks: [{ title: 'b' }, { title: 'a' }, { title: 'c' }],
  344. },
  345. {
  346. title: 'a',
  347. subtasks: [{ title: 'c' }, { title: 'a' }, { title: 'b' }],
  348. },
  349. ],
  350. },
  351. {
  352. include: [{ association: User.Tasks, include: [Task.SubTasks] }],
  353. },
  354. ),
  355. User.create(
  356. {
  357. id: 2,
  358. tasks: [
  359. {
  360. title: 'a',
  361. subtasks: [{ title: 'b' }, { title: 'a' }, { title: 'c' }],
  362. },
  363. {
  364. title: 'c',
  365. subtasks: [{ title: 'a' }],
  366. },
  367. {
  368. title: 'b',
  369. subtasks: [{ title: 'a' }, { title: 'b' }],
  370. },
  371. ],
  372. },
  373. {
  374. include: [{ association: User.Tasks, include: [Task.SubTasks] }],
  375. },
  376. ),
  377. ]);
  378. const users = await User.findAll({
  379. include: [
  380. {
  381. association: User.Tasks,
  382. limit: 2,
  383. order: [['title', 'ASC']],
  384. separate: true,
  385. as: 'tasks',
  386. include: [
  387. {
  388. association: Task.SubTasks,
  389. order: [['title', 'DESC']],
  390. separate: true,
  391. as: 'subtasks',
  392. },
  393. ],
  394. },
  395. ],
  396. order: [['id', 'ASC']],
  397. });
  398. expect(users[0].tasks.length).to.equal(2);
  399. expect(users[0].tasks[0].title).to.equal('a');
  400. expect(users[0].tasks[0].subtasks.length).to.equal(3);
  401. expect(users[0].tasks[0].subtasks[0].title).to.equal('c');
  402. expect(users[0].tasks[0].subtasks[1].title).to.equal('b');
  403. expect(users[0].tasks[0].subtasks[2].title).to.equal('a');
  404. expect(users[0].tasks[1].title).to.equal('b');
  405. expect(users[0].tasks[1].subtasks.length).to.equal(2);
  406. expect(users[0].tasks[1].subtasks[0].title).to.equal('c');
  407. expect(users[0].tasks[1].subtasks[1].title).to.equal('a');
  408. expect(users[1].tasks.length).to.equal(2);
  409. expect(users[1].tasks[0].title).to.equal('a');
  410. expect(users[1].tasks[0].subtasks.length).to.equal(3);
  411. expect(users[1].tasks[0].subtasks[0].title).to.equal('c');
  412. expect(users[1].tasks[0].subtasks[1].title).to.equal('b');
  413. expect(users[1].tasks[0].subtasks[2].title).to.equal('a');
  414. expect(users[1].tasks[1].title).to.equal('b');
  415. expect(users[1].tasks[1].subtasks.length).to.equal(2);
  416. expect(users[1].tasks[1].subtasks[0].title).to.equal('b');
  417. expect(users[1].tasks[1].subtasks[1].title).to.equal('a');
  418. await this.sequelize.queryInterface.dropAllTables({ schema: 'work' });
  419. await this.sequelize.dropSchema('work');
  420. const schemas = await this.sequelize.queryInterface.listSchemas();
  421. expect(schemas).to.not.include('work');
  422. });
  423. });
  424. }
  425. });
  426. describe('(1:N)', () => {
  427. describe('hasAssociation', () => {
  428. beforeEach(function () {
  429. this.Article = this.sequelize.define('Article', {
  430. pk: {
  431. type: DataTypes.INTEGER,
  432. autoIncrement: true,
  433. primaryKey: true,
  434. },
  435. title: DataTypes.STRING,
  436. });
  437. this.Label = this.sequelize.define('Label', {
  438. key: {
  439. type: DataTypes.INTEGER,
  440. autoIncrement: true,
  441. primaryKey: true,
  442. },
  443. text: DataTypes.STRING,
  444. });
  445. this.Article.hasMany(this.Label);
  446. return this.sequelize.sync({ force: true });
  447. });
  448. it('should only generate one set of foreignKeys', function () {
  449. this.Article = this.sequelize.define(
  450. 'Article',
  451. { title: DataTypes.STRING },
  452. { timestamps: false },
  453. );
  454. this.Label = this.sequelize.define(
  455. 'Label',
  456. { text: DataTypes.STRING },
  457. { timestamps: false },
  458. );
  459. this.Label.belongsTo(this.Article);
  460. this.Article.hasMany(this.Label);
  461. expect(Object.keys(this.Label.getAttributes())).to.deep.equal(['id', 'text', 'articleId']);
  462. });
  463. if (current.dialect.supports.transactions) {
  464. it('supports transactions', async function () {
  465. const sequelize = await createSingleTransactionalTestSequelizeInstance(this.sequelize);
  466. const Article = sequelize.define('Article', { title: DataTypes.STRING });
  467. const Label = sequelize.define('Label', { text: DataTypes.STRING });
  468. Article.hasMany(Label);
  469. await sequelize.sync({ force: true });
  470. const [article, label] = await Promise.all([
  471. Article.create({ title: 'foo' }),
  472. Label.create({ text: 'bar' }),
  473. ]);
  474. const t = await sequelize.startUnmanagedTransaction();
  475. await article.setLabels([label], { transaction: t });
  476. const articles0 = await Article.findAll({ transaction: t });
  477. const hasLabel0 = await articles0[0].hasLabel(label);
  478. expect(hasLabel0).to.be.false;
  479. const articles = await Article.findAll({ transaction: t });
  480. const hasLabel = await articles[0].hasLabel(label, { transaction: t });
  481. expect(hasLabel).to.be.true;
  482. await t.rollback();
  483. });
  484. }
  485. it('does not have any labels assigned to it initially', async function () {
  486. const [article, label1, label2] = await Promise.all([
  487. this.Article.create({ title: 'Article' }),
  488. this.Label.create({ text: 'Awesomeness' }),
  489. this.Label.create({ text: 'Epicness' }),
  490. ]);
  491. const [hasLabel1, hasLabel2] = await Promise.all([
  492. article.hasLabel(label1),
  493. article.hasLabel(label2),
  494. ]);
  495. expect(hasLabel1).to.be.false;
  496. expect(hasLabel2).to.be.false;
  497. });
  498. it('answers true if the label has been assigned', async function () {
  499. const [article, label1, label2] = await Promise.all([
  500. this.Article.create({ title: 'Article' }),
  501. this.Label.create({ text: 'Awesomeness' }),
  502. this.Label.create({ text: 'Epicness' }),
  503. ]);
  504. await article.addLabel(label1);
  505. const [hasLabel1, hasLabel2] = await Promise.all([
  506. article.hasLabel(label1),
  507. article.hasLabel(label2),
  508. ]);
  509. expect(hasLabel1).to.be.true;
  510. expect(hasLabel2).to.be.false;
  511. });
  512. it('answers correctly if the label has been assigned when passing a primary key instead of an object', async function () {
  513. const [article, label1, label2] = await Promise.all([
  514. this.Article.create({ title: 'Article' }),
  515. this.Label.create({ text: 'Awesomeness' }),
  516. this.Label.create({ text: 'Epicness' }),
  517. ]);
  518. await article.addLabel(label1);
  519. const [hasLabel1, hasLabel2] = await Promise.all([
  520. article.hasLabel(label1[this.Label.primaryKeyAttribute]),
  521. article.hasLabel(label2[this.Label.primaryKeyAttribute]),
  522. ]);
  523. expect(hasLabel1).to.be.true;
  524. expect(hasLabel2).to.be.false;
  525. });
  526. });
  527. describe('hasAssociations', () => {
  528. beforeEach(function () {
  529. this.Article = this.sequelize.define('Article', {
  530. pk: {
  531. type: DataTypes.INTEGER,
  532. autoIncrement: true,
  533. primaryKey: true,
  534. },
  535. title: DataTypes.STRING,
  536. });
  537. this.Label = this.sequelize.define('Label', {
  538. key: {
  539. type: DataTypes.INTEGER,
  540. autoIncrement: true,
  541. primaryKey: true,
  542. },
  543. text: DataTypes.STRING,
  544. });
  545. this.Article.hasMany(this.Label);
  546. return this.sequelize.sync({ force: true });
  547. });
  548. if (current.dialect.supports.transactions) {
  549. it('supports transactions', async function () {
  550. const sequelize = await createSingleTransactionalTestSequelizeInstance(this.sequelize);
  551. const Article = sequelize.define('Article', { title: DataTypes.STRING });
  552. const Label = sequelize.define('Label', { text: DataTypes.STRING });
  553. Article.hasMany(Label);
  554. await sequelize.sync({ force: true });
  555. const [article, label] = await Promise.all([
  556. Article.create({ title: 'foo' }),
  557. Label.create({ text: 'bar' }),
  558. ]);
  559. const t = await sequelize.startUnmanagedTransaction();
  560. await article.setLabels([label], { transaction: t });
  561. const articles = await Article.findAll({ transaction: t });
  562. const [hasLabel1, hasLabel2] = await Promise.all([
  563. articles[0].hasLabels([label]),
  564. articles[0].hasLabels([label], { transaction: t }),
  565. ]);
  566. expect(hasLabel1).to.be.false;
  567. expect(hasLabel2).to.be.true;
  568. await t.rollback();
  569. });
  570. }
  571. it('answers false if only some labels have been assigned', async function () {
  572. const [article, label1, label2] = await Promise.all([
  573. this.Article.create({ title: 'Article' }),
  574. this.Label.create({ text: 'Awesomeness' }),
  575. this.Label.create({ text: 'Epicness' }),
  576. ]);
  577. await article.addLabel(label1);
  578. const result = await article.hasLabels([label1, label2]);
  579. expect(result).to.be.false;
  580. });
  581. it('answers false if only some labels have been assigned when passing a primary key instead of an object', async function () {
  582. const [article, label1, label2] = await Promise.all([
  583. this.Article.create({ title: 'Article' }),
  584. this.Label.create({ text: 'Awesomeness' }),
  585. this.Label.create({ text: 'Epicness' }),
  586. ]);
  587. await article.addLabel(label1);
  588. const result = await article.hasLabels([
  589. label1[this.Label.primaryKeyAttribute],
  590. label2[this.Label.primaryKeyAttribute],
  591. ]);
  592. expect(result).to.be.false;
  593. });
  594. it('answers true if all label have been assigned', async function () {
  595. const [article, label1, label2] = await Promise.all([
  596. this.Article.create({ title: 'Article' }),
  597. this.Label.create({ text: 'Awesomeness' }),
  598. this.Label.create({ text: 'Epicness' }),
  599. ]);
  600. await article.setLabels([label1, label2]);
  601. const result = await article.hasLabels([label1, label2]);
  602. expect(result).to.be.true;
  603. });
  604. it('answers true if all label have been assigned when passing a primary key instead of an object', async function () {
  605. const [article, label1, label2] = await Promise.all([
  606. this.Article.create({ title: 'Article' }),
  607. this.Label.create({ text: 'Awesomeness' }),
  608. this.Label.create({ text: 'Epicness' }),
  609. ]);
  610. await article.setLabels([label1, label2]);
  611. const result = await article.hasLabels([
  612. label1[this.Label.primaryKeyAttribute],
  613. label2[this.Label.primaryKeyAttribute],
  614. ]);
  615. expect(result).to.be.true;
  616. });
  617. });
  618. describe('addAssociations', () => {
  619. if (current.dialect.supports.transactions) {
  620. it('supports transactions', async function () {
  621. const sequelize = await createSingleTransactionalTestSequelizeInstance(this.sequelize);
  622. const Article = sequelize.define('Article', { title: DataTypes.STRING });
  623. const Label = sequelize.define('Label', { text: DataTypes.STRING });
  624. Article.hasMany(Label);
  625. await sequelize.sync({ force: true });
  626. const [article, label] = await Promise.all([
  627. Article.create({ title: 'foo' }),
  628. Label.create({ text: 'bar' }),
  629. ]);
  630. const t = await sequelize.startUnmanagedTransaction();
  631. await article.addLabel(label, { transaction: t });
  632. const labels0 = await Label.findAll({
  633. where: { articleId: article.id },
  634. transaction: undefined,
  635. });
  636. expect(labels0.length).to.equal(0);
  637. const labels = await Label.findAll({ where: { articleId: article.id }, transaction: t });
  638. expect(labels.length).to.equal(1);
  639. await t.rollback();
  640. });
  641. }
  642. it('supports passing the primary key instead of an object', async function () {
  643. const Article = this.sequelize.define('Article', { title: DataTypes.STRING });
  644. const Label = this.sequelize.define('Label', { text: DataTypes.STRING });
  645. Article.hasMany(Label);
  646. await this.sequelize.sync({ force: true });
  647. const [article, label] = await Promise.all([
  648. Article.create({}),
  649. Label.create({ text: 'label one' }),
  650. ]);
  651. await article.addLabel(label.id);
  652. const labels = await article.getLabels();
  653. expect(labels[0].text).to.equal('label one'); // Make sure that we didn't modify one of the other attributes while building / saving a new instance
  654. });
  655. });
  656. describe('addMultipleAssociations', () => {
  657. it('adds associations without removing the current ones', async function () {
  658. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  659. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  660. Task.hasMany(User);
  661. await this.sequelize.sync({ force: true });
  662. await User.bulkCreate([{ username: 'foo ' }, { username: 'bar ' }, { username: 'baz ' }]);
  663. const task = await Task.create({ title: 'task' });
  664. const users0 = await User.findAll();
  665. const users = users0;
  666. await task.setUsers([users0[0]]);
  667. await task.addUsers([users[1], users[2]]);
  668. expect(await task.getUsers()).to.have.length(3);
  669. });
  670. it('handles decent sized bulk creates', async function () {
  671. const User = this.sequelize.define('User', {
  672. username: DataTypes.STRING,
  673. num: DataTypes.INTEGER,
  674. status: DataTypes.STRING,
  675. });
  676. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  677. Task.hasMany(User);
  678. await this.sequelize.sync({ force: true });
  679. const users0 = range(1000).map(i => ({ username: `user${i}`, num: i, status: 'live' }));
  680. await User.bulkCreate(users0);
  681. await Task.create({ title: 'task' });
  682. const users = await User.findAll();
  683. expect(users).to.have.length(1000);
  684. });
  685. });
  686. it('clears associations when passing null to the set-method with omitNull set to true', async () => {
  687. const sequelize = createSequelizeInstance({
  688. omitNull: true,
  689. });
  690. destroySequelizeAfterTest(sequelize);
  691. const User = sequelize.define('User', { username: DataTypes.STRING });
  692. const Task = sequelize.define('Task', { title: DataTypes.STRING });
  693. Task.hasMany(User);
  694. await sequelize.sync({ force: true });
  695. const user = await User.create({ username: 'foo' });
  696. const task = await Task.create({ title: 'task' });
  697. await task.setUsers([user]);
  698. expect(await task.getUsers()).to.have.length(1);
  699. await task.setUsers(null);
  700. expect(await task.getUsers()).to.have.length(0);
  701. });
  702. describe('createAssociations', () => {
  703. it('creates a new associated object', async function () {
  704. const Article = this.sequelize.define('Article', { title: DataTypes.STRING });
  705. const Label = this.sequelize.define('Label', { text: DataTypes.STRING });
  706. Article.hasMany(Label);
  707. await this.sequelize.sync({ force: true });
  708. const article0 = await Article.create({ title: 'foo' });
  709. await article0.createLabel({ text: 'bar' });
  710. const article = article0;
  711. const labels = await Label.findAll({ where: { articleId: article.id } });
  712. expect(labels.length).to.equal(1);
  713. });
  714. it('creates the object with the association directly', async function () {
  715. const spy = sinon.spy();
  716. const Article = this.sequelize.define('Article', {
  717. title: DataTypes.STRING,
  718. });
  719. const Label = this.sequelize.define('Label', {
  720. text: DataTypes.STRING,
  721. });
  722. Article.hasMany(Label);
  723. await this.sequelize.sync({ force: true });
  724. const article = await Article.create({ title: 'foo' });
  725. const label = await article.createLabel({ text: 'bar' }, { logging: spy });
  726. expect(spy.calledOnce).to.be.true;
  727. expect(label.articleId).to.equal(article.id);
  728. });
  729. if (current.dialect.supports.transactions) {
  730. it('supports transactions', async function () {
  731. const sequelize = await createSingleTransactionalTestSequelizeInstance(this.sequelize);
  732. const Article = sequelize.define('Article', { title: DataTypes.STRING });
  733. const Label = sequelize.define('Label', { text: DataTypes.STRING });
  734. Article.hasMany(Label);
  735. await sequelize.sync({ force: true });
  736. const article = await Article.create({ title: 'foo' });
  737. const t = await sequelize.startUnmanagedTransaction();
  738. await article.createLabel({ text: 'bar' }, { transaction: t });
  739. const labels1 = await Label.findAll();
  740. expect(labels1.length).to.equal(0);
  741. const labels0 = await Label.findAll({ where: { articleId: article.id } });
  742. expect(labels0.length).to.equal(0);
  743. const labels = await Label.findAll({ where: { articleId: article.id }, transaction: t });
  744. expect(labels.length).to.equal(1);
  745. await t.rollback();
  746. });
  747. }
  748. it('supports passing the field option', async function () {
  749. const Article = this.sequelize.define('Article', {
  750. title: DataTypes.STRING,
  751. });
  752. const Label = this.sequelize.define('Label', {
  753. text: DataTypes.STRING,
  754. });
  755. Article.hasMany(Label);
  756. await this.sequelize.sync({ force: true });
  757. const article0 = await Article.create();
  758. await article0.createLabel(
  759. {
  760. text: 'yolo',
  761. },
  762. {
  763. fields: ['text'],
  764. },
  765. );
  766. const article = article0;
  767. const labels = await article.getLabels();
  768. expect(labels.length).to.be.ok;
  769. });
  770. });
  771. describe('getting assocations with options', () => {
  772. beforeEach(async function () {
  773. this.User = this.sequelize.define('User', { username: DataTypes.STRING });
  774. this.Task = this.sequelize.define('Task', {
  775. title: DataTypes.STRING,
  776. active: DataTypes.BOOLEAN,
  777. });
  778. this.User.hasMany(this.Task);
  779. await this.sequelize.sync({ force: true });
  780. const [john, task1, task2] = await Promise.all([
  781. this.User.create({ username: 'John' }),
  782. this.Task.create({ title: 'Get rich', active: true }),
  783. this.Task.create({ title: 'Die trying', active: false }),
  784. ]);
  785. return john.setTasks([task1, task2]);
  786. });
  787. it('should treat the where object of associations as a first class citizen', async function () {
  788. this.Article = this.sequelize.define('Article', {
  789. title: DataTypes.STRING,
  790. });
  791. this.Label = this.sequelize.define('Label', {
  792. text: DataTypes.STRING,
  793. until: DataTypes.DATE,
  794. });
  795. this.Article.hasMany(this.Label);
  796. await this.sequelize.sync({ force: true });
  797. const [article, label1, label2] = await Promise.all([
  798. this.Article.create({ title: 'Article' }),
  799. this.Label.create({ text: 'Awesomeness', until: '2014-01-01 01:00:00' }),
  800. this.Label.create({ text: 'Epicness', until: '2014-01-03 01:00:00' }),
  801. ]);
  802. await article.setLabels([label1, label2]);
  803. const labels = await article.getLabels({
  804. where: { until: { [Op.gt]: dayjs('2014-01-02').toDate() } },
  805. });
  806. expect(labels).to.be.instanceof(Array);
  807. expect(labels).to.have.length(1);
  808. expect(labels[0].text).to.equal('Epicness');
  809. });
  810. it('gets all associated objects when no options are passed', async function () {
  811. const john = await this.User.findOne({ where: { username: 'John' } });
  812. const tasks = await john.getTasks();
  813. expect(tasks).to.have.length(2);
  814. });
  815. it('only get objects that fulfill the options', async function () {
  816. const john = await this.User.findOne({ where: { username: 'John' } });
  817. const tasks = await john.getTasks({
  818. where: { active: true },
  819. limit: 10,
  820. order: [['id', 'DESC']],
  821. });
  822. expect(tasks).to.have.length(1);
  823. });
  824. });
  825. describe('countAssociations', () => {
  826. beforeEach(async function () {
  827. this.User = this.sequelize.define('User', { username: DataTypes.STRING });
  828. this.Task = this.sequelize.define('Task', {
  829. title: DataTypes.STRING,
  830. active: DataTypes.BOOLEAN,
  831. });
  832. this.User.hasMany(this.Task, {
  833. foreignKey: 'userId',
  834. });
  835. await this.sequelize.sync({ force: true });
  836. const [john, task1, task2] = await Promise.all([
  837. this.User.create({ username: 'John' }),
  838. this.Task.create({ title: 'Get rich', active: true }),
  839. this.Task.create({ title: 'Die trying', active: false }),
  840. ]);
  841. this.user = john;
  842. return john.setTasks([task1, task2]);
  843. });
  844. it('should count all associations', async function () {
  845. await expect(this.user.countTasks({})).to.eventually.equal(2);
  846. });
  847. it('should count filtered associations', async function () {
  848. await expect(
  849. this.user.countTasks({
  850. where: {
  851. active: true,
  852. },
  853. }),
  854. ).to.eventually.equal(1);
  855. });
  856. it('should count scoped associations', async function () {
  857. this.User.hasMany(this.Task, {
  858. foreignKey: 'userId',
  859. as: 'activeTasks',
  860. inverse: {
  861. as: 'activeUsers',
  862. },
  863. scope: {
  864. active: true,
  865. },
  866. });
  867. await expect(this.user.countActiveTasks({})).to.eventually.equal(1);
  868. });
  869. });
  870. describe('thisAssociations', () => {
  871. it('should work with alias', async function () {
  872. const Person = this.sequelize.define('Group', {});
  873. Person.hasMany(Person, { as: 'Children', inverse: { as: 'parent' } });
  874. await this.sequelize.sync();
  875. });
  876. });
  877. });
  878. describe('foreign key constraints', () => {
  879. describe('1:m', () => {
  880. it('sets null by default', async function () {
  881. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  882. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  883. User.hasMany(Task);
  884. await this.sequelize.sync({ force: true });
  885. const [user, task0] = await Promise.all([
  886. User.create({ username: 'foo' }),
  887. Task.create({ title: 'task' }),
  888. ]);
  889. await user.setTasks([task0]);
  890. await user.destroy();
  891. const task = await task0.reload();
  892. expect(task.userId).to.equal(null);
  893. });
  894. it('sets to CASCADE if allowNull: false', async function () {
  895. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  896. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  897. User.hasMany(Task, { foreignKey: { allowNull: false } }); // defaults to CASCADE
  898. await this.sequelize.sync({ force: true });
  899. const user = await User.create({ username: 'foo' });
  900. await Task.create({ title: 'task', userId: user.id });
  901. await user.destroy();
  902. const tasks = await Task.findAll();
  903. expect(tasks).to.be.empty;
  904. });
  905. it('should be possible to remove all constraints', async function () {
  906. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  907. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  908. User.hasMany(Task, { foreignKeyConstraints: false });
  909. await this.sequelize.sync({ force: true });
  910. const [user, task0] = await Promise.all([
  911. User.create({ username: 'foo' }),
  912. Task.create({ title: 'task' }),
  913. ]);
  914. const task = task0;
  915. await user.setTasks([task0]);
  916. await user.destroy();
  917. await task.reload();
  918. expect(task.userId).to.equal(user.id);
  919. });
  920. it('can cascade deletes', async function () {
  921. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  922. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  923. User.hasMany(Task, { foreignKey: { onDelete: 'cascade' } });
  924. await this.sequelize.sync({ force: true });
  925. const [user, task] = await Promise.all([
  926. User.create({ username: 'foo' }),
  927. Task.create({ title: 'task' }),
  928. ]);
  929. await user.setTasks([task]);
  930. await user.destroy();
  931. const tasks = await Task.findAll();
  932. expect(tasks).to.have.length(0);
  933. });
  934. // NOTE: mssql does not support changing an autoincrement primary key
  935. if (!['mssql', 'db2', 'ibmi'].includes(dialectName)) {
  936. it('can cascade updates', async function () {
  937. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  938. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  939. User.hasMany(Task, { foreignKey: { onUpdate: 'cascade' } });
  940. await this.sequelize.sync({ force: true });
  941. const [user0, task] = await Promise.all([
  942. User.create({ username: 'foo' }),
  943. Task.create({ title: 'task' }),
  944. ]);
  945. await user0.setTasks([task]);
  946. const user = user0;
  947. // Changing the id of a DAO requires a little dance since
  948. // the `UPDATE` query generated by `save()` uses `id` in the
  949. // `WHERE` clause
  950. const tableName = User.table;
  951. await user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id });
  952. const tasks = await Task.findAll();
  953. expect(tasks).to.have.length(1);
  954. expect(tasks[0].userId).to.equal(999);
  955. });
  956. }
  957. if (current.dialect.supports.constraints.restrict) {
  958. it('can restrict deletes', async function () {
  959. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  960. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  961. User.hasMany(Task, { foreignKey: { onDelete: 'restrict' } });
  962. let tasks;
  963. await this.sequelize.sync({ force: true });
  964. const [user, task] = await Promise.all([
  965. User.create({ username: 'foo' }),
  966. Task.create({ title: 'task' }),
  967. ]);
  968. await user.setTasks([task]);
  969. try {
  970. tasks = await user.destroy();
  971. } catch (error) {
  972. if (!(error instanceof Sequelize.ForeignKeyConstraintError)) {
  973. throw error;
  974. }
  975. // Should fail due to FK violation
  976. tasks = await Task.findAll();
  977. }
  978. expect(tasks).to.have.length(1);
  979. });
  980. it('can restrict updates', async function () {
  981. const Task = this.sequelize.define('Task', { title: DataTypes.STRING });
  982. const User = this.sequelize.define('User', { username: DataTypes.STRING });
  983. User.hasMany(Task, { foreignKey: { onUpdate: 'restrict' } });
  984. let tasks;
  985. await this.sequelize.sync({ force: true });
  986. const [user0, task] = await Promise.all([
  987. User.create({ username: 'foo' }),
  988. Task.create({ title: 'task' }),
  989. ]);
  990. await user0.setTasks([task]);
  991. const user = user0;
  992. // Changing the id of a DAO requires a little dance since
  993. // the `UPDATE` query generated by `save()` uses `id` in the
  994. // `WHERE` clause
  995. const tableName = User.table;
  996. try {
  997. tasks = await user.sequelize.queryInterface.update(
  998. user,
  999. tableName,
  1000. { id: 999 },
  1001. { id: user.id },
  1002. );
  1003. } catch (error) {
  1004. if (!(error instanceof Sequelize.ForeignKeyConstraintError)) {
  1005. throw error;
  1006. }
  1007. // Should fail due to FK violation
  1008. tasks = await Task.findAll();
  1009. }
  1010. expect(tasks).to.have.length(1);
  1011. });
  1012. }
  1013. });
  1014. });
  1015. describe('Association options', () => {
  1016. allowDeprecationsInSuite(['SEQUELIZE0005']);
  1017. it('should setup underscored field with foreign keys when using underscored', function () {
  1018. const User = this.sequelize.define(
  1019. 'User',
  1020. { username: DataTypes.STRING },
  1021. { underscored: true },
  1022. );
  1023. const Account = this.sequelize.define(
  1024. 'Account',
  1025. { name: DataTypes.STRING },
  1026. { underscored: true },
  1027. );
  1028. User.hasMany(Account);
  1029. expect(Account.getAttributes().userId).to.exist;
  1030. expect(Account.getAttributes().userId.field).to.equal('user_id');
  1031. });
  1032. it('should use model name when using camelcase', function () {
  1033. const User = this.sequelize.define(
  1034. 'User',
  1035. { username: DataTypes.STRING },
  1036. { underscored: false },
  1037. );
  1038. const Account = this.sequelize.define(
  1039. 'Account',
  1040. { name: DataTypes.STRING },
  1041. { underscored: false },
  1042. );
  1043. User.hasMany(Account);
  1044. expect(Account.getAttributes().userId).to.exist;
  1045. expect(Account.getAttributes().userId.field).to.equal('userId');
  1046. });
  1047. it('can specify data type for auto-generated relational keys', async function () {
  1048. const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING });
  1049. const dataTypes = [DataTypes.INTEGER, DataTypes.STRING];
  1050. const Tasks = {};
  1051. if (current.dialect.supports.dataTypes.BIGINT) {
  1052. dataTypes.push(DataTypes.BIGINT);
  1053. }
  1054. for (const dataType of dataTypes) {
  1055. const tableName = `TaskXYZ_${dataType.getDataTypeId()}`;
  1056. Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING });
  1057. User.hasMany(Tasks[dataType], {
  1058. foreignKey: { name: 'userId', type: dataType },
  1059. foreignKeyConstraints: false,
  1060. });
  1061. await Tasks[dataType].sync({ force: true });
  1062. expect(Tasks[dataType].getAttributes().userId.type).to.be.an.instanceof(dataType);
  1063. }
  1064. });
  1065. it('infers the foreignKey.type if none provided', async function () {
  1066. const User = this.sequelize.define('User', {
  1067. id: { type: DataTypes.STRING, primaryKey: true },
  1068. username: DataTypes.STRING,
  1069. });
  1070. const Task = this.sequelize.define('Task', {
  1071. title: DataTypes.STRING,
  1072. });
  1073. User.hasMany(Task);
  1074. await this.sequelize.sync({ force: true });
  1075. expect(Task.getAttributes().userId.type instanceof DataTypes.STRING).to.be.ok;
  1076. });
  1077. describe('allows the user to provide an attribute definition object as foreignKey', () => {
  1078. it('works with a column that hasnt been defined before', function () {
  1079. const Task = this.sequelize.define('task', {});
  1080. const User = this.sequelize.define('user', {});
  1081. User.hasMany(Task, {
  1082. foreignKey: {
  1083. name: 'uid',
  1084. allowNull: false,
  1085. },
  1086. });
  1087. expect(Task.getAttributes().uid).to.be.ok;
  1088. expect(Task.getAttributes().uid.allowNull).to.be.false;
  1089. const targetTable = Task.getAttributes().uid.references.table;
  1090. assert(typeof targetTable === 'object');
  1091. expect(targetTable).to.deep.equal(User.table);
  1092. expect(Task.getAttributes().uid.references.key).to.equal('id');
  1093. });
  1094. it('works when taking a column directly from the object', function () {
  1095. const Project = this.sequelize.define('project', {
  1096. user_id: {
  1097. type: DataTypes.INTEGER,
  1098. defaultValue: 42,
  1099. },
  1100. });
  1101. const User = this.sequelize.define('user', {
  1102. uid: {
  1103. type: DataTypes.INTEGER,
  1104. primaryKey: true,
  1105. },
  1106. });
  1107. User.hasMany(Project, { foreignKey: Project.getAttributes().user_id });
  1108. expect(Project.getAttributes().user_id).to.be.ok;
  1109. const targetTable = Project.getAttributes().user_id.references.table;
  1110. assert(typeof targetTable === 'object');
  1111. expect(targetTable).to.deep.equal(User.table);
  1112. expect(Project.getAttributes().user_id.references.key).to.equal('uid');
  1113. expect(Project.getAttributes().user_id.defaultValue).to.equal(42);
  1114. });
  1115. it('works when merging with an existing definition', function () {
  1116. const Task = this.sequelize.define('task', {
  1117. userId: {
  1118. defaultValue: 42,
  1119. type: DataTypes.INTEGER,
  1120. },
  1121. });
  1122. const User = this.sequelize.define('user', {});
  1123. User.hasMany(Task, { foreignKey: { allowNull: true } });
  1124. expect(Task.getAttributes().userId).to.be.ok;
  1125. expect(Task.getAttributes().userId.defaultValue).to.equal(42);
  1126. expect(Task.getAttributes().userId.allowNull).to.be.ok;
  1127. });
  1128. });
  1129. it('should throw an error if foreignKey and as result in a name clash', function () {
  1130. const User = this.sequelize.define('user', {
  1131. user: DataTypes.INTEGER,
  1132. });
  1133. expect(User.hasMany.bind(User, User, { as: 'user' })).to.throw(
  1134. "Naming collision between attribute 'user' and association 'user' on model user. To remedy this, change the \"as\" options in your association definition",
  1135. );
  1136. });
  1137. it('should ignore group from ancestor on deep separated query', async function () {
  1138. const User = this.sequelize.define('user', {
  1139. userId: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  1140. username: DataTypes.STRING,
  1141. });
  1142. const Task = this.sequelize.define('task', {
  1143. taskId: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  1144. title: DataTypes.STRING,
  1145. });
  1146. const Job = this.sequelize.define('job', {
  1147. jobId: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
  1148. title: DataTypes.STRING,
  1149. });
  1150. Task.hasMany(Job, { foreignKey: 'taskId' });
  1151. User.hasMany(Task, { foreignKey: 'userId' });
  1152. await this.sequelize.sync({ force: true });
  1153. await User.create(
  1154. {
  1155. username: 'John Doe',
  1156. tasks: [
  1157. { title: 'Task #1', jobs: [{ title: 'Job #1' }, { title: 'Job #2' }] },
  1158. { title: 'Task #2', jobs: [{ title: 'Job #3' }, { title: 'Job #4' }] },
  1159. ],
  1160. },
  1161. { include: [{ model: Task, include: [Job] }] },
  1162. );
  1163. const { count, rows } = await User.findAndCountAll({
  1164. attributes: ['userId'],
  1165. include: [{ model: Task, separate: true, include: [{ model: Job, separate: true }] }],
  1166. group: [['userId']],
  1167. });
  1168. expect(count.length).to.equal(1);
  1169. expect(count).to.deep.equal([{ userId: 1, count: 1 }]);
  1170. expect(rows[0].tasks[0].jobs.length).to.equal(2);
  1171. });
  1172. });
  1173. describe('sourceKey', () => {
  1174. beforeEach(function () {
  1175. const User = this.sequelize.define(
  1176. 'UserXYZ',
  1177. { username: DataTypes.STRING, email: { type: DataTypes.STRING, allowNull: false } },
  1178. { indexes: [{ fields: ['email'], unique: true }] },
  1179. );
  1180. const Task = this.sequelize.define('TaskXYZ', {
  1181. title: DataTypes.STRING,
  1182. userEmail: { type: DataTypes.STRING, field: 'user_email_xyz' },
  1183. });
  1184. User.hasMany(Task, { foreignKey: 'userEmail', sourceKey: 'email', as: 'tasks' });
  1185. this.User = User;
  1186. this.Task = Task;
  1187. return this.sequelize.sync({ force: true });
  1188. });
  1189. it('should use sourceKey', async function () {
  1190. const User = this.User;
  1191. const Task = this.Task;
  1192. const user = await User.create({ username: 'John', email: 'john@example.com' });
  1193. await Task.create({ title: 'Fix PR', userEmail: 'john@example.com' });
  1194. const tasks = await user.getTasks();
  1195. expect(tasks.length).to.equal(1);
  1196. expect(tasks[0].title).to.equal('Fix PR');
  1197. });
  1198. it('should count related records', async function () {
  1199. const User = this.User;
  1200. const Task = this.Task;
  1201. const user = await User.create({ username: 'John', email: 'john@example.com' });
  1202. await Task.create({ title: 'Fix PR', userEmail: 'john@example.com' });
  1203. const tasksCount = await user.countTasks();
  1204. expect(tasksCount).to.equal(1);
  1205. });
  1206. it('should set right field when add relative', async function () {
  1207. const User = this.User;
  1208. const Task = this.Task;
  1209. const user = await User.create({ username: 'John', email: 'john@example.com' });
  1210. const task = await Task.create({ title: 'Fix PR' });
  1211. await user.addTask(task);
  1212. const hasTask = await user.hasTask(task.id);
  1213. expect(hasTask).to.be.true;
  1214. });
  1215. it('should create with nested associated models', async function () {
  1216. const User = this.User;
  1217. const values = {
  1218. username: 'John',
  1219. email: 'john@example.com',
  1220. tasks: [{ title: 'Fix new PR' }],
  1221. };
  1222. const user0 = await User.create(values, { include: ['tasks'] });
  1223. // Make sure tasks are defined for created user
  1224. expect(user0).to.have.property('tasks');
  1225. expect(user0.tasks).to.be.an('array');
  1226. expect(user0.tasks).to.lengthOf(1);
  1227. expect(user0.tasks[0].title).to.equal(values.tasks[0].title, 'task title is correct');
  1228. const user = await User.findOne({ where: { email: values.email } });
  1229. const tasks = await user.getTasks();
  1230. // Make sure tasks relationship is successful
  1231. expect(tasks).to.be.an('array');
  1232. expect(tasks).to.lengthOf(1);
  1233. expect(tasks[0].title).to.equal(values.tasks[0].title, 'task title is correct');
  1234. });
  1235. it('should create nested associations with symmetric getters/setters on FK', async function () {
  1236. // Dummy getter/setter to test they are symmetric
  1237. function toCustomFormat(string) {
  1238. return string && `FORMAT-${string}`;
  1239. }
  1240. function fromCustomFormat(string) {
  1241. return string && string.slice(7);
  1242. }
  1243. const Parent = this.sequelize.define('Parent', {
  1244. id: {
  1245. type: DataTypes.STRING,
  1246. primaryKey: true,
  1247. get() {
  1248. return fromCustomFormat(this.getDataValue('id'));
  1249. },
  1250. set(value) {
  1251. this.setDataValue('id', toCustomFormat(value));
  1252. },
  1253. },
  1254. });
  1255. const Child = this.sequelize.define('Child', {
  1256. id: {
  1257. type: DataTypes.STRING,
  1258. primaryKey: true,
  1259. },
  1260. parent: {
  1261. type: DataTypes.STRING,
  1262. get() {
  1263. return fromCustomFormat(this.getDataValue('parent'));
  1264. },
  1265. set(value) {
  1266. this.setDataValue('parent', toCustomFormat(value));
  1267. },
  1268. },
  1269. });
  1270. Child.belongsTo(Parent, {
  1271. as: 'Parent',
  1272. foreignKey: 'parent',
  1273. targetKey: 'id',
  1274. inverse: {
  1275. type: 'hasMany',
  1276. as: 'children',
  1277. },
  1278. });
  1279. const values = {
  1280. id: 'sJn369d8Em',
  1281. children: [{ id: 'dgeQAQaW7A' }],
  1282. };
  1283. await this.sequelize.sync({ force: true });
  1284. const father = await Parent.create(values, { include: { model: Child, as: 'children' } });
  1285. // Make sure tasks are defined for created user
  1286. expect(father.id).to.equal('sJn369d8Em');
  1287. expect(father.get('id', { raw: true })).to.equal('FORMAT-sJn369d8Em');
  1288. expect(father).to.have.property('children');
  1289. expect(father.children).to.be.an('array');
  1290. expect(father.children).to.lengthOf(1);
  1291. expect(father.children[0].parent).to.equal('sJn369d8Em');
  1292. expect(father.children[0].get('parent', { raw: true })).to.equal('FORMAT-sJn369d8Em');
  1293. });
  1294. });
  1295. describe('sourceKey with where clause in include', () => {
  1296. beforeEach(function () {
  1297. this.User = this.sequelize.define(
  1298. 'User',
  1299. {
  1300. username: DataTypes.STRING,
  1301. email: { type: DataTypes.STRING, field: 'mail', allowNull: false },
  1302. },
  1303. { indexes: [{ fields: ['mail'], unique: true }] },
  1304. );
  1305. this.Task = this.sequelize.define('Task', {
  1306. title: DataTypes.STRING,
  1307. userEmail: DataTypes.STRING,
  1308. taskStatus: DataTypes.STRING,
  1309. });
  1310. this.User.hasMany(this.Task, {
  1311. foreignKey: 'userEmail',
  1312. sourceKey: 'email',
  1313. });
  1314. return this.sequelize.sync({ force: true });
  1315. });
  1316. it('should use the specified sourceKey instead of the primary key', async function () {
  1317. await this.User.create({ username: 'John', email: 'john@example.com' });
  1318. await this.Task.bulkCreate([
  1319. { title: 'Active Task', userEmail: 'john@example.com', taskStatus: 'Active' },
  1320. { title: 'Inactive Task', userEmail: 'john@example.com', taskStatus: 'Inactive' },
  1321. ]);
  1322. const user = await this.User.findOne({
  1323. include: [
  1324. {
  1325. model: this.Task,
  1326. where: { taskStatus: 'Active' },
  1327. },
  1328. ],
  1329. where: { username: 'John' },
  1330. });
  1331. expect(user).to.be.ok;
  1332. expect(user.tasks.length).to.equal(1);
  1333. expect(user.tasks[0].title).to.equal('Active Task');
  1334. });
  1335. });
  1336. describe('Eager loading', () => {
  1337. beforeEach(function () {
  1338. this.Individual = this.sequelize.define('individual', {
  1339. name: DataTypes.STRING,
  1340. });
  1341. this.Hat = this.sequelize.define('hat', {
  1342. name: DataTypes.STRING,
  1343. });
  1344. this.Individual.hasMany(this.Hat, {
  1345. as: {
  1346. singular: 'personwearinghat',
  1347. plural: 'personwearinghats',
  1348. },
  1349. });
  1350. });
  1351. it('should load with an alias', async function () {
  1352. await this.sequelize.sync({ force: true });
  1353. const [individual0, hat] = await Promise.all([
  1354. this.Individual.create({ name: 'Foo Bar' }),
  1355. this.Hat.create({ name: 'Baz' }),
  1356. ]);
  1357. await individual0.addPersonwearinghat(hat);
  1358. const individual = await this.Individual.findOne({
  1359. where: { name: 'Foo Bar' },
  1360. include: [{ model: this.Hat, as: 'personwearinghats' }],
  1361. });
  1362. expect(individual.name).to.equal('Foo Bar');
  1363. expect(individual.personwearinghats.length).to.equal(1);
  1364. expect(individual.personwearinghats[0].name).to.equal('Baz');
  1365. });
  1366. it('should load all', async function () {
  1367. await this.sequelize.sync({ force: true });
  1368. const [individual0, hat] = await Promise.all([
  1369. this.Individual.create({ name: 'Foo Bar' }),
  1370. this.Hat.create({ name: 'Baz' }),
  1371. ]);
  1372. await individual0.addPersonwearinghat(hat);
  1373. const individual = await this.Individual.findOne({
  1374. where: { name: 'Foo Bar' },
  1375. include: [{ all: true }],
  1376. });
  1377. expect(individual.name).to.equal('Foo Bar');
  1378. expect(individual.personwearinghats.length).to.equal(1);
  1379. expect(individual.personwearinghats[0].name).to.equal('Baz');
  1380. });
  1381. });
  1382. });