1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 |
|
9 |
|
10 | const assert = require('assert');
|
11 | const bdd = require('./helpers/bdd-if');
|
12 | const should = require('./init.js');
|
13 | const uid = require('./helpers/uid-generator');
|
14 | const jdb = require('../');
|
15 | const DataSource = jdb.DataSource;
|
16 | const createPromiseCallback = require('../lib/utils.js').createPromiseCallback;
|
17 |
|
18 | let db, tmp, Book, Chapter, Author, Reader, Article, Employee;
|
19 | let Category, Job;
|
20 | let Picture, PictureLink;
|
21 | let Person, Address;
|
22 | let Link;
|
23 |
|
24 | const getTransientDataSource = function(settings) {
|
25 | return new DataSource('transient', settings, db.modelBuilder);
|
26 | };
|
27 |
|
28 | const getMemoryDataSource = function(settings) {
|
29 | return new DataSource('memory', settings, db.modelBuilder);
|
30 | };
|
31 |
|
32 | describe('relations', function() {
|
33 | before(function() {
|
34 | db = getSchema();
|
35 | });
|
36 |
|
37 | describe('hasMany', function() {
|
38 | before(function(done) {
|
39 | Book = db.define('Book', {name: String, type: String});
|
40 | Chapter = db.define('Chapter', {name: {type: String, index: true},
|
41 | bookType: String});
|
42 | Author = db.define('Author', {name: String});
|
43 | Reader = db.define('Reader', {name: String});
|
44 |
|
45 | db.automigrate(['Book', 'Chapter', 'Author', 'Reader'], done);
|
46 | });
|
47 |
|
48 | it('can be declared in different ways', function(done) {
|
49 | Book.hasMany(Chapter);
|
50 | Book.hasMany(Reader, {as: 'users'});
|
51 | Book.hasMany(Author, {foreignKey: 'projectId'});
|
52 | const b = new Book;
|
53 | b.chapters.should.be.an.instanceOf(Function);
|
54 | b.users.should.be.an.instanceOf(Function);
|
55 | b.authors.should.be.an.instanceOf(Function);
|
56 | Object.keys((new Chapter).toObject()).should.containEql('bookId');
|
57 | Object.keys((new Author).toObject()).should.containEql('projectId');
|
58 |
|
59 | db.automigrate(['Book', 'Chapter', 'Author', 'Reader'], done);
|
60 | });
|
61 |
|
62 | it('can be declared in short form', function(done) {
|
63 | Author = db.define('Author', {name: String});
|
64 | Reader = db.define('Reader', {name: String});
|
65 | Author.hasMany('readers');
|
66 | (new Author).readers.should.be.an.instanceOf(Function);
|
67 | Object.keys((new Reader).toObject()).should.containEql('authorId');
|
68 |
|
69 | db.autoupdate(['Author', 'Reader'], done);
|
70 | });
|
71 |
|
72 | describe('with scope', function() {
|
73 | before(function(done) {
|
74 | Book.hasMany(Chapter);
|
75 | done();
|
76 | });
|
77 |
|
78 | it('should build record on scope', function(done) {
|
79 | Book.create(function(err, book) {
|
80 | const chaps = book.chapters;
|
81 | const c = chaps.build();
|
82 | c.bookId.should.eql(book.id);
|
83 | c.save(done);
|
84 | });
|
85 | });
|
86 |
|
87 | it('should create record on scope', function(done) {
|
88 | Book.create(function(err, book) {
|
89 | book.chapters.create(function(err, c) {
|
90 | if (err) return done(err);
|
91 | should.exist(c);
|
92 | c.bookId.should.eql(book.id);
|
93 | done(err);
|
94 | });
|
95 | });
|
96 | });
|
97 |
|
98 | it('should not update FK', function(done) {
|
99 | Book.create(function(err, book) {
|
100 | book.chapters.create({name: 'chapter 1'}, function(err, c) {
|
101 | if (err) return done(err);
|
102 | should.exist(c);
|
103 | c.bookId.should.eql(book.id);
|
104 | c.name.should.eql('chapter 1');
|
105 | book.chapters.updateById(c.id, {name: 'chapter 0', bookId: 10}, function(err, cc) {
|
106 | should.exist(err);
|
107 | err.message.should.startWith('Cannot override foreign key');
|
108 | done();
|
109 | });
|
110 | });
|
111 | });
|
112 | });
|
113 |
|
114 | it('should create record on scope with promises', function(done) {
|
115 | Book.create()
|
116 | .then(function(book) {
|
117 | return book.chapters.create()
|
118 | .then(function(c) {
|
119 | should.exist(c);
|
120 | c.bookId.should.eql(book.id);
|
121 | done();
|
122 | });
|
123 | }).catch(done);
|
124 | });
|
125 |
|
126 | it('should create a batch of records on scope', function(done) {
|
127 | const chapters = [
|
128 | {name: 'a'},
|
129 | {name: 'z'},
|
130 | {name: 'c'},
|
131 | ];
|
132 | Book.create(function(err, book) {
|
133 | book.chapters.create(chapters, function(err, chs) {
|
134 | if (err) return done(err);
|
135 | should.exist(chs);
|
136 | chs.should.have.lengthOf(chapters.length);
|
137 | chs.forEach(function(c) {
|
138 | c.bookId.should.eql(book.id);
|
139 | });
|
140 | done();
|
141 | });
|
142 | });
|
143 | });
|
144 |
|
145 | it('should create a batch of records on scope with promises', function(done) {
|
146 | const chapters = [
|
147 | {name: 'a'},
|
148 | {name: 'z'},
|
149 | {name: 'c'},
|
150 | ];
|
151 | Book.create(function(err, book) {
|
152 | book.chapters.create(chapters)
|
153 | .then(function(chs) {
|
154 | should.exist(chs);
|
155 | chs.should.have.lengthOf(chapters.length);
|
156 | chs.forEach(function(c) {
|
157 | c.bookId.should.eql(book.id);
|
158 | });
|
159 | done();
|
160 | }).catch(done);
|
161 | });
|
162 | });
|
163 |
|
164 | it('should fetch all scoped instances', function(done) {
|
165 | Book.create(function(err, book) {
|
166 | book.chapters.create({name: 'a'}, function() {
|
167 | book.chapters.create({name: 'z'}, function() {
|
168 | book.chapters.create({name: 'c'}, function() {
|
169 | verify(book);
|
170 | });
|
171 | });
|
172 | });
|
173 | });
|
174 | function verify(book) {
|
175 | book.chapters(function(err, ch) {
|
176 | if (err) return done(err);
|
177 | should.exist(ch);
|
178 | ch.should.have.lengthOf(3);
|
179 |
|
180 | const chapters = book.chapters();
|
181 | chapters.should.eql(ch);
|
182 |
|
183 | book.chapters(function(e, c) {
|
184 | should.not.exist(e);
|
185 | should.exist(c);
|
186 | ch.should.have.lengthOf(3);
|
187 | const acz = ['a', 'c', 'z'];
|
188 | acz.should.containEql(c[0].name);
|
189 | acz.should.containEql(c[1].name);
|
190 | acz.should.containEql(c[2].name);
|
191 | done();
|
192 | });
|
193 | });
|
194 | }
|
195 | });
|
196 |
|
197 | it('should fetch all scoped instances with promises', function(done) {
|
198 | Book.create()
|
199 | .then(function(book) {
|
200 | return book.chapters.create({name: 'a'})
|
201 | .then(function() {
|
202 | return book.chapters.create({name: 'z'});
|
203 | })
|
204 | .then(function() {
|
205 | return book.chapters.create({name: 'c'});
|
206 | })
|
207 | .then(function() {
|
208 | return verify(book);
|
209 | });
|
210 | }).catch(done);
|
211 |
|
212 | function verify(book) {
|
213 | return book.chapters.find()
|
214 | .then(function(ch) {
|
215 | should.exist(ch);
|
216 | ch.should.have.lengthOf(3);
|
217 | const chapters = book.chapters();
|
218 | chapters.should.eql(ch);
|
219 | return book.chapters.find()
|
220 | .then(function(c) {
|
221 | should.exist(c);
|
222 | ch.should.have.lengthOf(3);
|
223 | const acz = ['a', 'c', 'z'];
|
224 | acz.should.containEql(c[0].name);
|
225 | acz.should.containEql(c[1].name);
|
226 | acz.should.containEql(c[2].name);
|
227 | done();
|
228 | });
|
229 | });
|
230 | }
|
231 | });
|
232 |
|
233 | it('should fetch all scoped instances with find() with callback and condition', function(done) {
|
234 | Book.create(function(err, book) {
|
235 | book.chapters.create({name: 'a'}, function() {
|
236 | book.chapters.create({name: 'z'}, function() {
|
237 | book.chapters.create({name: 'c'}, function() {
|
238 | verify(book);
|
239 | });
|
240 | });
|
241 | });
|
242 | });
|
243 | function verify(book) {
|
244 | book.chapters(function(err, ch) {
|
245 | if (err) return done(err);
|
246 | should.exist(ch);
|
247 | ch.should.have.lengthOf(3);
|
248 |
|
249 | const chapters = book.chapters();
|
250 | chapters.should.eql(ch);
|
251 | book.chapters.find(function(e, c) {
|
252 | should.not.exist(e);
|
253 | should.exist(c);
|
254 | ch.should.have.lengthOf(3);
|
255 | const acz = ['a', 'c', 'z'];
|
256 | acz.should.containEql(c[0].name);
|
257 | acz.should.containEql(c[1].name);
|
258 | acz.should.containEql(c[2].name);
|
259 | done();
|
260 | });
|
261 | });
|
262 | }
|
263 | });
|
264 |
|
265 | it('should fetch all scoped instances with find() with callback and no condition', function(done) {
|
266 | Book.create(function(err, book) {
|
267 | book.chapters.create({name: 'a'}, function() {
|
268 | book.chapters.create({name: 'z'}, function() {
|
269 | book.chapters.create({name: 'c'}, function() {
|
270 | verify(book);
|
271 | });
|
272 | });
|
273 | });
|
274 | });
|
275 | function verify(book) {
|
276 | book.chapters(function(err, ch) {
|
277 | if (err) return done(err);
|
278 | should.exist(ch);
|
279 | ch.should.have.lengthOf(3);
|
280 |
|
281 | const chapters = book.chapters();
|
282 | chapters.should.eql(ch);
|
283 |
|
284 | book.chapters.find(function(e, c) {
|
285 | should.not.exist(e);
|
286 | should.exist(c);
|
287 | should.exist(c.length);
|
288 | c.should.have.lengthOf(3);
|
289 | const acz = ['a', 'c', 'z'];
|
290 | acz.should.containEql(c[0].name);
|
291 | acz.should.containEql(c[1].name);
|
292 | acz.should.containEql(c[2].name);
|
293 | done();
|
294 | });
|
295 | });
|
296 | }
|
297 | });
|
298 |
|
299 | it('should find scoped record', function(done) {
|
300 | let id;
|
301 | Book.create(function(err, book) {
|
302 | book.chapters.create({name: 'a'}, function(err, ch) {
|
303 | id = ch.id;
|
304 | book.chapters.create({name: 'z'}, function() {
|
305 | book.chapters.create({name: 'c'}, function() {
|
306 | verify(book);
|
307 | });
|
308 | });
|
309 | });
|
310 | });
|
311 |
|
312 | function verify(book) {
|
313 | book.chapters.findById(id, function(err, ch) {
|
314 | if (err) return done(err);
|
315 | should.exist(ch);
|
316 | ch.id.should.eql(id);
|
317 | done();
|
318 | });
|
319 | }
|
320 | });
|
321 |
|
322 | it('should find scoped record with promises', function(done) {
|
323 | let id;
|
324 | Book.create()
|
325 | .then(function(book) {
|
326 | return book.chapters.create({name: 'a'})
|
327 | .then(function(ch) {
|
328 | id = ch.id;
|
329 | return book.chapters.create({name: 'z'});
|
330 | })
|
331 | .then(function() {
|
332 | return book.chapters.create({name: 'c'});
|
333 | })
|
334 | .then(function() {
|
335 | return verify(book);
|
336 | });
|
337 | }).catch(done);
|
338 |
|
339 | function verify(book) {
|
340 | return book.chapters.findById(id)
|
341 | .then(function(ch) {
|
342 | should.exist(ch);
|
343 | ch.id.should.eql(id);
|
344 | done();
|
345 | });
|
346 | }
|
347 | });
|
348 |
|
349 | it('should count scoped records - all and filtered', function(done) {
|
350 | Book.create(function(err, book) {
|
351 | book.chapters.create({name: 'a'}, function(err, ch) {
|
352 | book.chapters.create({name: 'b'}, function() {
|
353 | book.chapters.create({name: 'c'}, function() {
|
354 | verify(book);
|
355 | });
|
356 | });
|
357 | });
|
358 | });
|
359 |
|
360 | function verify(book) {
|
361 | book.chapters.count(function(err, count) {
|
362 | if (err) return done(err);
|
363 | count.should.equal(3);
|
364 | book.chapters.count({name: 'b'}, function(err, count) {
|
365 | if (err) return done(err);
|
366 | count.should.equal(1);
|
367 | done();
|
368 | });
|
369 | });
|
370 | }
|
371 | });
|
372 |
|
373 | it('should count scoped records - all and filtered with promises', function(done) {
|
374 | Book.create()
|
375 | .then(function(book) {
|
376 | book.chapters.create({name: 'a'})
|
377 | .then(function() {
|
378 | return book.chapters.create({name: 'b'});
|
379 | })
|
380 | .then(function() {
|
381 | return book.chapters.create({name: 'c'});
|
382 | })
|
383 | .then(function() {
|
384 | return verify(book);
|
385 | });
|
386 | }).catch(done);
|
387 |
|
388 | function verify(book) {
|
389 | return book.chapters.count()
|
390 | .then(function(count) {
|
391 | count.should.equal(3);
|
392 | return book.chapters.count({name: 'b'});
|
393 | })
|
394 | .then(function(count) {
|
395 | count.should.equal(1);
|
396 | done();
|
397 | });
|
398 | }
|
399 | });
|
400 |
|
401 | it('should set targetClass on scope property', function() {
|
402 | should.equal(Book.prototype.chapters._targetClass, 'Chapter');
|
403 | });
|
404 |
|
405 | it('should update scoped record', function(done) {
|
406 | let id;
|
407 | Book.create(function(err, book) {
|
408 | book.chapters.create({name: 'a'}, function(err, ch) {
|
409 | id = ch.id;
|
410 | book.chapters.updateById(id, {name: 'aa'}, function(err, ch) {
|
411 | verify(book);
|
412 | });
|
413 | });
|
414 | });
|
415 |
|
416 | function verify(book) {
|
417 | book.chapters.findById(id, function(err, ch) {
|
418 | if (err) return done(err);
|
419 | should.exist(ch);
|
420 | ch.id.should.eql(id);
|
421 | ch.name.should.equal('aa');
|
422 | done();
|
423 | });
|
424 | }
|
425 | });
|
426 |
|
427 | it('should update scoped record with promises', function(done) {
|
428 | let id;
|
429 | Book.create()
|
430 | .then(function(book) {
|
431 | return book.chapters.create({name: 'a'})
|
432 | .then(function(ch) {
|
433 | id = ch.id;
|
434 | return book.chapters.updateById(id, {name: 'aa'});
|
435 | })
|
436 | .then(function(ch) {
|
437 | return verify(book);
|
438 | });
|
439 | })
|
440 | .catch(done);
|
441 |
|
442 | function verify(book) {
|
443 | return book.chapters.findById(id)
|
444 | .then(function(ch) {
|
445 | should.exist(ch);
|
446 | ch.id.should.eql(id);
|
447 | ch.name.should.equal('aa');
|
448 | done();
|
449 | });
|
450 | }
|
451 | });
|
452 |
|
453 | it('should destroy scoped record', function(done) {
|
454 | let id;
|
455 | Book.create(function(err, book) {
|
456 | book.chapters.create({name: 'a'}, function(err, ch) {
|
457 | id = ch.id;
|
458 | book.chapters.destroy(id, function(err, ch) {
|
459 | verify(book);
|
460 | });
|
461 | });
|
462 | });
|
463 |
|
464 | function verify(book) {
|
465 | book.chapters.findById(id, function(err, ch) {
|
466 | should.exist(err);
|
467 | done();
|
468 | });
|
469 | }
|
470 | });
|
471 |
|
472 | it('should destroy scoped record with promises', function(done) {
|
473 | let id;
|
474 | Book.create()
|
475 | .then(function(book) {
|
476 | return book.chapters.create({name: 'a'})
|
477 | .then(function(ch) {
|
478 | id = ch.id;
|
479 | return book.chapters.destroy(id);
|
480 | })
|
481 | .then(function(ch) {
|
482 | return verify(book);
|
483 | });
|
484 | })
|
485 | .catch(done);
|
486 |
|
487 | function verify(book) {
|
488 | return book.chapters.findById(id)
|
489 | .catch(function(err) {
|
490 | should.exist(err);
|
491 | done();
|
492 | });
|
493 | }
|
494 | });
|
495 |
|
496 | it('should check existence of a scoped record', function(done) {
|
497 | let id;
|
498 | Book.create(function(err, book) {
|
499 | book.chapters.create({name: 'a'}, function(err, ch) {
|
500 | id = ch.id;
|
501 | book.chapters.create({name: 'z'}, function() {
|
502 | book.chapters.create({name: 'c'}, function() {
|
503 | verify(book);
|
504 | });
|
505 | });
|
506 | });
|
507 | });
|
508 |
|
509 | function verify(book) {
|
510 | book.chapters.exists(id, function(err, flag) {
|
511 | if (err) return done(err);
|
512 | flag.should.be.eql(true);
|
513 | done();
|
514 | });
|
515 | }
|
516 | });
|
517 |
|
518 | it('should check existence of a scoped record with promises', function(done) {
|
519 | let id;
|
520 | Book.create()
|
521 | .then(function(book) {
|
522 | return book.chapters.create({name: 'a'})
|
523 | .then(function(ch) {
|
524 | id = ch.id;
|
525 | return book.chapters.create({name: 'z'});
|
526 | })
|
527 | .then(function() {
|
528 | return book.chapters.create({name: 'c'});
|
529 | })
|
530 | .then(function() {
|
531 | return verify(book);
|
532 | });
|
533 | }).catch(done);
|
534 |
|
535 | function verify(book) {
|
536 | return book.chapters.exists(id)
|
537 | .then(function(flag) {
|
538 | flag.should.be.eql(true);
|
539 | done();
|
540 | });
|
541 | }
|
542 | });
|
543 |
|
544 | it('should check ignore related data on creation - array', function(done) {
|
545 | Book.create({chapters: []}, function(err, book) {
|
546 | if (err) return done(err);
|
547 | book.chapters.should.be.a.function;
|
548 | const obj = book.toObject();
|
549 | should.not.exist(obj.chapters);
|
550 | done();
|
551 | });
|
552 | });
|
553 |
|
554 | it('should check ignore related data on creation with promises - array', function(done) {
|
555 | Book.create({chapters: []})
|
556 | .then(function(book) {
|
557 | book.chapters.should.be.a.function;
|
558 | const obj = book.toObject();
|
559 | should.not.exist(obj.chapters);
|
560 | done();
|
561 | }).catch(done);
|
562 | });
|
563 |
|
564 | it('should check ignore related data on creation - object', function(done) {
|
565 | Book.create({chapters: {}}, function(err, book) {
|
566 | if (err) return done(err);
|
567 | book.chapters.should.be.a.function;
|
568 | const obj = book.toObject();
|
569 | should.not.exist(obj.chapters);
|
570 | done();
|
571 | });
|
572 | });
|
573 |
|
574 | it('should check ignore related data on creation with promises - object', function(done) {
|
575 | Book.create({chapters: {}})
|
576 | .then(function(book) {
|
577 | book.chapters.should.be.a.function;
|
578 | const obj = book.toObject();
|
579 | should.not.exist(obj.chapters);
|
580 | done();
|
581 | }).catch(done);
|
582 | });
|
583 | });
|
584 | });
|
585 |
|
586 | describe('hasMany through', function() {
|
587 | let Physician, Patient, Appointment, Address;
|
588 |
|
589 | before(function(done) {
|
590 | Physician = db.define('Physician', {name: String});
|
591 | Patient = db.define('Patient', {name: String, age: Number, realm: String,
|
592 | sequence: {type: Number, index: true}});
|
593 | Appointment = db.define('Appointment', {date: {type: Date,
|
594 | default: function() {
|
595 | return new Date();
|
596 | }}});
|
597 | Address = db.define('Address', {name: String});
|
598 |
|
599 | Physician.hasMany(Patient, {through: Appointment});
|
600 | Patient.hasMany(Physician, {through: Appointment});
|
601 | Patient.belongsTo(Address);
|
602 | Appointment.belongsTo(Patient);
|
603 | Appointment.belongsTo(Physician);
|
604 |
|
605 | db.automigrate(['Physician', 'Patient', 'Appointment', 'Address'], done);
|
606 | });
|
607 |
|
608 | it('should build record on scope', function(done) {
|
609 | Physician.create(function(err, physician) {
|
610 | const patient = physician.patients.build();
|
611 | patient.physicianId.should.eql(physician.id);
|
612 | patient.save(done);
|
613 | });
|
614 | });
|
615 |
|
616 | it('should create record on scope', function(done) {
|
617 | Physician.create(function(err, physician) {
|
618 | physician.patients.create(function(err, patient) {
|
619 | if (err) return done(err);
|
620 | should.exist(patient);
|
621 | Appointment.find({where: {physicianId: physician.id, patientId: patient.id}},
|
622 | function(err, apps) {
|
623 | if (err) return done(err);
|
624 | apps.should.have.lengthOf(1);
|
625 | done();
|
626 | });
|
627 | });
|
628 | });
|
629 | });
|
630 |
|
631 | it('should create record on scope with promises', function(done) {
|
632 | Physician.create()
|
633 | .then(function(physician) {
|
634 | return physician.patients.create()
|
635 | .then(function(patient) {
|
636 | should.exist(patient);
|
637 | return Appointment.find({where: {physicianId: physician.id, patientId: patient.id}})
|
638 | .then(function(apps) {
|
639 | apps.should.have.lengthOf(1);
|
640 | done();
|
641 | });
|
642 | });
|
643 | }).catch(done);
|
644 | });
|
645 |
|
646 | it('should create multiple records on scope', function(done) {
|
647 | const async = require('async');
|
648 | Physician.create(function(err, physician) {
|
649 | physician.patients.create([{}, {}], function(err, patients) {
|
650 | if (err) return done(err);
|
651 | should.exist(patients);
|
652 | patients.should.have.lengthOf(2);
|
653 | function verifyPatient(patient, next) {
|
654 | Appointment.find({where: {
|
655 | physicianId: physician.id,
|
656 | patientId: patient.id,
|
657 | }},
|
658 | function(err, apps) {
|
659 | if (err) return done(err);
|
660 | apps.should.have.lengthOf(1);
|
661 | next();
|
662 | });
|
663 | }
|
664 | async.forEach(patients, verifyPatient, done);
|
665 | });
|
666 | });
|
667 | });
|
668 |
|
669 | it('should create multiple records on scope with promises', function(done) {
|
670 | const async = require('async');
|
671 | Physician.create()
|
672 | .then(function(physician) {
|
673 | return physician.patients.create([{}, {}])
|
674 | .then(function(patients) {
|
675 | should.exist(patients);
|
676 | patients.should.have.lengthOf(2);
|
677 | function verifyPatient(patient, next) {
|
678 | Appointment.find({where: {
|
679 | physicianId: physician.id,
|
680 | patientId: patient.id,
|
681 | }})
|
682 | .then(function(apps) {
|
683 | apps.should.have.lengthOf(1);
|
684 | next();
|
685 | });
|
686 | }
|
687 | async.forEach(patients, verifyPatient, done);
|
688 | });
|
689 | }).catch(done);
|
690 | });
|
691 |
|
692 | it('should fetch all scoped instances', function(done) {
|
693 | Physician.create(function(err, physician) {
|
694 | physician.patients.create({name: 'a'}, function() {
|
695 | physician.patients.create({name: 'z'}, function() {
|
696 | physician.patients.create({name: 'c'}, function() {
|
697 | verify(physician);
|
698 | });
|
699 | });
|
700 | });
|
701 | });
|
702 | function verify(physician) {
|
703 | physician.patients(function(err, ch) {
|
704 | const patients = physician.patients();
|
705 | patients.should.eql(ch);
|
706 |
|
707 | if (err) return done(err);
|
708 | should.exist(ch);
|
709 | ch.should.have.lengthOf(3);
|
710 | done();
|
711 | });
|
712 | }
|
713 | });
|
714 |
|
715 | it('should fetch all scoped instances with promises', function(done) {
|
716 | Physician.create()
|
717 | .then(function(physician) {
|
718 | return physician.patients.create({name: 'a'})
|
719 | .then(function() {
|
720 | return physician.patients.create({name: 'z'});
|
721 | })
|
722 | .then(function() {
|
723 | return physician.patients.create({name: 'c'});
|
724 | })
|
725 | .then(function() {
|
726 | return verify(physician);
|
727 | });
|
728 | }).catch(done);
|
729 | function verify(physician) {
|
730 | return physician.patients.find()
|
731 | .then(function(ch) {
|
732 | const patients = physician.patients();
|
733 | should.equal(patients, ch);
|
734 |
|
735 | should.exist(ch);
|
736 | ch.should.have.lengthOf(3);
|
737 | done();
|
738 | });
|
739 | }
|
740 | });
|
741 |
|
742 | describe('fetch scoped instances with paging filters', function() {
|
743 | let samplePatientId;
|
744 | let physician;
|
745 |
|
746 | beforeEach(createSampleData);
|
747 |
|
748 | context('with filter skip', function() {
|
749 | bdd.itIf(connectorCapabilities.supportPagination !== false,
|
750 | 'skips the first patient', function(done) {
|
751 | physician.patients({skip: 1, order: 'sequence'}, function(err, ch) {
|
752 | if (err) return done(err);
|
753 | should.exist(ch);
|
754 | ch.should.have.lengthOf(2);
|
755 | ch[0].name.should.eql('z');
|
756 | ch[1].name.should.eql('c');
|
757 | done();
|
758 | });
|
759 | });
|
760 | });
|
761 | context('with filter order', function() {
|
762 | it('orders the result by patient name', function(done) {
|
763 | const filter = connectorCapabilities.adhocSort !== false ? {order: 'name DESC'} : {};
|
764 | physician.patients(filter, function(err, ch) {
|
765 | if (err) return done(err);
|
766 | should.exist(ch);
|
767 | ch.should.have.lengthOf(3);
|
768 | if (connectorCapabilities.adhocSort !== false) {
|
769 | ch[0].name.should.eql('z');
|
770 | ch[1].name.should.eql('c');
|
771 | ch[2].name.should.eql('a');
|
772 | } else {
|
773 | const acz = ['a', 'c', 'z'];
|
774 | ch[0].name.should.be.oneOf(acz);
|
775 | ch[1].name.should.be.oneOf(acz);
|
776 | ch[2].name.should.be.oneOf(acz);
|
777 | }
|
778 | done();
|
779 | });
|
780 | });
|
781 | });
|
782 | context('with filter limit', function() {
|
783 | it('limits to 1 result', function(done) {
|
784 | physician.patients({limit: 1, order: 'sequence'}, function(err, ch) {
|
785 | if (err) return done(err);
|
786 | should.exist(ch);
|
787 | ch.should.have.lengthOf(1);
|
788 | if (connectorCapabilities.adhocSort !== false) {
|
789 | ch[0].name.should.eql('a');
|
790 | } else {
|
791 | ch[0].name.should.be.oneOf(['a', 'c', 'z']);
|
792 | }
|
793 | done();
|
794 | });
|
795 | });
|
796 | });
|
797 | context('with filter fields', function() {
|
798 | it('includes field \'name\' but not \'age\'', function(done) {
|
799 | const fieldsFilter = {
|
800 | fields: {name: true, age: false},
|
801 | order: 'sequence',
|
802 | };
|
803 | physician.patients(fieldsFilter, function(err, ch) {
|
804 | if (err) return done(err);
|
805 | should.exist(ch);
|
806 | should.exist(ch[0].name);
|
807 | if (connectorCapabilities.adhocSort !== false) {
|
808 | ch[0].name.should.eql('a');
|
809 | } else {
|
810 | ch[0].name.should.be.oneOf(['a', 'c', 'z']);
|
811 | }
|
812 | should.not.exist(ch[0].age);
|
813 | done();
|
814 | });
|
815 | });
|
816 | });
|
817 | context('with filter include', function() {
|
818 | it('returns physicians included in patient', function(done) {
|
819 | const includeFilter = {include: 'physicians'};
|
820 | physician.patients(includeFilter, function(err, ch) {
|
821 | if (err) return done(err);
|
822 | ch.should.have.lengthOf(3);
|
823 | should.exist(ch[0].physicians);
|
824 | done();
|
825 | });
|
826 | });
|
827 | });
|
828 | context('with filter where', function() {
|
829 | it('returns patient where id equal to samplePatientId', function(done) {
|
830 | const whereFilter = {where: {id: samplePatientId}};
|
831 | physician.patients(whereFilter, function(err, ch) {
|
832 | if (err) return done(err);
|
833 | should.exist(ch);
|
834 | ch.should.have.lengthOf(1);
|
835 | ch[0].id.should.eql(samplePatientId);
|
836 | done();
|
837 | });
|
838 | });
|
839 | it('returns patient where name equal to samplePatient name', function(done) {
|
840 | const whereFilter = {where: {name: 'a'}};
|
841 | physician.patients(whereFilter, function(err, ch) {
|
842 | if (err) return done(err);
|
843 | should.exist(ch);
|
844 | ch.should.have.lengthOf(1);
|
845 | ch[0].name.should.eql('a');
|
846 | done();
|
847 | });
|
848 | });
|
849 | it('returns patients where id in an array', function(done) {
|
850 | const idArr = [];
|
851 | let whereFilter;
|
852 | physician.patients.create({name: 'b'}, function(err, p) {
|
853 | idArr.push(samplePatientId, p.id);
|
854 | whereFilter = {where: {id: {inq: idArr}}};
|
855 | physician.patients(whereFilter, function(err, ch) {
|
856 | if (err) return done(err);
|
857 | should.exist(ch);
|
858 | ch.should.have.lengthOf(2);
|
859 | if (typeof idArr[0] === 'object') {
|
860 |
|
861 | idArr[0] = idArr[0].toString();
|
862 | idArr[1] = idArr[1].toString();
|
863 | idArr.indexOf(ch[0].id.toString()).should.not.equal(-1);
|
864 | idArr.indexOf(ch[1].id.toString()).should.not.equal(-1);
|
865 | } else {
|
866 | idArr.indexOf(ch[0].id).should.not.equal(-1);
|
867 | idArr.indexOf(ch[1].id).should.not.equal(-1);
|
868 | }
|
869 | done();
|
870 | });
|
871 | });
|
872 | });
|
873 | it('returns empty result when patientId does not belongs to physician', function(done) {
|
874 | Patient.create({name: 'x'}, function(err, p) {
|
875 | if (err) return done(err);
|
876 | should.exist(p);
|
877 |
|
878 | const wrongWhereFilter = {where: {id: p.id}};
|
879 | physician.patients(wrongWhereFilter, function(err, ch) {
|
880 | if (err) return done(err);
|
881 | should.exist(ch);
|
882 | ch.should.have.lengthOf(0);
|
883 | done();
|
884 | });
|
885 | });
|
886 | });
|
887 | });
|
888 | context('findById with filter include', function() {
|
889 | it('returns patient where id equal to \'samplePatientId\'' +
|
890 | 'with included physicians', function(done) {
|
891 | const includeFilter = {include: 'physicians'};
|
892 | physician.patients.findById(samplePatientId,
|
893 | includeFilter, function(err, ch) {
|
894 | if (err) return done(err);
|
895 | should.exist(ch);
|
896 | ch.id.should.eql(samplePatientId);
|
897 | should.exist(ch.physicians);
|
898 | done();
|
899 | });
|
900 | });
|
901 | });
|
902 | context('findById with filter fields', function() {
|
903 | it('returns patient where id equal to \'samplePatientId\'' +
|
904 | 'with field \'name\' but not \'age\'', function(done) {
|
905 | const fieldsFilter = {fields: {name: true, age: false}};
|
906 | physician.patients.findById(samplePatientId,
|
907 | fieldsFilter, function(err, ch) {
|
908 | if (err) return done(err);
|
909 | should.exist(ch);
|
910 | should.exist(ch.name);
|
911 | ch.name.should.eql('a');
|
912 | should.not.exist(ch.age);
|
913 | done();
|
914 | });
|
915 | });
|
916 | });
|
917 | context('findById with include filter that contains string fields', function() {
|
918 | it('should accept string and convert it to array', function(done) {
|
919 | const includeFilter = {include: {relation: 'patients', scope: {fields: 'name'}}};
|
920 | const physicianId = physician.id;
|
921 | Physician.findById(physicianId, includeFilter, function(err, result) {
|
922 | if (err) return done(err);
|
923 | should.exist(result);
|
924 | result.id.should.eql(physicianId);
|
925 | should.exist(result.patients);
|
926 | result.patients().should.be.an.instanceOf(Array);
|
927 | should.exist(result.patients()[0]);
|
928 | should.exist(result.patients()[0].name);
|
929 | should.not.exist(result.patients()[0].age);
|
930 | done();
|
931 | });
|
932 | });
|
933 | });
|
934 |
|
935 | function createSampleData(done) {
|
936 | Physician.create(function(err, result) {
|
937 | result.patients.create({name: 'a', age: '10', sequence: 1},
|
938 | function(err, p) {
|
939 | samplePatientId = p.id;
|
940 | result.patients.create({name: 'z', age: '20', sequence: 2},
|
941 | function() {
|
942 | result.patients.create({name: 'c', sequence: 3}, function() {
|
943 | physician = result;
|
944 | done();
|
945 | });
|
946 | });
|
947 | });
|
948 | });
|
949 | }
|
950 | });
|
951 |
|
952 | describe('find over related model with options', function() {
|
953 | after(function() {
|
954 | Physician.clearObservers('access');
|
955 | Patient.clearObservers('access');
|
956 | });
|
957 | before(function() {
|
958 | Physician.observe('access', beforeAccessFn);
|
959 | Patient.observe('access', beforeAccessFn);
|
960 |
|
961 | function beforeAccessFn(ctx, next) {
|
962 | ctx.query.where.realm = ctx.options.realm;
|
963 | next();
|
964 | }
|
965 | });
|
966 | it('should find be filtered from option', function(done) {
|
967 | let id;
|
968 | Physician.create(function(err, physician) {
|
969 | if (err) return done(err);
|
970 | physician.patients.create({name: 'a', realm: 'test'}, function(err, ch) {
|
971 | if (err) return done(err);
|
972 | id = ch.id;
|
973 | physician.patients.create({name: 'z', realm: 'test'}, function(err) {
|
974 | if (err) return done(err);
|
975 | physician.patients.create({name: 'c', realm: 'anotherRealm'}, function(err) {
|
976 | if (err) return done(err);
|
977 | verify(physician);
|
978 | });
|
979 | });
|
980 | });
|
981 | });
|
982 |
|
983 | function verify(physician) {
|
984 | physician.patients({order: 'name ASC'}, {realm: 'test'}, function(err, records) {
|
985 | if (err) return done(err);
|
986 | should.exist(records);
|
987 | records.length.should.eql(2);
|
988 | const expected = ['a:test', 'z:test'];
|
989 | const actual = records.map(function(r) { return r.name + ':' + r.realm; });
|
990 | actual.sort().should.eql(expected.sort());
|
991 | done();
|
992 | });
|
993 | }
|
994 | });
|
995 | });
|
996 |
|
997 | it('should find scoped record', function(done) {
|
998 | let id;
|
999 | Physician.create(function(err, physician) {
|
1000 | physician.patients.create({name: 'a'}, function(err, ch) {
|
1001 | id = ch.id;
|
1002 | physician.patients.create({name: 'z'}, function() {
|
1003 | physician.patients.create({name: 'c'}, function() {
|
1004 | verify(physician);
|
1005 | });
|
1006 | });
|
1007 | });
|
1008 | });
|
1009 |
|
1010 | function verify(physician) {
|
1011 | physician.patients.findById(id, function(err, ch) {
|
1012 | if (err) return done(err);
|
1013 | should.exist(ch);
|
1014 | ch.id.should.eql(id);
|
1015 | done();
|
1016 | });
|
1017 | }
|
1018 | });
|
1019 |
|
1020 | it('should find scoped record with promises', function(done) {
|
1021 | let id;
|
1022 | Physician.create()
|
1023 | .then(function(physician) {
|
1024 | return physician.patients.create({name: 'a'})
|
1025 | .then(function(ch) {
|
1026 | id = ch.id;
|
1027 | return physician.patients.create({name: 'z'});
|
1028 | })
|
1029 | .then(function() {
|
1030 | return physician.patients.create({name: 'c'});
|
1031 | })
|
1032 | .then(function() {
|
1033 | return verify(physician);
|
1034 | });
|
1035 | }).catch(done);
|
1036 |
|
1037 | function verify(physician) {
|
1038 | return physician.patients.findById(id, function(err, ch) {
|
1039 | if (err) return done(err);
|
1040 | should.exist(ch);
|
1041 | ch.id.should.eql(id);
|
1042 | done();
|
1043 | });
|
1044 | }
|
1045 | });
|
1046 |
|
1047 | it('should allow to use include syntax on related data', function(done) {
|
1048 | Physician.create(function(err, physician) {
|
1049 | physician.patients.create({name: 'a'}, function(err, patient) {
|
1050 | Address.create({name: 'z'}, function(err, address) {
|
1051 | if (err) return done(err);
|
1052 | patient.address(address);
|
1053 | patient.save(function() {
|
1054 | verify(physician, address.id);
|
1055 | });
|
1056 | });
|
1057 | });
|
1058 | });
|
1059 | function verify(physician, addressId) {
|
1060 | physician.patients({include: 'address'}, function(err, ch) {
|
1061 | if (err) return done(err);
|
1062 | should.exist(ch);
|
1063 | ch.should.have.lengthOf(1);
|
1064 | ch[0].addressId.should.eql(addressId);
|
1065 | const address = ch[0].address();
|
1066 | should.exist(address);
|
1067 | address.should.be.an.instanceof(Address);
|
1068 | address.name.should.equal('z');
|
1069 | done();
|
1070 | });
|
1071 | }
|
1072 | });
|
1073 |
|
1074 | it('should allow to use include syntax on related data with promises', function(done) {
|
1075 | Physician.create()
|
1076 | .then(function(physician) {
|
1077 | return physician.patients.create({name: 'a'})
|
1078 | .then(function(patient) {
|
1079 | return Address.create({name: 'z'})
|
1080 | .then(function(address) {
|
1081 | patient.address(address);
|
1082 | return patient.save()
|
1083 | .then(function() {
|
1084 | return verify(physician, address.id);
|
1085 | });
|
1086 | });
|
1087 | });
|
1088 | }).catch(done);
|
1089 |
|
1090 | function verify(physician, addressId) {
|
1091 | return physician.patients.find({include: 'address'})
|
1092 | .then(function(ch) {
|
1093 | should.exist(ch);
|
1094 | ch.should.have.lengthOf(1);
|
1095 | ch[0].addressId.toString().should.eql(addressId.toString());
|
1096 | const address = ch[0].address();
|
1097 | should.exist(address);
|
1098 | address.should.be.an.instanceof(Address);
|
1099 | address.name.should.equal('z');
|
1100 | done();
|
1101 | });
|
1102 | }
|
1103 | });
|
1104 |
|
1105 | it('should set targetClass on scope property', function() {
|
1106 | should.equal(Physician.prototype.patients._targetClass, 'Patient');
|
1107 | });
|
1108 |
|
1109 | it('should update scoped record', function(done) {
|
1110 | let id;
|
1111 | Physician.create(function(err, physician) {
|
1112 | physician.patients.create({name: 'a'}, function(err, ch) {
|
1113 | id = ch.id;
|
1114 | physician.patients.updateById(id, {name: 'aa'}, function(err, ch) {
|
1115 | verify(physician);
|
1116 | });
|
1117 | });
|
1118 | });
|
1119 |
|
1120 | function verify(physician) {
|
1121 | physician.patients.findById(id, function(err, ch) {
|
1122 | if (err) return done(err);
|
1123 | should.exist(ch);
|
1124 | ch.id.should.eql(id);
|
1125 | ch.name.should.equal('aa');
|
1126 | done();
|
1127 | });
|
1128 | }
|
1129 | });
|
1130 |
|
1131 | it('should update scoped record with promises', function(done) {
|
1132 | let id;
|
1133 | Physician.create()
|
1134 | .then(function(physician) {
|
1135 | return physician.patients.create({name: 'a'})
|
1136 | .then(function(ch) {
|
1137 | id = ch.id;
|
1138 | return physician.patients.updateById(id, {name: 'aa'})
|
1139 | .then(function(ch) {
|
1140 | return verify(physician);
|
1141 | });
|
1142 | });
|
1143 | }).catch(done);
|
1144 |
|
1145 | function verify(physician) {
|
1146 | return physician.patients.findById(id)
|
1147 | .then(function(ch) {
|
1148 | should.exist(ch);
|
1149 | ch.id.should.eql(id);
|
1150 | ch.name.should.equal('aa');
|
1151 | done();
|
1152 | });
|
1153 | }
|
1154 | });
|
1155 |
|
1156 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
1157 | 'should destroy scoped record', function(done) {
|
1158 | let id;
|
1159 | Physician.create(function(err, physician) {
|
1160 | physician.patients.create({name: 'a'}, function(err, ch) {
|
1161 | id = ch.id;
|
1162 | physician.patients.destroy(id, function(err, ch) {
|
1163 | verify(physician);
|
1164 | });
|
1165 | });
|
1166 | });
|
1167 |
|
1168 | function verify(physician) {
|
1169 | physician.patients.findById(id, function(err, ch) {
|
1170 | should.exist(err);
|
1171 | done();
|
1172 | });
|
1173 | }
|
1174 | });
|
1175 |
|
1176 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
1177 | 'should destroy scoped record with promises', function(done) {
|
1178 | let id;
|
1179 | Physician.create()
|
1180 | .then(function(physician) {
|
1181 | return physician.patients.create({name: 'a'})
|
1182 | .then(function(ch) {
|
1183 | id = ch.id;
|
1184 | return physician.patients.destroy(id)
|
1185 | .then(function(ch) {
|
1186 | return verify(physician);
|
1187 | });
|
1188 | });
|
1189 | }).catch(done);
|
1190 |
|
1191 | function verify(physician) {
|
1192 | return physician.patients.findById(id)
|
1193 | .then(function(ch) {
|
1194 | should.not.exist(ch);
|
1195 | done();
|
1196 | })
|
1197 | .catch(function(err) {
|
1198 | should.exist(err);
|
1199 | done();
|
1200 | });
|
1201 | }
|
1202 | });
|
1203 |
|
1204 | it('should check existence of a scoped record', function(done) {
|
1205 | let id;
|
1206 | Physician.create(function(err, physician) {
|
1207 | physician.patients.create({name: 'a'}, function(err, ch) {
|
1208 | if (err) return done(err);
|
1209 | id = ch.id;
|
1210 | physician.patients.create({name: 'z'}, function() {
|
1211 | physician.patients.create({name: 'c'}, function() {
|
1212 | verify(physician);
|
1213 | });
|
1214 | });
|
1215 | });
|
1216 | });
|
1217 |
|
1218 | function verify(physician) {
|
1219 | physician.patients.exists(id, function(err, flag) {
|
1220 | if (err) return done(err);
|
1221 | flag.should.be.eql(true);
|
1222 | done();
|
1223 | });
|
1224 | }
|
1225 | });
|
1226 |
|
1227 | it('should check existence of a scoped record with promises', function(done) {
|
1228 | let id;
|
1229 | Physician.create()
|
1230 | .then(function(physician) {
|
1231 | return physician.patients.create({name: 'a'})
|
1232 | .then(function(ch) {
|
1233 | id = ch.id;
|
1234 | return physician.patients.create({name: 'z'});
|
1235 | })
|
1236 | .then(function() {
|
1237 | return physician.patients.create({name: 'c'});
|
1238 | })
|
1239 | .then(function() {
|
1240 | return verify(physician);
|
1241 | });
|
1242 | }).catch(done);
|
1243 |
|
1244 | function verify(physician) {
|
1245 | return physician.patients.exists(id)
|
1246 | .then(function(flag) {
|
1247 | flag.should.be.eql(true);
|
1248 | done();
|
1249 | });
|
1250 | }
|
1251 | });
|
1252 |
|
1253 | it('should allow to add connection with instance', function(done) {
|
1254 | Physician.create({name: 'ph1'}, function(e, physician) {
|
1255 | Patient.create({name: 'pa1'}, function(e, patient) {
|
1256 | physician.patients.add(patient, function(e, app) {
|
1257 | should.not.exist(e);
|
1258 | should.exist(app);
|
1259 | app.should.be.an.instanceOf(Appointment);
|
1260 | app.physicianId.should.eql(physician.id);
|
1261 | app.patientId.should.eql(patient.id);
|
1262 | done();
|
1263 | });
|
1264 | });
|
1265 | });
|
1266 | });
|
1267 |
|
1268 | it('should allow to add connection with instance with promises', function(done) {
|
1269 | Physician.create({name: 'ph1'})
|
1270 | .then(function(physician) {
|
1271 | return Patient.create({name: 'pa1'})
|
1272 | .then(function(patient) {
|
1273 | return physician.patients.add(patient)
|
1274 | .then(function(app) {
|
1275 | should.exist(app);
|
1276 | app.should.be.an.instanceOf(Appointment);
|
1277 | app.physicianId.should.eql(physician.id);
|
1278 | app.patientId.should.eql(patient.id);
|
1279 | done();
|
1280 | });
|
1281 | });
|
1282 | }).catch(done);
|
1283 | });
|
1284 |
|
1285 | it('should allow to add connection with through data', function(done) {
|
1286 | Physician.create({name: 'ph1'}, function(e, physician) {
|
1287 | Patient.create({name: 'pa1'}, function(e, patient) {
|
1288 | const now = Date.now();
|
1289 | physician.patients.add(patient, {date: new Date(now)}, function(e, app) {
|
1290 | should.not.exist(e);
|
1291 | should.exist(app);
|
1292 | app.should.be.an.instanceOf(Appointment);
|
1293 | app.physicianId.should.eql(physician.id);
|
1294 | app.patientId.should.eql(patient.id);
|
1295 | app.patientId.should.eql(patient.id);
|
1296 | app.date.getTime().should.equal(now);
|
1297 | done();
|
1298 | });
|
1299 | });
|
1300 | });
|
1301 | });
|
1302 |
|
1303 | it('should allow to add connection with through data with promises', function(done) {
|
1304 | Physician.create({name: 'ph1'})
|
1305 | .then(function(physician) {
|
1306 | return Patient.create({name: 'pa1'})
|
1307 | .then(function(patient) {
|
1308 | const now = Date.now();
|
1309 | return physician.patients.add(patient, {date: new Date(now)})
|
1310 | .then(function(app) {
|
1311 | should.exist(app);
|
1312 | app.should.be.an.instanceOf(Appointment);
|
1313 | app.physicianId.should.eql(physician.id);
|
1314 | app.patientId.should.eql(patient.id);
|
1315 | app.patientId.should.eql(patient.id);
|
1316 | app.date.getTime().should.equal(now);
|
1317 | done();
|
1318 | });
|
1319 | });
|
1320 | }).catch(done);
|
1321 | });
|
1322 |
|
1323 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
1324 | 'should allow to remove connection with instance', function(done) {
|
1325 | let id;
|
1326 | Physician.create(function(err, physician) {
|
1327 | physician.patients.create({name: 'a'}, function(err, patient) {
|
1328 | id = patient.id;
|
1329 | physician.patients.remove(id, function(err, ch) {
|
1330 | verify(physician);
|
1331 | });
|
1332 | });
|
1333 | });
|
1334 |
|
1335 | function verify(physician) {
|
1336 | physician.patients.exists(id, function(err, flag) {
|
1337 | if (err) return done(err);
|
1338 | flag.should.be.eql(false);
|
1339 | done();
|
1340 | });
|
1341 | }
|
1342 | });
|
1343 |
|
1344 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
1345 | 'should allow to remove connection with instance with promises', function(done) {
|
1346 | let id;
|
1347 | Physician.create()
|
1348 | .then(function(physician) {
|
1349 | return physician.patients.create({name: 'a'})
|
1350 | .then(function(patient) {
|
1351 | id = patient.id;
|
1352 | return physician.patients.remove(id)
|
1353 | .then(function(ch) {
|
1354 | return verify(physician);
|
1355 | });
|
1356 | });
|
1357 | }).catch(done);
|
1358 |
|
1359 | function verify(physician) {
|
1360 | return physician.patients.exists(id)
|
1361 | .then(function(flag) {
|
1362 | flag.should.be.eql(false);
|
1363 | done();
|
1364 | });
|
1365 | }
|
1366 | });
|
1367 |
|
1368 | beforeEach(function(done) {
|
1369 | Appointment.destroyAll(function(err) {
|
1370 | Physician.destroyAll(function(err) {
|
1371 | Patient.destroyAll(done);
|
1372 | });
|
1373 | });
|
1374 | });
|
1375 | });
|
1376 |
|
1377 | describe('hasMany through - collect', function() {
|
1378 | let Physician, Patient, Appointment, Address;
|
1379 | let idPatient, idPhysician;
|
1380 |
|
1381 | beforeEach(function(done) {
|
1382 | idPatient = uid.fromConnector(db) || 1234;
|
1383 | idPhysician = uid.fromConnector(db) || 2345;
|
1384 | Physician = db.define('Physician', {name: String});
|
1385 | Patient = db.define('Patient', {name: String});
|
1386 | Appointment = db.define('Appointment', {date: {type: Date,
|
1387 | default: function() {
|
1388 | return new Date();
|
1389 | }}});
|
1390 | Address = db.define('Address', {name: String});
|
1391 |
|
1392 | db.automigrate(['Physician', 'Patient', 'Appointment', 'Address'], done);
|
1393 | });
|
1394 |
|
1395 | describe('with default options', function() {
|
1396 | it('can determine the collect by modelTo\'s name as default', function() {
|
1397 | Physician.hasMany(Patient, {through: Appointment});
|
1398 | Patient.hasMany(Physician, {through: Appointment, as: 'yyy'});
|
1399 | Patient.belongsTo(Address);
|
1400 | Appointment.belongsTo(Physician);
|
1401 | Appointment.belongsTo(Patient);
|
1402 | const physician = new Physician({id: idPhysician});
|
1403 | const scope1 = physician.patients._scope;
|
1404 | scope1.should.have.property('collect', 'patient');
|
1405 | scope1.should.have.property('include', 'patient');
|
1406 | const patient = new Patient({id: idPatient});
|
1407 | const scope2 = patient.yyy._scope;
|
1408 | scope2.should.have.property('collect', 'physician');
|
1409 | scope2.should.have.property('include', 'physician');
|
1410 | });
|
1411 | });
|
1412 |
|
1413 | describe('when custom reverse belongsTo names for both sides', function() {
|
1414 | it('can determine the collect via keyThrough', function() {
|
1415 | Physician.hasMany(Patient, {
|
1416 | through: Appointment, foreignKey: 'fooId', keyThrough: 'barId',
|
1417 | });
|
1418 | Patient.hasMany(Physician, {
|
1419 | through: Appointment, foreignKey: 'barId', keyThrough: 'fooId', as: 'yyy',
|
1420 | });
|
1421 | Appointment.belongsTo(Physician, {as: 'foo'});
|
1422 | Appointment.belongsTo(Patient, {as: 'bar'});
|
1423 | Patient.belongsTo(Address);
|
1424 | Appointment.belongsTo(Patient, {as: 'car'});
|
1425 |
|
1426 | const physician = new Physician({id: idPhysician});
|
1427 | const scope1 = physician.patients._scope;
|
1428 | scope1.should.have.property('collect', 'bar');
|
1429 | scope1.should.have.property('include', 'bar');
|
1430 | const patient = new Patient({id: idPatient});
|
1431 | const scope2 = patient.yyy._scope;
|
1432 | scope2.should.have.property('collect', 'foo');
|
1433 | scope2.should.have.property('include', 'foo');
|
1434 | });
|
1435 |
|
1436 | it('can determine the collect via modelTo name', function() {
|
1437 | Physician.hasMany(Patient, {through: Appointment});
|
1438 | Patient.hasMany(Physician, {through: Appointment, as: 'yyy'});
|
1439 | Appointment.belongsTo(Physician, {as: 'foo', foreignKey: 'physicianId'});
|
1440 | Appointment.belongsTo(Patient, {as: 'bar', foreignKey: 'patientId'});
|
1441 | Patient.belongsTo(Address);
|
1442 |
|
1443 | const physician = new Physician({id: idPhysician});
|
1444 | const scope1 = physician.patients._scope;
|
1445 | scope1.should.have.property('collect', 'bar');
|
1446 | scope1.should.have.property('include', 'bar');
|
1447 | const patient = new Patient({id: idPatient});
|
1448 | const scope2 = patient.yyy._scope;
|
1449 | scope2.should.have.property('collect', 'foo');
|
1450 | scope2.should.have.property('include', 'foo');
|
1451 | });
|
1452 |
|
1453 | it('can determine the collect via modelTo name (with jams)', function() {
|
1454 | Physician.hasMany(Patient, {through: Appointment});
|
1455 | Patient.hasMany(Physician, {through: Appointment, as: 'yyy'});
|
1456 | Appointment.belongsTo(Physician, {as: 'foo', foreignKey: 'physicianId'});
|
1457 | Appointment.belongsTo(Patient, {as: 'bar', foreignKey: 'patientId'});
|
1458 | Patient.belongsTo(Address);
|
1459 | Appointment.belongsTo(Physician, {as: 'goo', foreignKey: 'physicianId'});
|
1460 | Appointment.belongsTo(Patient, {as: 'car', foreignKey: 'patientId'});
|
1461 |
|
1462 | const physician = new Physician({id: idPhysician});
|
1463 | const scope1 = physician.patients._scope;
|
1464 | scope1.should.have.property('collect', 'bar');
|
1465 | scope1.should.have.property('include', 'bar');
|
1466 | const patient = new Patient({id: idPatient});
|
1467 | const scope2 = patient.yyy._scope;
|
1468 | scope2.should.have.property('collect', 'foo');
|
1469 | scope2.should.have.property('include', 'foo');
|
1470 | });
|
1471 | });
|
1472 |
|
1473 | describe('when custom reverse belongsTo name for one side only', function() {
|
1474 | beforeEach(function() {
|
1475 | Physician.hasMany(Patient, {as: 'xxx', through: Appointment, foreignKey: 'fooId'});
|
1476 | Patient.hasMany(Physician, {as: 'yyy', through: Appointment, keyThrough: 'fooId'});
|
1477 | Appointment.belongsTo(Physician, {as: 'foo'});
|
1478 | Appointment.belongsTo(Patient);
|
1479 | Patient.belongsTo(Address);
|
1480 | Appointment.belongsTo(Physician, {as: 'bar'});
|
1481 | });
|
1482 |
|
1483 | it('can determine the collect via model name', function() {
|
1484 | const physician = new Physician({id: idPhysician});
|
1485 | const scope1 = physician.xxx._scope;
|
1486 | scope1.should.have.property('collect', 'patient');
|
1487 | scope1.should.have.property('include', 'patient');
|
1488 | });
|
1489 |
|
1490 | it('can determine the collect via keyThrough', function() {
|
1491 | const patient = new Patient({id: idPatient});
|
1492 | const scope2 = patient.yyy._scope;
|
1493 | scope2.should.have.property('collect', 'foo');
|
1494 | scope2.should.have.property('include', 'foo');
|
1495 | });
|
1496 | });
|
1497 | });
|
1498 |
|
1499 | describe('hasMany through - customized relation name and foreign key', function() {
|
1500 | let Physician, Patient, Appointment;
|
1501 |
|
1502 | beforeEach(function(done) {
|
1503 | Physician = db.define('Physician', {name: String});
|
1504 | Patient = db.define('Patient', {name: String});
|
1505 | Appointment = db.define('Appointment', {date: {type: Date, defaultFn: 'now'}});
|
1506 |
|
1507 | db.automigrate(['Physician', 'Patient', 'Appointment'], done);
|
1508 | });
|
1509 |
|
1510 | it('should use real target class', function() {
|
1511 | Physician.hasMany(Patient, {through: Appointment, as: 'xxx', foreignKey: 'aaaId', keyThrough: 'bbbId'});
|
1512 | Patient.hasMany(Physician, {through: Appointment, as: 'yyy', foreignKey: 'bbbId', keyThrough: 'aaaId'});
|
1513 | Appointment.belongsTo(Physician, {as: 'aaa', foreignKey: 'aaaId'});
|
1514 | Appointment.belongsTo(Patient, {as: 'bbb', foreignKey: 'bbbId'});
|
1515 | const physician = new Physician({id: 1});
|
1516 | physician.xxx.should.have.property('_targetClass', 'Patient');
|
1517 | const patient = new Patient({id: 1});
|
1518 | patient.yyy.should.have.property('_targetClass', 'Physician');
|
1519 | });
|
1520 | });
|
1521 |
|
1522 | describe('hasMany through bi-directional relations on the same model', function() {
|
1523 | let User, Follow, Address;
|
1524 | let idFollower, idFollowee;
|
1525 |
|
1526 | before(function(done) {
|
1527 | idFollower = uid.fromConnector(db) || 3456;
|
1528 | idFollowee = uid.fromConnector(db) || 4567;
|
1529 | User = db.define('User', {name: String});
|
1530 | Follow = db.define('Follow', {date: {type: Date,
|
1531 | default: function() {
|
1532 | return new Date();
|
1533 | }}});
|
1534 | Address = db.define('Address', {name: String});
|
1535 |
|
1536 | User.hasMany(User, {
|
1537 | as: 'followers', foreignKey: 'followeeId', keyThrough: 'followerId', through: Follow,
|
1538 | });
|
1539 | User.hasMany(User, {
|
1540 | as: 'following', foreignKey: 'followerId', keyThrough: 'followeeId', through: Follow,
|
1541 | });
|
1542 | User.belongsTo(Address);
|
1543 | Follow.belongsTo(User, {as: 'follower'});
|
1544 | Follow.belongsTo(User, {as: 'followee'});
|
1545 | db.automigrate(['User', 'Follow'], done);
|
1546 | });
|
1547 |
|
1548 | it('should set foreignKeys of through model correctly in first relation',
|
1549 | function(done) {
|
1550 | const follower = new User({id: idFollower});
|
1551 | const followee = new User({id: idFollowee});
|
1552 | followee.followers.add(follower, function(err, throughInst) {
|
1553 | if (err) return done(err);
|
1554 | should.exist(throughInst);
|
1555 | throughInst.followerId.should.eql(follower.id);
|
1556 | throughInst.followeeId.should.eql(followee.id);
|
1557 | done();
|
1558 | });
|
1559 | });
|
1560 |
|
1561 | it('should set foreignKeys of through model correctly in second relation',
|
1562 | function(done) {
|
1563 | const follower = new User({id: idFollower});
|
1564 | const followee = new User({id: idFollowee});
|
1565 | follower.following.add(followee, function(err, throughInst) {
|
1566 | if (err) return done(err);
|
1567 | should.exist(throughInst);
|
1568 | throughInst.followeeId.toString().should.eql(followee.id.toString());
|
1569 | throughInst.followerId.toString().should.eql(follower.id.toString());
|
1570 | done();
|
1571 | });
|
1572 | });
|
1573 | });
|
1574 |
|
1575 | describe('hasMany through - between same models', function() {
|
1576 | let User, Follow, Address;
|
1577 | let idFollower, idFollowee;
|
1578 |
|
1579 | before(function(done) {
|
1580 | idFollower = uid.fromConnector(db) || 3456;
|
1581 | idFollowee = uid.fromConnector(db) || 4567;
|
1582 | User = db.define('User', {name: String});
|
1583 | Follow = db.define('Follow', {date: {type: Date,
|
1584 | default: function() {
|
1585 | return new Date();
|
1586 | }}});
|
1587 | Address = db.define('Address', {name: String});
|
1588 |
|
1589 | User.hasMany(User, {
|
1590 | as: 'followers', foreignKey: 'followeeId', keyThrough: 'followerId', through: Follow,
|
1591 | });
|
1592 | User.hasMany(User, {
|
1593 | as: 'following', foreignKey: 'followerId', keyThrough: 'followeeId', through: Follow,
|
1594 | });
|
1595 | User.belongsTo(Address);
|
1596 | Follow.belongsTo(User, {as: 'follower'});
|
1597 | Follow.belongsTo(User, {as: 'followee'});
|
1598 | db.automigrate(['User', 'Follow', 'Address'], done);
|
1599 | });
|
1600 |
|
1601 | it('should set the keyThrough and the foreignKey', function(done) {
|
1602 | const user = new User({id: idFollower});
|
1603 | const user2 = new User({id: idFollowee});
|
1604 | user.following.add(user2, function(err, f) {
|
1605 | if (err) return done(err);
|
1606 | should.exist(f);
|
1607 | f.followeeId.should.eql(user2.id);
|
1608 | f.followerId.should.eql(user.id);
|
1609 | done();
|
1610 | });
|
1611 | });
|
1612 |
|
1613 | it('can determine the collect via keyThrough for each side', function() {
|
1614 | const user = new User({id: idFollower});
|
1615 | const scope1 = user.followers._scope;
|
1616 | scope1.should.have.property('collect', 'follower');
|
1617 | scope1.should.have.property('include', 'follower');
|
1618 | const scope2 = user.following._scope;
|
1619 | scope2.should.have.property('collect', 'followee');
|
1620 | scope2.should.have.property('include', 'followee');
|
1621 | });
|
1622 | });
|
1623 |
|
1624 | describe('hasMany with properties', function() {
|
1625 | before(function(done) {
|
1626 | Book = db.define('Book', {name: String, type: String});
|
1627 | Chapter = db.define('Chapter', {name: {type: String, index: true},
|
1628 | bookType: String});
|
1629 | Book.hasMany(Chapter, {properties: {type: 'bookType'}});
|
1630 | db.automigrate(['Book', 'Chapter'], done);
|
1631 | });
|
1632 |
|
1633 | it('should create record on scope', function(done) {
|
1634 | Book.create({type: 'fiction'}, function(err, book) {
|
1635 | book.chapters.create(function(err, c) {
|
1636 | if (err) return done(err);
|
1637 | should.exist(c);
|
1638 | c.bookId.should.eql(book.id);
|
1639 | c.bookType.should.equal('fiction');
|
1640 | done();
|
1641 | });
|
1642 | });
|
1643 | });
|
1644 |
|
1645 | it('should create record on scope with promises', function(done) {
|
1646 | Book.create({type: 'fiction'})
|
1647 | .then(function(book) {
|
1648 | return book.chapters.create()
|
1649 | .then(function(c) {
|
1650 | should.exist(c);
|
1651 | c.bookId.should.eql(book.id);
|
1652 | c.bookType.should.equal('fiction');
|
1653 | done();
|
1654 | });
|
1655 | }).catch(done);
|
1656 | });
|
1657 | });
|
1658 |
|
1659 | describe('hasMany with scope and properties', function() {
|
1660 | it('can be declared with properties', function(done) {
|
1661 | Category = db.define('Category', {name: String, jobType: String});
|
1662 | Job = db.define('Job', {name: String, type: String});
|
1663 |
|
1664 | Category.hasMany(Job, {
|
1665 | properties: function(inst, target) {
|
1666 | if (!inst.jobType) return;
|
1667 | return {type: inst.jobType};
|
1668 | },
|
1669 | scope: function(inst, filter) {
|
1670 | const m = this.properties(inst);
|
1671 | if (m) return {where: m};
|
1672 | },
|
1673 | });
|
1674 | db.automigrate(['Category', 'Job'], done);
|
1675 | });
|
1676 |
|
1677 | it('should create record on scope', function(done) {
|
1678 | Category.create(function(err, c) {
|
1679 | should.not.exists(err);
|
1680 | c.jobs.create({type: 'book'}, function(err, p) {
|
1681 | should.not.exists(err);
|
1682 | p.categoryId.should.eql(c.id);
|
1683 | p.type.should.equal('book');
|
1684 | c.jobs.create({type: 'widget'}, function(err, p) {
|
1685 | should.not.exists(err);
|
1686 | p.categoryId.should.eql(c.id);
|
1687 | p.type.should.equal('widget');
|
1688 | done();
|
1689 | });
|
1690 | });
|
1691 | });
|
1692 | });
|
1693 |
|
1694 | it('should create record on scope with promises', function(done) {
|
1695 | Category.create()
|
1696 | .then(function(c) {
|
1697 | return c.jobs.create({type: 'book'})
|
1698 | .then(function(p) {
|
1699 | p.categoryId.should.eql(c.id);
|
1700 | p.type.should.equal('book');
|
1701 | return c.jobs.create({type: 'widget'})
|
1702 | .then(function(p) {
|
1703 | p.categoryId.should.eql(c.id);
|
1704 | p.type.should.equal('widget');
|
1705 | done();
|
1706 | });
|
1707 | });
|
1708 | }).catch(done);
|
1709 | });
|
1710 |
|
1711 | it('should find records on scope', function(done) {
|
1712 | Category.findOne(function(err, c) {
|
1713 | should.not.exists(err);
|
1714 | c.jobs(function(err, jobs) {
|
1715 | should.not.exists(err);
|
1716 | jobs.should.have.length(2);
|
1717 | done();
|
1718 | });
|
1719 | });
|
1720 | });
|
1721 |
|
1722 | it('should find records on scope with promises', function(done) {
|
1723 | Category.findOne()
|
1724 | .then(function(c) {
|
1725 | return c.jobs.find();
|
1726 | })
|
1727 | .then(function(jobs) {
|
1728 | jobs.should.have.length(2);
|
1729 | done();
|
1730 | })
|
1731 | .catch(done);
|
1732 | });
|
1733 |
|
1734 | it('should find record on scope - filtered', function(done) {
|
1735 | Category.findOne(function(err, c) {
|
1736 | should.not.exists(err);
|
1737 | c.jobs({where: {type: 'book'}}, function(err, jobs) {
|
1738 | should.not.exists(err);
|
1739 | jobs.should.have.length(1);
|
1740 | jobs[0].type.should.equal('book');
|
1741 | done();
|
1742 | });
|
1743 | });
|
1744 | });
|
1745 |
|
1746 | it('should find record on scope with promises - filtered', function(done) {
|
1747 | Category.findOne()
|
1748 | .then(function(c) {
|
1749 | return c.jobs.find({where: {type: 'book'}});
|
1750 | })
|
1751 | .then(function(jobs) {
|
1752 | jobs.should.have.length(1);
|
1753 | jobs[0].type.should.equal('book');
|
1754 | done();
|
1755 | })
|
1756 | .catch(done);
|
1757 | });
|
1758 |
|
1759 |
|
1760 |
|
1761 |
|
1762 |
|
1763 |
|
1764 |
|
1765 |
|
1766 |
|
1767 |
|
1768 | it('should create record on scope - properties', function(done) {
|
1769 | Category.findOne(function(err, c) {
|
1770 | should.not.exists(err);
|
1771 | c.jobType = 'tool';
|
1772 | c.jobs.create(function(err, p) {
|
1773 | p.categoryId.should.eql(c.id);
|
1774 | p.type.should.equal('tool');
|
1775 | done();
|
1776 | });
|
1777 | });
|
1778 | });
|
1779 |
|
1780 |
|
1781 | it('should find records on scope', function(done) {
|
1782 | Category.findOne(function(err, c) {
|
1783 | should.not.exists(err);
|
1784 | c.jobs(function(err, jobs) {
|
1785 | should.not.exists(err);
|
1786 | jobs.should.have.length(3);
|
1787 | done();
|
1788 | });
|
1789 | });
|
1790 | });
|
1791 |
|
1792 | it('should find record on scope - scoped', function(done) {
|
1793 | Category.findOne(function(err, c) {
|
1794 | should.not.exists(err);
|
1795 | c.jobType = 'book';
|
1796 | c.jobs(function(err, jobs) {
|
1797 | should.not.exists(err);
|
1798 | jobs.should.have.length(1);
|
1799 | jobs[0].type.should.equal('book');
|
1800 | done();
|
1801 | });
|
1802 | });
|
1803 | });
|
1804 |
|
1805 |
|
1806 | it('should find record on scope - scoped', function(done) {
|
1807 | Category.findOne(function(err, c) {
|
1808 | should.not.exists(err);
|
1809 | c.jobType = 'tool';
|
1810 | c.jobs(function(err, jobs) {
|
1811 | should.not.exists(err);
|
1812 | jobs.should.have.length(1);
|
1813 | jobs[0].type.should.equal('tool');
|
1814 | done();
|
1815 | });
|
1816 | });
|
1817 | });
|
1818 |
|
1819 | it('should find count of records on scope - scoped', function(done) {
|
1820 | Category.findOne(function(err, c) {
|
1821 | should.not.exists(err);
|
1822 | c.jobType = 'tool';
|
1823 | c.jobs.count(function(err, count) {
|
1824 | should.not.exists(err);
|
1825 | count.should.equal(1);
|
1826 | done();
|
1827 | });
|
1828 | });
|
1829 | });
|
1830 |
|
1831 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
1832 | 'should delete records on scope - scoped', function(done) {
|
1833 | Category.findOne(function(err, c) {
|
1834 | should.not.exists(err);
|
1835 | c.jobType = 'tool';
|
1836 | c.jobs.destroyAll(function(err, result) {
|
1837 | done(err);
|
1838 | });
|
1839 | });
|
1840 | });
|
1841 |
|
1842 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
1843 | 'should find record on scope - verify', function(done) {
|
1844 | Category.findOne(function(err, c) {
|
1845 | should.not.exists(err);
|
1846 | c.jobs(function(err, jobs) {
|
1847 | should.not.exists(err);
|
1848 | jobs.should.have.length(2);
|
1849 | done(err);
|
1850 | });
|
1851 | });
|
1852 | });
|
1853 | });
|
1854 |
|
1855 | describe('relations validation', function() {
|
1856 | let validationError;
|
1857 |
|
1858 | const logRelationValidationError = function(code, rType, rName) {
|
1859 | validationError = {code, rType, rName};
|
1860 | };
|
1861 |
|
1862 | it('rejects belongsTo relation if `model` is not provided', function() {
|
1863 | try {
|
1864 | const Picture = db.define('Picture', {name: String}, {relations: {
|
1865 | author: {
|
1866 | type: 'belongsTo',
|
1867 | foreignKey: 'authorId'},
|
1868 | }});
|
1869 | should.not.exist(Picture, 'relation validation should have thrown');
|
1870 | } catch (err) {
|
1871 | err.details.should.eql({
|
1872 | code: 'BELONGS_TO_MISSING_MODEL',
|
1873 | rType: 'belongsTo',
|
1874 | rName: 'author'});
|
1875 | }
|
1876 | });
|
1877 |
|
1878 | it('rejects polymorphic belongsTo relation if `model` is provided', function() {
|
1879 | try {
|
1880 | const Picture = db.define('Picture', {name: String}, {relations: {
|
1881 | imageable: {
|
1882 | type: 'belongsTo',
|
1883 | model: 'Picture',
|
1884 | polymorphic: true},
|
1885 | }});
|
1886 | should.not.exist(Picture, 'relation validation should have thrown');
|
1887 | } catch (err) {
|
1888 | err.details.should.eql({
|
1889 | code: 'POLYMORPHIC_BELONGS_TO_MODEL',
|
1890 | rType: 'belongsTo',
|
1891 | rName: 'imageable'});
|
1892 | }
|
1893 | });
|
1894 |
|
1895 | it('rejects polymorphic non belongsTo relation if `model` is not provided', function() {
|
1896 | try {
|
1897 | const Article = db.define('Picture', {name: String}, {relations: {
|
1898 | pictures: {
|
1899 | type: 'hasMany',
|
1900 | polymorphic: 'imageable'},
|
1901 | }});
|
1902 | should.not.exist(Picture, 'relation validation should have thrown');
|
1903 | } catch (err) {
|
1904 | err.details.should.eql({
|
1905 | code: 'POLYMORPHIC_NOT_BELONGS_TO_MISSING_MODEL',
|
1906 | rType: 'hasMany',
|
1907 | rName: 'pictures'});
|
1908 | }
|
1909 | });
|
1910 |
|
1911 | it('rejects polymorphic relation if `foreignKey` is provided but discriminator ' +
|
1912 | 'is missing', function() {
|
1913 | try {
|
1914 | const Article = db.define('Picture', {name: String}, {relations: {
|
1915 | pictures: {
|
1916 | type: 'hasMany',
|
1917 | model: 'Picture',
|
1918 | polymorphic: {foreignKey: 'imageableId'}},
|
1919 | }});
|
1920 | should.not.exist(Picture, 'relation validation should have thrown');
|
1921 | } catch (err) {
|
1922 | err.details.should.eql({
|
1923 | code: 'POLYMORPHIC_MISSING_DISCRIMINATOR',
|
1924 | rType: 'hasMany',
|
1925 | rName: 'pictures'});
|
1926 | }
|
1927 | });
|
1928 |
|
1929 | it('rejects polymorphic relation if `discriminator` is provided but foreignKey ' +
|
1930 | 'is missing', function() {
|
1931 | try {
|
1932 | const Article = db.define('Picture', {name: String}, {relations: {
|
1933 | pictures: {
|
1934 | type: 'hasMany',
|
1935 | model: 'Picture',
|
1936 | polymorphic: {discriminator: 'imageableType'}},
|
1937 | }});
|
1938 | should.not.exist(Picture, 'relation validation should have thrown');
|
1939 | } catch (err) {
|
1940 | err.details.should.eql({
|
1941 | code: 'POLYMORPHIC_MISSING_FOREIGN_KEY',
|
1942 | rType: 'hasMany',
|
1943 | rName: 'pictures'});
|
1944 | }
|
1945 | });
|
1946 |
|
1947 | it('rejects polymorphic relation if `polymorphic.as` is provided along ' +
|
1948 | 'with custom foreignKey/discriminator', function() {
|
1949 | try {
|
1950 | const Article = db.define('Picture', {name: String}, {relations: {
|
1951 | pictures: {
|
1952 | type: 'hasMany',
|
1953 | model: 'Picture',
|
1954 | polymorphic: {
|
1955 | as: 'image',
|
1956 | foreignKey: 'imageableId',
|
1957 | discriminator: 'imageableType',
|
1958 | }},
|
1959 | }});
|
1960 | should.not.exist(Picture, 'relation validation should have thrown');
|
1961 | } catch (err) {
|
1962 | err.details.should.eql({
|
1963 | code: 'POLYMORPHIC_EXTRANEOUS_AS',
|
1964 | rType: 'hasMany',
|
1965 | rName: 'pictures'});
|
1966 | }
|
1967 | });
|
1968 |
|
1969 | it('rejects polymorphic relation if `polymorphic.selector` is provided along ' +
|
1970 | 'with custom foreignKey/discriminator', function() {
|
1971 | try {
|
1972 | const Article = db.define('Picture', {name: String}, {relations: {
|
1973 | pictures: {
|
1974 | type: 'hasMany',
|
1975 | model: 'Picture',
|
1976 | polymorphic: {
|
1977 | selector: 'image',
|
1978 | foreignKey: 'imageableId',
|
1979 | discriminator: 'imageableType',
|
1980 | }},
|
1981 | }});
|
1982 | should.not.exist(Picture, 'relation validation should have thrown');
|
1983 | } catch (err) {
|
1984 | err.details.should.eql({
|
1985 | code: 'POLYMORPHIC_EXTRANEOUS_SELECTOR',
|
1986 | rType: 'hasMany',
|
1987 | rName: 'pictures'});
|
1988 | }
|
1989 | });
|
1990 |
|
1991 | it('warns on use of deprecated `polymorphic.as` keyword in polymorphic relation', function() {
|
1992 | let message = 'deprecation not reported';
|
1993 | process.once('deprecation', function(err) { message = err.message; });
|
1994 |
|
1995 | const Article = db.define('Picture', {name: String}, {relations: {
|
1996 | pictures: {type: 'hasMany', model: 'Picture', polymorphic: {as: 'imageable'}},
|
1997 | }});
|
1998 |
|
1999 | message.should.match(/keyword `polymorphic.as` which will be DEPRECATED in LoopBack.next/);
|
2000 | });
|
2001 | });
|
2002 |
|
2003 | describe('polymorphic hasOne', function() {
|
2004 | before(function(done) {
|
2005 | Picture = db.define('Picture', {name: String});
|
2006 | Article = db.define('Article', {name: String});
|
2007 | Employee = db.define('Employee', {name: String});
|
2008 |
|
2009 | db.automigrate(['Picture', 'Article', 'Employee'], done);
|
2010 | });
|
2011 |
|
2012 | it('can be declared using default polymorphic selector', function(done) {
|
2013 | Article.hasOne(Picture, {as: 'packshot', polymorphic: 'imageable'});
|
2014 | Employee.hasOne(Picture, {as: 'mugshot', polymorphic: 'imageable'});
|
2015 | Picture.belongsTo('imageable', {polymorphic: true});
|
2016 |
|
2017 | Article.relations['packshot'].toJSON().should.eql({
|
2018 | name: 'packshot',
|
2019 | type: 'hasOne',
|
2020 | modelFrom: 'Article',
|
2021 | keyFrom: 'id',
|
2022 | modelTo: 'Picture',
|
2023 | keyTo: 'imageableId',
|
2024 | multiple: false,
|
2025 | polymorphic: {
|
2026 | selector: 'imageable',
|
2027 | foreignKey: 'imageableId',
|
2028 | discriminator: 'imageableType',
|
2029 | },
|
2030 | });
|
2031 |
|
2032 | Picture.relations['imageable'].toJSON().should.eql({
|
2033 | name: 'imageable',
|
2034 | type: 'belongsTo',
|
2035 | modelFrom: 'Picture',
|
2036 | keyFrom: 'imageableId',
|
2037 | modelTo: '<polymorphic>',
|
2038 | keyTo: 'id',
|
2039 | multiple: false,
|
2040 | polymorphic: {
|
2041 | selector: 'imageable',
|
2042 | foreignKey: 'imageableId',
|
2043 | discriminator: 'imageableType',
|
2044 | },
|
2045 | });
|
2046 |
|
2047 | db.automigrate(['Picture', 'Article', 'Employee'], done);
|
2048 | });
|
2049 |
|
2050 | it('should create polymorphic relation - Article', function(done) {
|
2051 | Article.create({name: 'Article 1'}, function(err, article) {
|
2052 | should.not.exists(err);
|
2053 | article.packshot.create({name: 'Packshot'}, function(err, pic) {
|
2054 | if (err) return done(err);
|
2055 | should.exist(pic);
|
2056 | pic.imageableId.should.eql(article.id);
|
2057 | pic.imageableType.should.equal('Article');
|
2058 | done();
|
2059 | });
|
2060 | });
|
2061 | });
|
2062 |
|
2063 | it('should create polymorphic relation with promises - article', function(done) {
|
2064 | Article.create({name: 'Article 1'})
|
2065 | .then(function(article) {
|
2066 | return article.packshot.create({name: 'Packshot'})
|
2067 | .then(function(pic) {
|
2068 | should.exist(pic);
|
2069 | pic.imageableId.should.eql(article.id);
|
2070 | pic.imageableType.should.equal('Article');
|
2071 | done();
|
2072 | });
|
2073 | }).catch(done);
|
2074 | });
|
2075 |
|
2076 | it('should create polymorphic relation - reader', function(done) {
|
2077 | Employee.create({name: 'Employee 1'}, function(err, employee) {
|
2078 | should.not.exists(err);
|
2079 | employee.mugshot.create({name: 'Mugshot'}, function(err, pic) {
|
2080 | if (err) return done(err);
|
2081 | should.exist(pic);
|
2082 | pic.imageableId.should.eql(employee.id);
|
2083 | pic.imageableType.should.equal('Employee');
|
2084 | done();
|
2085 | });
|
2086 | });
|
2087 | });
|
2088 |
|
2089 | it('should find polymorphic relation - article', function(done) {
|
2090 | Article.findOne(function(err, article) {
|
2091 | should.not.exists(err);
|
2092 | article.packshot(function(err, pic) {
|
2093 | if (err) return done(err);
|
2094 |
|
2095 | const packshot = article.packshot();
|
2096 | packshot.should.equal(pic);
|
2097 |
|
2098 | pic.name.should.equal('Packshot');
|
2099 | pic.imageableId.toString().should.eql(article.id.toString());
|
2100 | pic.imageableType.should.equal('Article');
|
2101 | done();
|
2102 | });
|
2103 | });
|
2104 | });
|
2105 |
|
2106 | it('should find polymorphic relation - employee', function(done) {
|
2107 | Employee.findOne(function(err, employee) {
|
2108 | should.not.exists(err);
|
2109 | employee.mugshot(function(err, mugshot) {
|
2110 | if (err) return done(err);
|
2111 | mugshot.name.should.equal('Mugshot');
|
2112 | mugshot.imageableId.toString().should.eql(employee.id.toString());
|
2113 | mugshot.imageableType.should.equal('Employee');
|
2114 | done();
|
2115 | });
|
2116 | });
|
2117 | });
|
2118 |
|
2119 | it('should include polymorphic relation - article', function(done) {
|
2120 | Article.findOne({include: 'packshot'}, function(err, article) {
|
2121 | should.not.exists(err);
|
2122 | const packshot = article.packshot();
|
2123 | should.exist(packshot);
|
2124 | packshot.name.should.equal('Packshot');
|
2125 | done();
|
2126 | });
|
2127 | });
|
2128 |
|
2129 | it('should find polymorphic relation with promises - employee', function(done) {
|
2130 | Employee.findOne()
|
2131 | .then(function(employee) {
|
2132 | return employee.mugshot.get()
|
2133 | .then(function(pic) {
|
2134 | pic.name.should.equal('Mugshot');
|
2135 | pic.imageableId.toString().should.eql(employee.id.toString());
|
2136 | pic.imageableType.should.equal('Employee');
|
2137 | done();
|
2138 | });
|
2139 | }).catch(done);
|
2140 | });
|
2141 |
|
2142 | it('should find inverse polymorphic relation - article', function(done) {
|
2143 | Picture.findOne({where: {name: 'Packshot'}}, function(err, pic) {
|
2144 | should.not.exists(err);
|
2145 | pic.imageable(function(err, imageable) {
|
2146 | if (err) return done(err);
|
2147 | imageable.should.be.instanceof(Article);
|
2148 | imageable.name.should.equal('Article 1');
|
2149 | done();
|
2150 | });
|
2151 | });
|
2152 | });
|
2153 |
|
2154 | it('should include inverse polymorphic relation - article', function(done) {
|
2155 | Picture.findOne({where: {name: 'Packshot'}, include: 'imageable'},
|
2156 | function(err, pic) {
|
2157 | should.not.exists(err);
|
2158 | const imageable = pic.imageable();
|
2159 | should.exist(imageable);
|
2160 | imageable.should.be.instanceof(Article);
|
2161 | imageable.name.should.equal('Article 1');
|
2162 | done();
|
2163 | });
|
2164 | });
|
2165 |
|
2166 | it('should find inverse polymorphic relation - employee', function(done) {
|
2167 | Picture.findOne({where: {name: 'Mugshot'}}, function(err, pic) {
|
2168 | should.not.exists(err);
|
2169 | pic.imageable(function(err, imageable) {
|
2170 | if (err) return done(err);
|
2171 | imageable.should.be.instanceof(Employee);
|
2172 | imageable.name.should.equal('Employee 1');
|
2173 | done();
|
2174 | });
|
2175 | });
|
2176 | });
|
2177 | });
|
2178 |
|
2179 | describe('polymorphic hasOne with non standard ids', function() {
|
2180 | before(function(done) {
|
2181 | Picture = db.define('Picture', {name: String});
|
2182 | Article = db.define('Article', {
|
2183 | username: {type: String, id: true, generated: true},
|
2184 | name: String,
|
2185 | });
|
2186 | Employee = db.define('Employee', {
|
2187 | username: {type: String, id: true, generated: true},
|
2188 | name: String,
|
2189 | });
|
2190 |
|
2191 | db.automigrate(['Picture', 'Article', 'Employee'], done);
|
2192 | });
|
2193 |
|
2194 | it('can be declared using custom foreignKey/discriminator', function(done) {
|
2195 | Article.hasOne(Picture, {
|
2196 | as: 'packshot',
|
2197 | polymorphic: {
|
2198 | foreignKey: 'oid',
|
2199 | discriminator: 'type',
|
2200 | },
|
2201 | });
|
2202 | Employee.hasOne(Picture, {
|
2203 | as: 'mugshot',
|
2204 | polymorphic: {
|
2205 | foreignKey: 'oid',
|
2206 | discriminator: 'type',
|
2207 | },
|
2208 | });
|
2209 | Picture.belongsTo('imageable', {
|
2210 | idName: 'username',
|
2211 | polymorphic: {
|
2212 | idType: Article.definition.properties.username.type,
|
2213 | foreignKey: 'oid',
|
2214 | discriminator: 'type',
|
2215 | },
|
2216 | });
|
2217 |
|
2218 | Article.relations['packshot'].toJSON().should.eql({
|
2219 | name: 'packshot',
|
2220 | type: 'hasOne',
|
2221 | modelFrom: 'Article',
|
2222 | keyFrom: 'username',
|
2223 | modelTo: 'Picture',
|
2224 | keyTo: 'oid',
|
2225 | multiple: false,
|
2226 | polymorphic: {
|
2227 | selector: 'packshot',
|
2228 | foreignKey: 'oid',
|
2229 | discriminator: 'type',
|
2230 | },
|
2231 | });
|
2232 |
|
2233 | const imageableRel = Picture.relations['imageable'].toJSON();
|
2234 |
|
2235 |
|
2236 | assert(typeof imageableRel.polymorphic.idType == 'function');
|
2237 |
|
2238 |
|
2239 |
|
2240 | const idType = imageableRel.polymorphic.idType;
|
2241 | delete imageableRel.polymorphic.idType;
|
2242 |
|
2243 | imageableRel.should.eql({
|
2244 | name: 'imageable',
|
2245 | type: 'belongsTo',
|
2246 | modelFrom: 'Picture',
|
2247 | keyFrom: 'oid',
|
2248 | modelTo: '<polymorphic>',
|
2249 | keyTo: 'username',
|
2250 | multiple: false,
|
2251 | polymorphic: {
|
2252 | selector: 'imageable',
|
2253 | foreignKey: 'oid',
|
2254 | discriminator: 'type',
|
2255 | },
|
2256 | });
|
2257 |
|
2258 |
|
2259 | imageableRel.polymorphic.idType = idType;
|
2260 |
|
2261 | db.automigrate(['Picture', 'Article', 'Employee'], done);
|
2262 | });
|
2263 |
|
2264 | it('should create polymorphic relation - article', function(done) {
|
2265 | Article.create({name: 'Article 1'}, function(err, article) {
|
2266 | should.not.exists(err);
|
2267 | article.packshot.create({name: 'Packshot'}, function(err, pic) {
|
2268 | if (err) return done(err);
|
2269 | should.exist(pic);
|
2270 | pic.oid.toString().should.equal(article.username.toString());
|
2271 | pic.type.should.equal('Article');
|
2272 | done();
|
2273 | });
|
2274 | });
|
2275 | });
|
2276 |
|
2277 | it('should create polymorphic relation with promises - article', function(done) {
|
2278 | Article.create({name: 'Article 1'})
|
2279 | .then(function(article) {
|
2280 | return article.packshot.create({name: 'Packshot'})
|
2281 | .then(function(pic) {
|
2282 | should.exist(pic);
|
2283 | pic.oid.toString().should.equal(article.username.toString());
|
2284 | pic.type.should.equal('Article');
|
2285 | done();
|
2286 | });
|
2287 | }).catch(done);
|
2288 | });
|
2289 |
|
2290 | it('should create polymorphic relation - employee', function(done) {
|
2291 | Employee.create({name: 'Employee 1'}, function(err, employee) {
|
2292 | should.not.exists(err);
|
2293 | employee.mugshot.create({name: 'Mugshot'}, function(err, pic) {
|
2294 | if (err) return done(err);
|
2295 | should.exist(pic);
|
2296 | pic.oid.toString().should.equal(employee.username.toString());
|
2297 | pic.type.should.equal('Employee');
|
2298 | done();
|
2299 | });
|
2300 | });
|
2301 | });
|
2302 |
|
2303 | it('should find polymorphic relation - article', function(done) {
|
2304 | Article.findOne(function(err, article) {
|
2305 | should.not.exists(err);
|
2306 | article.packshot(function(err, pic) {
|
2307 | if (err) return done(err);
|
2308 |
|
2309 | const packshot = article.packshot();
|
2310 | packshot.should.equal(pic);
|
2311 |
|
2312 | pic.name.should.equal('Packshot');
|
2313 | pic.oid.toString().should.equal(article.username.toString());
|
2314 | pic.type.should.equal('Article');
|
2315 | done();
|
2316 | });
|
2317 | });
|
2318 | });
|
2319 |
|
2320 | it('should find polymorphic relation - employee', function(done) {
|
2321 | Employee.findOne(function(err, employee) {
|
2322 | should.not.exists(err);
|
2323 | employee.mugshot(function(err, pic) {
|
2324 | if (err) return done(err);
|
2325 | pic.name.should.equal('Mugshot');
|
2326 | pic.oid.toString().should.equal(employee.username.toString());
|
2327 | pic.type.should.equal('Employee');
|
2328 | done();
|
2329 | });
|
2330 | });
|
2331 | });
|
2332 |
|
2333 | it('should find inverse polymorphic relation - article', function(done) {
|
2334 | Picture.findOne({where: {name: 'Packshot'}}, function(err, pic) {
|
2335 | should.not.exists(err);
|
2336 | pic.imageable(function(err, imageable) {
|
2337 | if (err) return done(err);
|
2338 | imageable.should.be.instanceof(Article);
|
2339 | imageable.name.should.equal('Article 1');
|
2340 | done();
|
2341 | });
|
2342 | });
|
2343 | });
|
2344 |
|
2345 | it('should find inverse polymorphic relation - employee', function(done) {
|
2346 | Picture.findOne({where: {name: 'Mugshot'}}, function(err, p) {
|
2347 | should.not.exists(err);
|
2348 | p.imageable(function(err, imageable) {
|
2349 | if (err) return done(err);
|
2350 | imageable.should.be.instanceof(Employee);
|
2351 | imageable.name.should.equal('Employee 1');
|
2352 | done();
|
2353 | });
|
2354 | });
|
2355 | });
|
2356 |
|
2357 | it('should include polymorphic relation - employee', function(done) {
|
2358 | Employee.findOne({include: 'mugshot'},
|
2359 | function(err, employee) {
|
2360 | should.not.exists(err);
|
2361 | const mugshot = employee.mugshot();
|
2362 | should.exist(mugshot);
|
2363 | mugshot.name.should.equal('Mugshot');
|
2364 | done();
|
2365 | });
|
2366 | });
|
2367 |
|
2368 | it('should include inverse polymorphic relation - employee', function(done) {
|
2369 | Picture.findOne({where: {name: 'Mugshot'}, include: 'imageable'},
|
2370 | function(err, pic) {
|
2371 | should.not.exists(err);
|
2372 | const imageable = pic.imageable();
|
2373 | should.exist(imageable);
|
2374 | imageable.should.be.instanceof(Employee);
|
2375 | imageable.name.should.equal('Employee 1');
|
2376 | done();
|
2377 | });
|
2378 | });
|
2379 | });
|
2380 |
|
2381 | describe('polymorphic hasMany', function() {
|
2382 | before(function(done) {
|
2383 | Picture = db.define('Picture', {name: String});
|
2384 | Article = db.define('Article', {name: String});
|
2385 | Employee = db.define('Employee', {name: String});
|
2386 |
|
2387 | db.automigrate(['Picture', 'Article', 'Employee'], done);
|
2388 | });
|
2389 |
|
2390 | it('can be declared with model JSON definition when related model is already attached', function(done) {
|
2391 | const ds = new DataSource('memory');
|
2392 |
|
2393 |
|
2394 |
|
2395 |
|
2396 | const Picture = ds.define('Picture', {name: String}, {relations: {
|
2397 | imageable: {type: 'belongsTo', polymorphic: true},
|
2398 | }});
|
2399 | const Article = ds.define('Article', {name: String}, {relations: {
|
2400 | pictures: {type: 'hasMany', model: 'Picture', polymorphic: 'imageable'},
|
2401 | }});
|
2402 |
|
2403 | assert(Article.relations['pictures']);
|
2404 | assert.deepEqual(Article.relations['pictures'].toJSON(), {
|
2405 | name: 'pictures',
|
2406 | type: 'hasMany',
|
2407 | modelFrom: 'Article',
|
2408 | keyFrom: 'id',
|
2409 | modelTo: 'Picture',
|
2410 | keyTo: 'imageableId',
|
2411 | multiple: true,
|
2412 | polymorphic: {
|
2413 | selector: 'imageable',
|
2414 | foreignKey: 'imageableId',
|
2415 | discriminator: 'imageableType',
|
2416 | },
|
2417 | });
|
2418 |
|
2419 | assert(Picture.relations['imageable']);
|
2420 | assert.deepEqual(Picture.relations['imageable'].toJSON(), {
|
2421 | name: 'imageable',
|
2422 | type: 'belongsTo',
|
2423 | modelFrom: 'Picture',
|
2424 | keyFrom: 'imageableId',
|
2425 | modelTo: '<polymorphic>',
|
2426 | keyTo: 'id',
|
2427 | multiple: false,
|
2428 | polymorphic: {
|
2429 | selector: 'imageable',
|
2430 | foreignKey: 'imageableId',
|
2431 | discriminator: 'imageableType',
|
2432 | },
|
2433 | });
|
2434 | done();
|
2435 | });
|
2436 |
|
2437 | it('can be declared with model JSON definition when related model is not yet attached', function(done) {
|
2438 | const ds = new DataSource('memory');
|
2439 |
|
2440 |
|
2441 |
|
2442 |
|
2443 | const Author = ds.define('Author', {name: String}, {relations: {
|
2444 | pictures: {type: 'hasMany', model: 'Picture', polymorphic: 'imageable'},
|
2445 | }});
|
2446 | const Picture = ds.define('Picture', {name: String}, {relations: {
|
2447 | imageable: {type: 'belongsTo', polymorphic: true},
|
2448 | }});
|
2449 |
|
2450 | assert(Author.relations['pictures']);
|
2451 | assert.deepEqual(Author.relations['pictures'].toJSON(), {
|
2452 | name: 'pictures',
|
2453 | type: 'hasMany',
|
2454 | modelFrom: 'Author',
|
2455 | keyFrom: 'id',
|
2456 | modelTo: 'Picture',
|
2457 | keyTo: 'imageableId',
|
2458 | multiple: true,
|
2459 | polymorphic: {
|
2460 | selector: 'imageable',
|
2461 | foreignKey: 'imageableId',
|
2462 | discriminator: 'imageableType',
|
2463 | },
|
2464 | });
|
2465 |
|
2466 | assert(Picture.relations['imageable']);
|
2467 | assert.deepEqual(Picture.relations['imageable'].toJSON(), {
|
2468 | name: 'imageable',
|
2469 | type: 'belongsTo',
|
2470 | modelFrom: 'Picture',
|
2471 | keyFrom: 'imageableId',
|
2472 | modelTo: '<polymorphic>',
|
2473 | keyTo: 'id',
|
2474 | multiple: false,
|
2475 | polymorphic: {
|
2476 | selector: 'imageable',
|
2477 | foreignKey: 'imageableId',
|
2478 | discriminator: 'imageableType',
|
2479 | },
|
2480 | });
|
2481 | done();
|
2482 | });
|
2483 |
|
2484 | it('can be declared using default polymorphic selector', function(done) {
|
2485 | Article.hasMany(Picture, {polymorphic: 'imageable'});
|
2486 | Employee.hasMany(Picture, {polymorphic: {
|
2487 | foreignKey: 'imageableId',
|
2488 | discriminator: 'imageableType',
|
2489 | }});
|
2490 | Picture.belongsTo('imageable', {polymorphic: true});
|
2491 |
|
2492 | Article.relations['pictures'].toJSON().should.eql({
|
2493 | name: 'pictures',
|
2494 | type: 'hasMany',
|
2495 | modelFrom: 'Article',
|
2496 | keyFrom: 'id',
|
2497 | modelTo: 'Picture',
|
2498 | keyTo: 'imageableId',
|
2499 | multiple: true,
|
2500 | polymorphic: {
|
2501 | selector: 'imageable',
|
2502 | foreignKey: 'imageableId',
|
2503 | discriminator: 'imageableType',
|
2504 | },
|
2505 | });
|
2506 |
|
2507 | Picture.relations['imageable'].toJSON().should.eql({
|
2508 | name: 'imageable',
|
2509 | type: 'belongsTo',
|
2510 | modelFrom: 'Picture',
|
2511 | keyFrom: 'imageableId',
|
2512 | modelTo: '<polymorphic>',
|
2513 | keyTo: 'id',
|
2514 | multiple: false,
|
2515 | polymorphic: {
|
2516 | selector: 'imageable',
|
2517 | foreignKey: 'imageableId',
|
2518 | discriminator: 'imageableType',
|
2519 | },
|
2520 | });
|
2521 |
|
2522 | db.automigrate(['Picture', 'Article', 'Employee'], done);
|
2523 | });
|
2524 |
|
2525 | it('should create polymorphic relation - article', function(done) {
|
2526 | Article.create({name: 'Article 1'}, function(err, article) {
|
2527 | should.not.exists(err);
|
2528 | article.pictures.create({name: 'Article Pic'}, function(err, pics) {
|
2529 | if (err) return done(err);
|
2530 | should.exist(pics);
|
2531 | pics.imageableId.should.eql(article.id);
|
2532 | pics.imageableType.should.equal('Article');
|
2533 | done();
|
2534 | });
|
2535 | });
|
2536 | });
|
2537 |
|
2538 | it('should create polymorphic relation - employee', function(done) {
|
2539 | Employee.create({name: 'Employee 1'}, function(err, employee) {
|
2540 | should.not.exists(err);
|
2541 | employee.pictures.create({name: 'Employee Pic'}, function(err, pics) {
|
2542 | if (err) return done(err);
|
2543 | should.exist(pics);
|
2544 | pics.imageableId.should.eql(employee.id);
|
2545 | pics.imageableType.should.equal('Employee');
|
2546 | done();
|
2547 | });
|
2548 | });
|
2549 | });
|
2550 |
|
2551 | it('should find polymorphic items - article', function(done) {
|
2552 | Article.findOne(function(err, article) {
|
2553 | should.not.exists(err);
|
2554 | if (!article) return done();
|
2555 | article.pictures(function(err, pics) {
|
2556 | if (err) return done(err);
|
2557 |
|
2558 | const pictures = article.pictures();
|
2559 | pictures.should.eql(pics);
|
2560 |
|
2561 | pics.should.have.length(1);
|
2562 | pics[0].name.should.equal('Article Pic');
|
2563 | done();
|
2564 | });
|
2565 | });
|
2566 | });
|
2567 |
|
2568 | it('should find polymorphic items - employee', function(done) {
|
2569 | Employee.findOne(function(err, employee) {
|
2570 | should.not.exists(err);
|
2571 | employee.pictures(function(err, pics) {
|
2572 | if (err) return done(err);
|
2573 | pics.should.have.length(1);
|
2574 | pics[0].name.should.equal('Employee Pic');
|
2575 | done();
|
2576 | });
|
2577 | });
|
2578 | });
|
2579 |
|
2580 | it('should find the inverse of polymorphic relation - article', function(done) {
|
2581 | Picture.findOne({where: {name: 'Article Pic'}}, function(err, pics) {
|
2582 | if (err) return done(err);
|
2583 | pics.imageableType.should.equal('Article');
|
2584 | pics.imageable(function(err, imageable) {
|
2585 | if (err) return done(err);
|
2586 | imageable.should.be.instanceof(Article);
|
2587 | imageable.name.should.equal('Article 1');
|
2588 | done();
|
2589 | });
|
2590 | });
|
2591 | });
|
2592 |
|
2593 | it('should find the inverse of polymorphic relation - employee', function(done) {
|
2594 | Picture.findOne({where: {name: 'Employee Pic'}}, function(err, pics) {
|
2595 | if (err) return done(err);
|
2596 | pics.imageableType.should.equal('Employee');
|
2597 | pics.imageable(function(err, imageable) {
|
2598 | if (err) return done(err);
|
2599 | imageable.should.be.instanceof(Employee);
|
2600 | imageable.name.should.equal('Employee 1');
|
2601 | done();
|
2602 | });
|
2603 | });
|
2604 | });
|
2605 |
|
2606 | bdd.itIf(connectorCapabilities.adhocSort !== false,
|
2607 | 'should include the inverse of polymorphic relation', function(done) {
|
2608 | Picture.find({include: 'imageable'}, function(err, pics) {
|
2609 | if (err) return done(err);
|
2610 | pics.should.have.length(2);
|
2611 |
|
2612 | const actual = pics.map(
|
2613 | function(pic) {
|
2614 | return {imageName: pic.name, name: pic.imageable().name};
|
2615 | },
|
2616 | );
|
2617 |
|
2618 | actual.should.containDeep([
|
2619 | {name: 'Article 1', imageName: 'Article Pic'},
|
2620 | {name: 'Employee 1', imageName: 'Employee Pic'},
|
2621 | ]);
|
2622 |
|
2623 | done();
|
2624 | });
|
2625 | });
|
2626 |
|
2627 | bdd.itIf(connectorCapabilities.adhocSort === false,
|
2628 | 'should include the inverse of polymorphic relation w/o adhocSort', function(done) {
|
2629 | Picture.find({include: 'imageable'}, function(err, pics) {
|
2630 | if (err) return done(err);
|
2631 | pics.should.have.length(2);
|
2632 | const names = ['Article Pic', 'Employee Pic'];
|
2633 | const imageables = ['Article 1', 'Employee 1'];
|
2634 | names.should.containEql(pics[0].name);
|
2635 | names.should.containEql(pics[1].name);
|
2636 | imageables.should.containEql(pics[0].imageable().name);
|
2637 | imageables.should.containEql(pics[1].imageable().name);
|
2638 | done();
|
2639 | });
|
2640 | });
|
2641 |
|
2642 | it('should assign a polymorphic relation', function(done) {
|
2643 | Article.create({name: 'Article 2'}, function(err, article) {
|
2644 | should.not.exists(err);
|
2645 | const p = new Picture({name: 'Sample'});
|
2646 | p.imageable(article);
|
2647 | p.imageableId.should.eql(article.id);
|
2648 | p.imageableType.should.equal('Article');
|
2649 | p.save(done);
|
2650 | });
|
2651 | });
|
2652 |
|
2653 |
|
2654 | it('should find polymorphic items - article', function(done) {
|
2655 | Article.findOne({where: {name: 'Article 2'}}, function(err, article) {
|
2656 | should.not.exists(err);
|
2657 | article.pictures(function(err, pics) {
|
2658 | if (err) return done(err);
|
2659 | pics.should.have.length(1);
|
2660 | pics[0].name.should.equal('Sample');
|
2661 | done();
|
2662 | });
|
2663 | });
|
2664 | });
|
2665 |
|
2666 |
|
2667 | it('should find the inverse of polymorphic relation - article', function(done) {
|
2668 | Picture.findOne({where: {name: 'Sample'}}, function(err, p) {
|
2669 | if (err) return done(err);
|
2670 | p.imageableType.should.equal('Article');
|
2671 | p.imageable(function(err, imageable) {
|
2672 | if (err) return done(err);
|
2673 | imageable.should.be.instanceof(Article);
|
2674 | imageable.name.should.equal('Article 2');
|
2675 | done();
|
2676 | });
|
2677 | });
|
2678 | });
|
2679 |
|
2680 | it('should include the inverse of polymorphic relation - article',
|
2681 | function(done) {
|
2682 | Picture.findOne({where: {name: 'Sample'}, include: 'imageable'},
|
2683 | function(err, p) {
|
2684 | if (err) return done(err);
|
2685 | const imageable = p.imageable();
|
2686 | should.exist(imageable);
|
2687 | imageable.should.be.instanceof(Article);
|
2688 | imageable.name.should.equal('Article 2');
|
2689 | done();
|
2690 | });
|
2691 | });
|
2692 |
|
2693 | it('can be declared using custom foreignKey/discriminator', function(done) {
|
2694 | Article.hasMany(Picture, {polymorphic: {
|
2695 | foreignKey: 'imageId',
|
2696 | discriminator: 'imageType',
|
2697 | }});
|
2698 | Employee.hasMany(Picture, {polymorphic: {
|
2699 | foreignKey: 'imageId',
|
2700 | discriminator: 'imageType',
|
2701 | }});
|
2702 | Picture.belongsTo('imageable', {polymorphic: {
|
2703 | foreignKey: 'imageId',
|
2704 | discriminator: 'imageType',
|
2705 | }});
|
2706 |
|
2707 | Article.relations['pictures'].toJSON().should.eql({
|
2708 | name: 'pictures',
|
2709 | type: 'hasMany',
|
2710 | modelFrom: 'Article',
|
2711 | keyFrom: 'id',
|
2712 | modelTo: 'Picture',
|
2713 | keyTo: 'imageId',
|
2714 | multiple: true,
|
2715 | polymorphic: {
|
2716 | selector: 'pictures',
|
2717 | foreignKey: 'imageId',
|
2718 | discriminator: 'imageType',
|
2719 | },
|
2720 | });
|
2721 |
|
2722 | Picture.relations['imageable'].toJSON().should.eql({
|
2723 | name: 'imageable',
|
2724 | type: 'belongsTo',
|
2725 | modelFrom: 'Picture',
|
2726 | keyFrom: 'imageId',
|
2727 | modelTo: '<polymorphic>',
|
2728 | keyTo: 'id',
|
2729 | multiple: false,
|
2730 | polymorphic: {
|
2731 | selector: 'imageable',
|
2732 | foreignKey: 'imageId',
|
2733 | discriminator: 'imageType',
|
2734 | },
|
2735 | });
|
2736 |
|
2737 | db.automigrate(['Picture', 'Article', 'Employee'], done);
|
2738 | });
|
2739 | });
|
2740 |
|
2741 | describe('polymorphic hasAndBelongsToMany through', function() {
|
2742 | let idArticle, idEmployee;
|
2743 |
|
2744 | before(function(done) {
|
2745 | idArticle = uid.fromConnector(db) || 3456;
|
2746 | idEmployee = uid.fromConnector(db) || 4567;
|
2747 | Picture = db.define('Picture', {name: String});
|
2748 | Article = db.define('Article', {name: String});
|
2749 | Employee = db.define('Employee', {name: String});
|
2750 | PictureLink = db.define('PictureLink', {});
|
2751 |
|
2752 | db.automigrate(['Picture', 'Article', 'Employee', 'PictureLink'], done);
|
2753 | });
|
2754 |
|
2755 | it('can be declared using default polymorphic selector', function(done) {
|
2756 | Article.hasAndBelongsToMany(Picture, {through: PictureLink, polymorphic: 'imageable'});
|
2757 | Employee.hasAndBelongsToMany(Picture, {through: PictureLink, polymorphic: 'imageable'});
|
2758 |
|
2759 | Picture.hasMany(Article, {through: PictureLink, polymorphic: 'imageable', invert: true});
|
2760 | Picture.hasMany(Employee, {through: PictureLink, polymorphic: 'imageable', invert: true});
|
2761 | db.automigrate(['Picture', 'Article', 'Employee', 'PictureLink'], done);
|
2762 | });
|
2763 |
|
2764 | it('can determine the collect via modelTo name', function() {
|
2765 | Article.hasAndBelongsToMany(Picture, {through: PictureLink, polymorphic: 'imageable'});
|
2766 | Employee.hasAndBelongsToMany(Picture, {through: PictureLink, polymorphic: 'imageable'});
|
2767 |
|
2768 | Picture.hasMany(Article, {through: PictureLink, polymorphic: 'imageable', invert: true});
|
2769 | Picture.hasMany(Employee, {through: PictureLink, polymorphic: 'imageable', invert: true});
|
2770 | const article = new Article({id: idArticle});
|
2771 | const scope1 = article.pictures._scope;
|
2772 | scope1.should.have.property('collect', 'picture');
|
2773 | scope1.should.have.property('include', 'picture');
|
2774 | const employee = new Employee({id: idEmployee});
|
2775 | const scope2 = employee.pictures._scope;
|
2776 | scope2.should.have.property('collect', 'picture');
|
2777 | scope2.should.have.property('include', 'picture');
|
2778 | const picture = new Picture({id: idArticle});
|
2779 | const scope3 = picture.articles._scope;
|
2780 | scope3.should.have.property('collect', 'imageable');
|
2781 | scope3.should.have.property('include', 'imageable');
|
2782 | const scope4 = picture.employees._scope;
|
2783 | scope4.should.have.property('collect', 'imageable');
|
2784 | scope4.should.have.property('include', 'imageable');
|
2785 | });
|
2786 |
|
2787 | let article, employee;
|
2788 | const pictures = [];
|
2789 | it('should create polymorphic relation - article', function(done) {
|
2790 | Article.create({name: 'Article 1'}, function(err, a) {
|
2791 | if (err) return done(err);
|
2792 | article = a;
|
2793 | article.pictures.create({name: 'Article Pic 1'}, function(err, pic) {
|
2794 | if (err) return done(err);
|
2795 | pictures.push(pic);
|
2796 | article.pictures.create({name: 'Article Pic 2'}, function(err, pic) {
|
2797 | if (err) return done(err);
|
2798 | pictures.push(pic);
|
2799 | done();
|
2800 | });
|
2801 | });
|
2802 | });
|
2803 | });
|
2804 |
|
2805 | it('should create polymorphic relation - employee', function(done) {
|
2806 | Employee.create({name: 'Employee 1'}, function(err, r) {
|
2807 | if (err) return done(err);
|
2808 | employee = r;
|
2809 | employee.pictures.create({name: 'Employee Pic 1'}, function(err, pic) {
|
2810 | if (err) return done(err);
|
2811 | pictures.push(pic);
|
2812 | done();
|
2813 | });
|
2814 | });
|
2815 | });
|
2816 |
|
2817 | it('should create polymorphic through model', function(done) {
|
2818 | PictureLink.findOne(function(err, link) {
|
2819 | if (err) return done(err);
|
2820 | if (connectorCapabilities.adhocSort !== false) {
|
2821 | link.pictureId.should.eql(pictures[0].id);
|
2822 | link.imageableId.should.eql(article.id);
|
2823 | link.imageableType.should.equal('Article');
|
2824 | link.imageable(function(err, imageable) {
|
2825 | imageable.should.be.instanceof(Article);
|
2826 | imageable.id.should.eql(article.id);
|
2827 | done();
|
2828 | });
|
2829 | } else {
|
2830 | const picIds = pictures.map(pic => pic.id.toString());
|
2831 | picIds.should.containEql(link.pictureId.toString());
|
2832 | link.imageableType.should.be.oneOf('Article', 'Employee');
|
2833 | link.imageable(function(err, imageable) {
|
2834 | imageable.id.should.be.oneOf(article.id, employee.id);
|
2835 | done();
|
2836 | });
|
2837 | }
|
2838 | });
|
2839 | });
|
2840 |
|
2841 | it('should get polymorphic relation through model - article', function(done) {
|
2842 | if (!article) return done();
|
2843 | Article.findById(article.id, function(err, article) {
|
2844 | if (err) return done(err);
|
2845 | article.name.should.equal('Article 1');
|
2846 | article.pictures(function(err, pics) {
|
2847 | if (err) return done(err);
|
2848 | pics.should.have.length(2);
|
2849 | const names = pics.map(p => p.name);
|
2850 | const expected = ['Article Pic 1', 'Article Pic 2'];
|
2851 | if (connectorCapabilities.adhocSort !== false) {
|
2852 | names.should.eql(expected);
|
2853 | } else {
|
2854 | names.should.containDeep(expected);
|
2855 | }
|
2856 | done();
|
2857 | });
|
2858 | });
|
2859 | });
|
2860 |
|
2861 | it('should get polymorphic relation through model - employee', function(done) {
|
2862 | Employee.findById(employee.id, function(err, employee) {
|
2863 | if (err) return done(err);
|
2864 | employee.name.should.equal('Employee 1');
|
2865 | employee.pictures(function(err, pics) {
|
2866 | if (err) return done(err);
|
2867 | pics.should.have.length(1);
|
2868 | pics[0].name.should.equal('Employee Pic 1');
|
2869 | done();
|
2870 | });
|
2871 | });
|
2872 | });
|
2873 |
|
2874 | it('should include polymorphic items', function(done) {
|
2875 | Article.find({include: 'pictures'}, function(err, articles) {
|
2876 | articles.should.have.length(1);
|
2877 | if (!articles) return done();
|
2878 | articles[0].pictures(function(err, pics) {
|
2879 | pics.should.have.length(2);
|
2880 | const names = pics.map(p => p.name);
|
2881 | const expected = ['Article Pic 1', 'Article Pic 2'];
|
2882 | if (connectorCapabilities.adhocSort !== false) {
|
2883 | names.should.eql(expected);
|
2884 | } else {
|
2885 | names.should.containDeep(expected);
|
2886 | }
|
2887 | done();
|
2888 | });
|
2889 | });
|
2890 | });
|
2891 |
|
2892 | let anotherPicture;
|
2893 | it('should add to a polymorphic relation - article', function(done) {
|
2894 | if (!article) return done();
|
2895 | Article.findById(article.id, function(err, article) {
|
2896 | Picture.create({name: 'Example'}, function(err, pic) {
|
2897 | if (err) return done(err);
|
2898 | pictures.push(pic);
|
2899 | anotherPicture = pic;
|
2900 | article.pictures.add(pic, function(err, link) {
|
2901 | link.should.be.instanceof(PictureLink);
|
2902 | link.pictureId.should.eql(pic.id);
|
2903 | link.imageableId.should.eql(article.id);
|
2904 | link.imageableType.should.equal('Article');
|
2905 | done();
|
2906 | });
|
2907 | });
|
2908 | });
|
2909 | });
|
2910 |
|
2911 |
|
2912 | it('should create polymorphic through model', function(done) {
|
2913 | if (!anotherPicture) return done();
|
2914 | PictureLink.findOne({where: {pictureId: anotherPicture.id, imageableType: 'Article'}},
|
2915 | function(err, link) {
|
2916 | if (err) return done(err);
|
2917 | link.pictureId.toString().should.eql(anotherPicture.id.toString());
|
2918 | link.imageableId.toString().should.eql(article.id.toString());
|
2919 | link.imageableType.should.equal('Article');
|
2920 | done();
|
2921 | });
|
2922 | });
|
2923 |
|
2924 | let anotherArticle, anotherEmployee;
|
2925 |
|
2926 | it('should add to a polymorphic relation - article', function(done) {
|
2927 | Article.create({name: 'Article 2'}, function(err, article) {
|
2928 | if (err) return done(err);
|
2929 | anotherArticle = article;
|
2930 | if (!anotherPicture) return done();
|
2931 | article.pictures.add(anotherPicture.id, function(err, pic) {
|
2932 | if (err) return done(err);
|
2933 | done();
|
2934 | });
|
2935 | });
|
2936 | });
|
2937 |
|
2938 |
|
2939 | it('should add to a polymorphic relation - article', function(done) {
|
2940 | Employee.create({name: 'Employee 2'}, function(err, reader) {
|
2941 | if (err) return done(err);
|
2942 | anotherEmployee = reader;
|
2943 | if (!anotherPicture) return done();
|
2944 | reader.pictures.add(anotherPicture.id, function(err, pic) {
|
2945 | if (err) return done(err);
|
2946 | done();
|
2947 | });
|
2948 | });
|
2949 | });
|
2950 |
|
2951 | it('should get the inverse polymorphic relation - article', function(done) {
|
2952 | if (!anotherPicture) return done();
|
2953 | Picture.findById(anotherPicture.id, function(err, pic) {
|
2954 | pic.articles(function(err, articles) {
|
2955 | articles.should.have.length(2);
|
2956 | const names = articles.map(pic => pic.name);
|
2957 | const expected = ['Article 1', 'Article 2'];
|
2958 | if (connectorCapabilities.adhocSort !== false) {
|
2959 | names.should.eql(expected);
|
2960 | } else {
|
2961 | names.should.containDeep(expected);
|
2962 | }
|
2963 | done();
|
2964 | });
|
2965 | });
|
2966 | });
|
2967 |
|
2968 | it('should get the inverse polymorphic relation - reader', function(done) {
|
2969 | if (!anotherPicture) return done();
|
2970 | Picture.findById(anotherPicture.id, function(err, pic) {
|
2971 | pic.employees(function(err, employees) {
|
2972 | employees.should.have.length(1);
|
2973 | if (connectorCapabilities.adhocSort !== false) {
|
2974 | employees[0].name.should.equal('Employee 2');
|
2975 | } else {
|
2976 | const employeeNames = ['Employee 1', 'Employee 2'];
|
2977 | employees[0].name.should.be.oneOf(employeeNames);
|
2978 | }
|
2979 | done();
|
2980 | });
|
2981 | });
|
2982 | });
|
2983 |
|
2984 | it('should find polymorphic items - article', function(done) {
|
2985 | if (!article) return done();
|
2986 | Article.findById(article.id, function(err, article) {
|
2987 | article.pictures(function(err, pics) {
|
2988 | pics.should.have.length(3);
|
2989 | const names = pics.map(pic => pic.name);
|
2990 | const expected = ['Article Pic 1', 'Article Pic 2', 'Example'];
|
2991 | if (connectorCapabilities.adhocSort !== false) {
|
2992 | names.should.eql(expected);
|
2993 | } else {
|
2994 | names.should.containDeep(expected);
|
2995 | }
|
2996 | done();
|
2997 | });
|
2998 | });
|
2999 | });
|
3000 |
|
3001 | it('should check if polymorphic relation exists - article', function(done) {
|
3002 | if (!article) return done();
|
3003 | Article.findById(article.id, function(err, article) {
|
3004 | article.pictures.exists(anotherPicture.id, function(err, exists) {
|
3005 | exists.should.be.true;
|
3006 | done();
|
3007 | });
|
3008 | });
|
3009 | });
|
3010 |
|
3011 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
3012 | 'should remove from a polymorphic relation - article', function(done) {
|
3013 | if (!article || !anotherPicture) return done();
|
3014 | Article.findById(article.id, function(err, article) {
|
3015 | article.pictures.remove(anotherPicture.id, function(err) {
|
3016 | if (err) return done(err);
|
3017 | done();
|
3018 | });
|
3019 | });
|
3020 | });
|
3021 |
|
3022 | bdd.itIf(connectorCapabilities.cloudantCompatible !== false,
|
3023 | 'should find polymorphic items - article', function(done) {
|
3024 | if (!article) return done();
|
3025 | Article.findById(article.id, function(err, article) {
|
3026 | article.pictures(function(err, pics) {
|
3027 |
|
3028 |
|
3029 | const expectedLength = connectorCapabilities.deleteWithOtherThanId !== false ?
|
3030 | 2 : 3;
|
3031 | pics.should.have.length(expectedLength);
|
3032 |
|
3033 | const names = pics.map(p => p.name);
|
3034 | if (connectorCapabilities.adhocSort !== false) {
|
3035 | names.should.eql(['Article Pic 1', 'Article Pic 2']);
|
3036 | } else {
|
3037 | names.should.containDeep(['Article Pic 1', 'Article Pic 2', 'Example']);
|
3038 | }
|
3039 | done();
|
3040 | });
|
3041 | });
|
3042 | });
|
3043 |
|
3044 |
|
3045 | it('should check if polymorphic relation exists - article', function(done) {
|
3046 | if (!article) return done();
|
3047 | Article.findById(article.id, function(err, article) {
|
3048 | article.pictures.exists(7, function(err, exists) {
|
3049 | exists.should.be.false;
|
3050 | done();
|
3051 | });
|
3052 | });
|
3053 | });
|
3054 |
|
3055 | it('should create polymorphic item through relation scope', function(done) {
|
3056 | if (!anotherPicture) return done();
|
3057 | Picture.findById(anotherPicture.id, function(err, pic) {
|
3058 | pic.articles.create({name: 'Article 3'}, function(err, prd) {
|
3059 | if (err) return done(err);
|
3060 | article = prd;
|
3061 | should.equal(article.name, 'Article 3');
|
3062 | done();
|
3063 | });
|
3064 | });
|
3065 | });
|
3066 |
|
3067 | it('should create polymorphic through model - new article', function(done) {
|
3068 | if (!article || !anotherPicture) return done();
|
3069 | PictureLink.findOne({where: {
|
3070 | pictureId: anotherPicture.id, imageableId: article.id, imageableType: 'Article',
|
3071 | }}, function(err, link) {
|
3072 | if (err) return done(err);
|
3073 | link.pictureId.toString().should.eql(anotherPicture.id.toString());
|
3074 | link.imageableId.toString().should.eql(article.id.toString());
|
3075 | link.imageableType.should.equal('Article');
|
3076 | done();
|
3077 | });
|
3078 | });
|
3079 |
|
3080 | it('should find polymorphic items - new article', function(done) {
|
3081 | if (!article) return done();
|
3082 | Article.findById(article.id, function(err, article) {
|
3083 | article.pictures(function(err, pics) {
|
3084 | pics.should.have.length(1);
|
3085 | pics[0].id.should.eql(anotherPicture.id);
|
3086 | pics[0].name.should.equal('Example');
|
3087 | done();
|
3088 | });
|
3089 | });
|
3090 | });
|
3091 |
|
3092 | it('should use author_pictures as modelThrough', function(done) {
|
3093 | Article.hasAndBelongsToMany(Picture, {throughTable: 'article_pictures'});
|
3094 | Article.relations['pictures'].toJSON().should.eql({
|
3095 | name: 'pictures',
|
3096 | type: 'hasMany',
|
3097 | modelFrom: 'Article',
|
3098 | keyFrom: 'id',
|
3099 | modelTo: 'Picture',
|
3100 | keyTo: 'articleId',
|
3101 | multiple: true,
|
3102 | modelThrough: 'article_pictures',
|
3103 | keyThrough: 'pictureId',
|
3104 | });
|
3105 | done();
|
3106 | });
|
3107 |
|
3108 | it('can be declared using custom foreignKey/discriminator', function(done) {
|
3109 | Article.hasAndBelongsToMany(Picture, {through: PictureLink, polymorphic: {
|
3110 | foreignKey: 'imageId',
|
3111 | discriminator: 'imageType',
|
3112 | }});
|
3113 | Employee.hasAndBelongsToMany(Picture, {through: PictureLink, polymorphic: {
|
3114 | foreignKey: 'imageId',
|
3115 | discriminator: 'imageType',
|
3116 | }});
|
3117 |
|
3118 | Picture.hasMany(Article, {through: PictureLink, polymorphic: {
|
3119 | foreignKey: 'imageId',
|
3120 | discriminator: 'imageType',
|
3121 | }, invert: true});
|
3122 | Picture.hasMany(Employee, {through: PictureLink, polymorphic: {
|
3123 | foreignKey: 'imageId',
|
3124 | discriminator: 'imageType',
|
3125 | }, invert: true});
|
3126 |
|
3127 | Article.relations['pictures'].toJSON().should.eql({
|
3128 | name: 'pictures',
|
3129 | type: 'hasMany',
|
3130 | modelFrom: 'Article',
|
3131 | keyFrom: 'id',
|
3132 | modelTo: 'Picture',
|
3133 | keyTo: 'imageId',
|
3134 | multiple: true,
|
3135 | modelThrough: 'PictureLink',
|
3136 | keyThrough: 'pictureId',
|
3137 | polymorphic: {
|
3138 | selector: 'pictures',
|
3139 | foreignKey: 'imageId',
|
3140 | discriminator: 'imageType',
|
3141 | },
|
3142 | });
|
3143 |
|
3144 | Picture.relations['articles'].toJSON().should.eql({
|
3145 | name: 'articles',
|
3146 | type: 'hasMany',
|
3147 | modelFrom: 'Picture',
|
3148 | keyFrom: 'id',
|
3149 | modelTo: 'Article',
|
3150 | keyTo: 'pictureId',
|
3151 | multiple: true,
|
3152 | modelThrough: 'PictureLink',
|
3153 | keyThrough: 'imageId',
|
3154 | polymorphic: {
|
3155 | foreignKey: 'imageId',
|
3156 | discriminator: 'imageType',
|
3157 | selector: 'articles',
|
3158 | invert: true,
|
3159 | },
|
3160 | });
|
3161 |
|
3162 | db.automigrate(['Picture', 'Article', 'Employee', 'PictureLink'], done);
|
3163 | });
|
3164 | });
|
3165 |
|
3166 | describe('belongsTo', function() {
|
3167 | let List, Item, Fear, Mind;
|
3168 |
|
3169 | let listId, itemId;
|
3170 |
|
3171 | it('can be declared in different ways', function() {
|
3172 | List = db.define('List', {name: String});
|
3173 | Item = db.define('Item', {name: String});
|
3174 | Fear = db.define('Fear');
|
3175 | Mind = db.define('Mind');
|
3176 |
|
3177 |
|
3178 | Item.belongsTo(List);
|
3179 | Object.keys((new Item).toObject()).should.containEql('listId');
|
3180 | (new Item).list.should.be.an.instanceOf(Function);
|
3181 |
|
3182 |
|
3183 | Fear.belongsTo('mind', {
|
3184 | methods: {check: function() { return true; }},
|
3185 | });
|
3186 |
|
3187 | Object.keys((new Fear).toObject()).should.containEql('mindId');
|
3188 | (new Fear).mind.should.be.an.instanceOf(Function);
|
3189 |
|
3190 | });
|
3191 |
|
3192 | it('should setup a custom method on accessor', function() {
|
3193 | const rel = Fear.relations['mind'];
|
3194 | rel.defineMethod('other', function() {
|
3195 | return true;
|
3196 | });
|
3197 | });
|
3198 |
|
3199 | it('should have setup a custom method on accessor', function() {
|
3200 | const f = new Fear();
|
3201 | f.mind.check.should.be.a.function;
|
3202 | f.mind.check().should.be.true;
|
3203 | f.mind.other.should.be.a.function;
|
3204 | f.mind.other().should.be.true;
|
3205 | });
|
3206 |
|
3207 | it('can be used to query data', function(done) {
|
3208 | List.hasMany('todos', {model: Item});
|
3209 | db.automigrate(['List', 'Item', 'Fear', 'Mind'], function() {
|
3210 | List.create({name: 'List 1'}, function(e, list) {
|
3211 | listId = list.id;
|
3212 | should.not.exist(e);
|
3213 | should.exist(list);
|
3214 | list.todos.create({name: 'Item 1'}, function(err, todo) {
|
3215 | itemId = todo.id;
|
3216 | todo.list(function(e, l) {
|
3217 | should.not.exist(e);
|
3218 | should.exist(l);
|
3219 | l.should.be.an.instanceOf(List);
|
3220 | todo.list().id.should.eql(l.id);
|
3221 | todo.list().name.should.equal('List 1');
|
3222 | done();
|
3223 | });
|
3224 | });
|
3225 | });
|
3226 | });
|
3227 | });
|
3228 |
|
3229 | it('can be used to query data with get() with callback', function(done) {
|
3230 | List.hasMany('todos', {model: Item});
|
3231 | db.automigrate(['List', 'Item', 'Fear', 'Find'], function() {
|
3232 | List.create({name: 'List 1'}, function(e, list) {
|
3233 | listId = list.id;
|
3234 | should.not.exist(e);
|
3235 | should.exist(list);
|
3236 | list.todos.create({name: 'Item 1'}, function(err, todo) {
|
3237 | itemId = todo.id;
|
3238 | todo.list.get(function(e, l) {
|
3239 | should.not.exist(e);
|
3240 | should.exist(l);
|
3241 | l.should.be.an.instanceOf(List);
|
3242 | todo.list().id.should.eql(l.id);
|
3243 | todo.list().name.should.equal('List 1');
|
3244 | done();
|
3245 | });
|
3246 | });
|
3247 | });
|
3248 | });
|
3249 | });
|
3250 |
|
3251 | it('can be used to query data with promises', function(done) {
|
3252 | List.hasMany('todos', {model: Item});
|
3253 | db.automigrate(['List', 'Item', 'Fear', 'Find'], function() {
|
3254 | List.create({name: 'List 1'})
|
3255 | .then(function(list) {
|
3256 | listId = list.id;
|
3257 | should.exist(list);
|
3258 | return list.todos.create({name: 'Item 1'});
|
3259 | })
|
3260 | .then(function(todo) {
|
3261 | itemId = todo.id;
|
3262 | return todo.list.get()
|
3263 | .then(function(l) {
|
3264 | should.exist(l);
|
3265 | l.should.be.an.instanceOf(List);
|
3266 | todo.list().id.should.eql(l.id);
|
3267 | todo.list().name.should.equal('List 1');
|
3268 | done();
|
3269 | });
|
3270 | })
|
3271 | .catch(done);
|
3272 | });
|
3273 | });
|
3274 |
|
3275 | it('could accept objects when creating on scope', function(done) {
|
3276 | List.create(function(e, list) {
|
3277 | should.not.exist(e);
|
3278 | should.exist(list);
|
3279 | Item.create({list: list}, function(err, item) {
|
3280 | if (err) return done(err);
|
3281 | should.exist(item);
|
3282 | should.exist(item.listId);
|
3283 | item.listId.should.eql(list.id);
|
3284 | item.__cachedRelations.list.should.equal(list);
|
3285 | done();
|
3286 | });
|
3287 | });
|
3288 | });
|
3289 |
|
3290 | it('should update related item on scope', function(done) {
|
3291 | Item.findById(itemId, function(e, todo) {
|
3292 | todo.list.update({name: 'List A'}, function(err, list) {
|
3293 | if (err) return done(err);
|
3294 | should.exist(list);
|
3295 | list.name.should.equal('List A');
|
3296 | done();
|
3297 | });
|
3298 | });
|
3299 | });
|
3300 |
|
3301 | it('should not update related item FK on scope', function(done) {
|
3302 | Item.findById(itemId, function(e, todo) {
|
3303 | if (e) return done(e);
|
3304 | todo.list.update({id: 10}, function(err, list) {
|
3305 | should.exist(err);
|
3306 | err.message.should.startWith('Cannot override foreign key');
|
3307 | done();
|
3308 | });
|
3309 | });
|
3310 | });
|
3311 |
|
3312 | it('should get related item on scope', function(done) {
|
3313 | Item.findById(itemId, function(e, todo) {
|
3314 | todo.list(function(err, list) {
|
3315 | if (err) return done(err);
|
3316 | should.exist(list);
|
3317 | list.name.should.equal('List A');
|
3318 | done();
|
3319 | });
|
3320 | });
|
3321 | });
|
3322 |
|
3323 | it('should destroy related item on scope', function(done) {
|
3324 | Item.findById(itemId, function(e, todo) {
|
3325 | todo.list.destroy(function(err) {
|
3326 | if (err) return done(err);
|
3327 | done();
|
3328 | });
|
3329 | });
|
3330 | });
|
3331 |
|
3332 | it('should get related item on scope - verify', function(done) {
|
3333 | Item.findById(itemId, function(e, todo) {
|
3334 | todo.list(function(err, list) {
|
3335 | if (err) return done(err);
|
3336 | should.not.exist(list);
|
3337 | done();
|
3338 | });
|
3339 | });
|
3340 | });
|
3341 |
|
3342 | it('should not have deleted related item', function(done) {
|
3343 | List.findById(listId, function(e, list) {
|
3344 | should.not.exist(e);
|
3345 | should.exist(list);
|
3346 | done();
|
3347 | });
|
3348 | });
|
3349 |
|
3350 | it('should allow to create belongsTo model in beforeCreate hook', function(done) {
|
3351 | let mind;
|
3352 | Fear.beforeCreate = function(next) {
|
3353 | this.mind.create(function(err, m) {
|
3354 | mind = m;
|
3355 | if (err) next(err); else next();
|
3356 | });
|
3357 | };
|
3358 | Fear.create(function(err, fear) {
|
3359 | should.not.exists(err);
|
3360 | should.exists(fear);
|
3361 | fear.mindId.should.eql(mind.id);
|
3362 | should.exists(fear.mind());
|
3363 | done();
|
3364 | });
|
3365 | });
|
3366 |
|
3367 | it('should allow to create belongsTo model in beforeCreate hook with promises', function(done) {
|
3368 | let mind;
|
3369 | Fear.beforeCreate = function(next) {
|
3370 | this.mind.create()
|
3371 | .then(function(m) {
|
3372 | mind = m;
|
3373 | next();
|
3374 | }).catch(next);
|
3375 | };
|
3376 | Fear.create()
|
3377 | .then(function(fear) {
|
3378 | should.exists(fear);
|
3379 | fear.mindId.should.eql(mind.id);
|
3380 | should.exists(fear.mind());
|
3381 | done();
|
3382 | }).catch(done);
|
3383 | });
|
3384 | });
|
3385 |
|
3386 | describe('belongsTo with scope', function() {
|
3387 | let Person, Passport;
|
3388 |
|
3389 | it('can be declared with scope and properties', function(done) {
|
3390 | Person = db.define('Person', {name: String, age: Number, passportNotes: String});
|
3391 | Passport = db.define('Passport', {name: String, notes: String});
|
3392 | Passport.belongsTo(Person, {
|
3393 | properties: {notes: 'passportNotes'},
|
3394 | scope: {fields: {id: true, name: true}},
|
3395 | });
|
3396 | db.automigrate(['Person', 'Passport'], done);
|
3397 | });
|
3398 |
|
3399 | let personCreated;
|
3400 | it('should create record on scope', function(done) {
|
3401 | const p = new Passport({name: 'Passport', notes: 'Some notes...'});
|
3402 | p.person.create({name: 'Fred', age: 36}, function(err, person) {
|
3403 | personCreated = person;
|
3404 | p.personId.toString().should.eql(person.id.toString());
|
3405 | person.name.should.equal('Fred');
|
3406 | person.passportNotes.should.equal('Some notes...');
|
3407 | p.save(function(err, passport) {
|
3408 | should.not.exists(err);
|
3409 | done();
|
3410 | });
|
3411 | });
|
3412 | });
|
3413 |
|
3414 | it('should find record on scope', function(done) {
|
3415 | Passport.findOne(function(err, p) {
|
3416 | p.personId.toString().should.eql(personCreated.id.toString());
|
3417 | p.person(function(err, person) {
|
3418 | person.name.should.equal('Fred');
|
3419 | person.should.have.property('age', undefined);
|
3420 | person.should.have.property('passportNotes', undefined);
|
3421 | done();
|
3422 | });
|
3423 | });
|
3424 | });
|
3425 |
|
3426 | it('should create record on scope with promises', function(done) {
|
3427 | const p = new Passport({name: 'Passport', notes: 'Some notes...'});
|
3428 | p.person.create({name: 'Fred', age: 36})
|
3429 | .then(function(person) {
|
3430 | p.personId.should.eql(person.id);
|
3431 | person.name.should.equal('Fred');
|
3432 | person.passportNotes.should.equal('Some notes...');
|
3433 | return p.save();
|
3434 | })
|
3435 | .then(function(passport) {
|
3436 | done();
|
3437 | })
|
3438 | .catch(done);
|
3439 | });
|
3440 |
|
3441 | it('should find record on scope with promises', function(done) {
|
3442 | Passport.findOne()
|
3443 | .then(function(p) {
|
3444 | if (connectorCapabilities.adhocSort !== false) {
|
3445 |
|
3446 |
|
3447 | p.personId.should.eql(personCreated.id);
|
3448 | }
|
3449 | return p.person.get();
|
3450 | })
|
3451 | .then(function(person) {
|
3452 | person.name.should.equal('Fred');
|
3453 | person.should.have.property('age', undefined);
|
3454 | person.should.have.property('passportNotes', undefined);
|
3455 | done();
|
3456 | })
|
3457 | .catch(done);
|
3458 | });
|
3459 | });
|
3460 |
|
3461 |
|
3462 |
|
3463 |
|
3464 | describe.skip('belongsTo with embed', function() {
|
3465 | let Person, Passport;
|
3466 |
|
3467 | it('can be declared with embed and properties', function(done) {
|
3468 | Person = db.define('Person', {name: String, age: Number});
|
3469 | Passport = db.define('Passport', {name: String, notes: String});
|
3470 | Passport.belongsTo(Person, {
|
3471 | properties: ['name'],
|
3472 | options: {embedsProperties: true, invertProperties: true},
|
3473 | });
|
3474 | db.automigrate(['Person', 'Passport'], done);
|
3475 | });
|
3476 |
|
3477 | it('should create record with embedded data', function(done) {
|
3478 | Person.create({name: 'Fred', age: 36}, function(err, person) {
|
3479 | const p = new Passport({name: 'Passport', notes: 'Some notes...'});
|
3480 | p.person(person);
|
3481 | p.personId.should.eql(person.id);
|
3482 | const data = p.toObject(true);
|
3483 | data.person.id.should.eql(person.id);
|
3484 | data.person.name.should.equal('Fred');
|
3485 | p.save(function(err) {
|
3486 | should.not.exists(err);
|
3487 | done();
|
3488 | });
|
3489 | });
|
3490 | });
|
3491 |
|
3492 | it('should find record with embedded data', function(done) {
|
3493 | Passport.findOne(function(err, p) {
|
3494 | should.not.exists(err);
|
3495 | const data = p.toObject(true);
|
3496 | data.person.id.should.eql(p.personId);
|
3497 | data.person.name.should.equal('Fred');
|
3498 | done();
|
3499 | });
|
3500 | });
|
3501 |
|
3502 | it('should find record with embedded data with promises', function(done) {
|
3503 | Passport.findOne()
|
3504 | .then(function(p) {
|
3505 | const data = p.toObject(true);
|
3506 | data.person.id.should.eql(p.personId);
|
3507 | data.person.name.should.equal('Fred');
|
3508 | done();
|
3509 | }).catch(done);
|
3510 | });
|
3511 | });
|
3512 |
|
3513 | describe('hasOne', function() {
|
3514 | let Supplier, Account;
|
3515 | let supplierId, accountId;
|
3516 |
|
3517 | before(function() {
|
3518 | Supplier = db.define('Supplier', {name: String});
|
3519 | Account = db.define('Account', {accountNo: String, supplierName: String});
|
3520 | });
|
3521 |
|
3522 | it('can be declared using hasOne method', function() {
|
3523 | Supplier.hasOne(Account, {
|
3524 | properties: {name: 'supplierName'},
|
3525 | methods: {check: function() { return true; }},
|
3526 | });
|
3527 | Object.keys((new Account()).toObject()).should.containEql('supplierId');
|
3528 | (new Supplier()).account.should.be.an.instanceOf(Function);
|
3529 | });
|
3530 |
|
3531 | it('should setup a custom method on accessor', function() {
|
3532 | const rel = Supplier.relations['account'];
|
3533 | rel.defineMethod('other', function() {
|
3534 | return true;
|
3535 | });
|
3536 | });
|
3537 |
|
3538 | it('should have setup a custom method on accessor', function() {
|
3539 | const s = new Supplier();
|
3540 | s.account.check.should.be.a.function;
|
3541 | s.account.check().should.be.true;
|
3542 | s.account.other.should.be.a.function;
|
3543 | s.account.other().should.be.true;
|
3544 | });
|
3545 |
|
3546 | it('can be used to query data', function(done) {
|
3547 | db.automigrate(['Supplier', 'Account'], function() {
|
3548 | Supplier.create({name: 'Supplier 1'}, function(e, supplier) {
|
3549 | supplierId = supplier.id;
|
3550 | should.not.exist(e);
|
3551 | should.exist(supplier);
|
3552 | supplier.account.create({accountNo: 'a01'}, function(err, account) {
|
3553 | supplier.account(function(e, act) {
|
3554 | accountId = act.id;
|
3555 | should.not.exist(e);
|
3556 | should.exist(act);
|
3557 | act.should.be.an.instanceOf(Account);
|
3558 | supplier.account().id.should.eql(act.id);
|
3559 | act.supplierName.should.equal(supplier.name);
|
3560 | done();
|
3561 | });
|
3562 | });
|
3563 | });
|
3564 | });
|
3565 | });
|
3566 |
|
3567 | it('can be used to query data with get() with callback', function(done) {
|
3568 | db.automigrate(['Supplier', 'Account'], function() {
|
3569 | Supplier.create({name: 'Supplier 1'}, function(e, supplier) {
|
3570 | supplierId = supplier.id;
|
3571 | should.not.exist(e);
|
3572 | should.exist(supplier);
|
3573 | supplier.account.create({accountNo: 'a01'}, function(err, account) {
|
3574 | supplier.account.get(function(e, act) {
|
3575 | accountId = act.id;
|
3576 | should.not.exist(e);
|
3577 | should.exist(act);
|
3578 | act.should.be.an.instanceOf(Account);
|
3579 | supplier.account().id.should.eql(act.id);
|
3580 | act.supplierName.should.equal(supplier.name);
|
3581 | done();
|
3582 | });
|
3583 | });
|
3584 | });
|
3585 | });
|
3586 | });
|
3587 |
|
3588 | it('can be used to query data with promises', function(done) {
|
3589 | db.automigrate(['Supplier', 'Account'], function() {
|
3590 | Supplier.create({name: 'Supplier 1'})
|
3591 | .then(function(supplier) {
|
3592 | supplierId = supplier.id;
|
3593 | should.exist(supplier);
|
3594 | return supplier.account.create({accountNo: 'a01'})
|
3595 | .then(function(account) {
|
3596 | return supplier.account.get();
|
3597 | })
|
3598 | .then(function(act) {
|
3599 | accountId = act.id;
|
3600 | should.exist(act);
|
3601 | act.should.be.an.instanceOf(Account);
|
3602 | supplier.account().id.should.eql(act.id);
|
3603 | act.supplierName.should.equal(supplier.name);
|
3604 | done();
|
3605 | });
|
3606 | })
|
3607 | .catch(done);
|
3608 | });
|
3609 | });
|
3610 |
|
3611 | it('should set targetClass on scope property', function() {
|
3612 | should.equal(Supplier.prototype.account._targetClass, 'Account');
|
3613 | });
|
3614 |
|
3615 | it('should update the related item on scope', function(done) {
|
3616 | Supplier.findById(supplierId, function(e, supplier) {
|
3617 | should.not.exist(e);
|
3618 | should.exist(supplier);
|
3619 | supplier.account.update({supplierName: 'Supplier A'}, function(err, act) {
|
3620 | should.not.exist(e);
|
3621 | act.supplierName.should.equal('Supplier A');
|
3622 | done();
|
3623 | });
|
3624 | });
|
3625 | });
|
3626 |
|
3627 | it('should not update the related item FK on scope', function(done) {
|
3628 | Supplier.findById(supplierId, function(err, supplier) {
|
3629 | if (err) return done(err);
|
3630 | should.exist(supplier);
|
3631 | supplier.account.update({supplierName: 'Supplier A', supplierId: 10}, function(err, acct) {
|
3632 | should.exist(err);
|
3633 | err.message.should.containEql('Cannot override foreign key');
|
3634 | done();
|
3635 | });
|
3636 | });
|
3637 | });
|
3638 |
|
3639 | it('should update the related item on scope with promises', function(done) {
|
3640 | Supplier.findById(supplierId)
|
3641 | .then(function(supplier) {
|
3642 | should.exist(supplier);
|
3643 | return supplier.account.update({supplierName: 'Supplier B'});
|
3644 | })
|
3645 | .then(function(act) {
|
3646 | act.supplierName.should.equal('Supplier B');
|
3647 | done();
|
3648 | })
|
3649 | .catch(done);
|
3650 | });
|
3651 |
|
3652 | it('should error trying to change the foreign key in the update', function(done) {
|
3653 | Supplier.create({name: 'Supplier 2'}, function(e, supplier) {
|
3654 | const sid = supplier.id;
|
3655 | Supplier.findById(supplierId, function(e, supplier) {
|
3656 | should.not.exist(e);
|
3657 | should.exist(supplier);
|
3658 | supplier.account.update({supplierName: 'Supplier A',
|
3659 | supplierId: sid},
|
3660 | function(err, act) {
|
3661 | should.exist(err);
|
3662 | err.message.should.startWith('Cannot override foreign key');
|
3663 | done();
|
3664 | });
|
3665 | });
|
3666 | });
|
3667 | });
|
3668 |
|
3669 | it('should update the related item on scope with same foreign key', function(done) {
|
3670 | Supplier.create({name: 'Supplier 2'}, function(err, supplier) {
|
3671 | Supplier.findById(supplierId, function(err, supplier) {
|
3672 | if (err) return done(err);
|
3673 | should.exist(supplier);
|
3674 | supplier.account.update({supplierName: 'Supplier A',
|
3675 | supplierId: supplierId},
|
3676 | function(err, act) {
|
3677 | if (err) return done(err);
|
3678 | act.supplierName.should.equal('Supplier A');
|
3679 | act.supplierId.toString().should.eql(supplierId.toString());
|
3680 | done();
|
3681 | });
|
3682 | });
|
3683 | });
|
3684 | });
|
3685 |
|
3686 | it('should get the related item on scope', function(done) {
|
3687 | Supplier.findById(supplierId, function(e, supplier) {
|
3688 | should.not.exist(e);
|
3689 | should.exist(supplier);
|
3690 | supplier.account(function(err, act) {
|
3691 | should.not.exist(e);
|
3692 | should.exist(act);
|
3693 | act.supplierName.should.equal('Supplier A');
|
3694 | done();
|
3695 | });
|
3696 | });
|
3697 | });
|
3698 |
|
3699 | it('should get the related item on scope with promises', function(done) {
|
3700 | Supplier.findById(supplierId)
|
3701 | .then(function(supplier) {
|
3702 | should.exist(supplier);
|
3703 | return supplier.account.get();
|
3704 | })
|
3705 | .then(function(act) {
|
3706 | should.exist(act);
|
3707 | act.supplierName.should.equal('Supplier A');
|
3708 | done();
|
3709 | })
|
3710 | .catch(done);
|
3711 | });
|
3712 |
|
3713 | it('should destroy the related item on scope', function(done) {
|
3714 | Supplier.findById(supplierId, function(e, supplier) {
|
3715 | should.not.exist(e);
|
3716 | should.exist(supplier);
|
3717 | supplier.account.destroy(function(err) {
|
3718 | should.not.exist(e);
|
3719 | done();
|
3720 | });
|
3721 | });
|
3722 | });
|
3723 |
|
3724 | it('should destroy the related item on scope with promises', function(done) {
|
3725 | Supplier.findById(supplierId)
|
3726 | .then(function(supplier) {
|
3727 | should.exist(supplier);
|
3728 | return supplier.account.create({accountNo: 'a01'})
|
3729 | .then(function(account) {
|
3730 | return supplier.account.destroy();
|
3731 | })
|
3732 | .then(function(err) {
|
3733 | done();
|
3734 | });
|
3735 | })
|
3736 | .catch(done);
|
3737 | });
|
3738 |
|
3739 | it('should get the related item on scope - verify', function(done) {
|
3740 | Supplier.findById(supplierId, function(e, supplier) {
|
3741 | should.not.exist(e);
|
3742 | should.exist(supplier);
|
3743 | supplier.account(function(err, act) {
|
3744 | should.not.exist(e);
|
3745 | should.not.exist(act);
|
3746 | done();
|
3747 | });
|
3748 | });
|
3749 | });
|
3750 |
|
3751 | it('should get the related item on scope with promises - verify', function(done) {
|
3752 | Supplier.findById(supplierId)
|
3753 | .then(function(supplier) {
|
3754 | should.exist(supplier);
|
3755 | return supplier.account.get();
|
3756 | })
|
3757 | .then(function(act) {
|
3758 | should.not.exist(act);
|
3759 | done();
|
3760 | })
|
3761 | .catch(done);
|
3762 | });
|
3763 |
|
3764 | it('should have deleted related item', function(done) {
|
3765 | Supplier.findById(supplierId, function(e, supplier) {
|
3766 | should.not.exist(e);
|
3767 | should.exist(supplier);
|
3768 | done();
|
3769 | });
|
3770 | });
|
3771 | });
|
3772 |
|
3773 | describe('hasOne with scope', function() {
|
3774 | let Supplier, Account;
|
3775 | let supplierId, accountId;
|
3776 |
|
3777 | before(function() {
|
3778 | Supplier = db.define('Supplier', {name: String});
|
3779 | Account = db.define('Account', {accountNo: String, supplierName: String, block: Boolean});
|
3780 | Supplier.hasOne(Account, {scope: {where: {block: false}}, properties: {name: 'supplierName'}});
|
3781 | });
|
3782 |
|
3783 | it('can be used to query data', function(done) {
|
3784 | db.automigrate(['Supplier', 'Account'], function() {
|
3785 | Supplier.create({name: 'Supplier 1'}, function(e, supplier) {
|
3786 | supplierId = supplier.id;
|
3787 | should.not.exist(e);
|
3788 | should.exist(supplier);
|
3789 | supplier.account.create({accountNo: 'a01', block: false}, function(err, account) {
|
3790 | supplier.account(function(e, act) {
|
3791 | accountId = act.id;
|
3792 | should.not.exist(e);
|
3793 | should.exist(act);
|
3794 | act.should.be.an.instanceOf(Account);
|
3795 | should.exist(act.block);
|
3796 | act.block.should.be.false;
|
3797 | supplier.account().id.should.eql(act.id);
|
3798 | act.supplierName.should.equal(supplier.name);
|
3799 | done();
|
3800 | });
|
3801 | });
|
3802 | });
|
3803 | });
|
3804 | });
|
3805 |
|
3806 | it('should include record that matches scope', function(done) {
|
3807 | Supplier.findById(supplierId, {include: 'account'}, function(err, supplier) {
|
3808 | should.exists(supplier.toJSON().account);
|
3809 | supplier.account(function(err, account) {
|
3810 | should.exists(account);
|
3811 | done();
|
3812 | });
|
3813 | });
|
3814 | });
|
3815 |
|
3816 | bdd.itIf(connectorCapabilities.supportUpdateWithoutId !== false,
|
3817 | 'should not find record that does not match scope', function(done) {
|
3818 | Account.updateAll({block: true}, function(err) {
|
3819 | if (err) return done(err);
|
3820 | Supplier.findById(supplierId, function(err, supplier) {
|
3821 | supplier.account(function(err, account) {
|
3822 | should.not.exists(account);
|
3823 | done();
|
3824 | });
|
3825 | });
|
3826 | });
|
3827 | });
|
3828 |
|
3829 | bdd.itIf(connectorCapabilities.supportUpdateWithoutId !== false,
|
3830 | 'should not include record that does not match scope', function(done) {
|
3831 | Account.updateAll({block: true}, function(err) {
|
3832 | if (err) return done(err);
|
3833 | Supplier.findById(supplierId, {include: 'account'}, function(err, supplier) {
|
3834 | should.not.exists(supplier.toJSON().account);
|
3835 | supplier.account(function(err, account) {
|
3836 | should.not.exists(account);
|
3837 | done();
|
3838 | });
|
3839 | });
|
3840 | });
|
3841 | });
|
3842 |
|
3843 | it('can be used to query data with promises', function(done) {
|
3844 | db.automigrate(['Supplier', 'Account'], function() {
|
3845 | Supplier.create({name: 'Supplier 1'})
|
3846 | .then(function(supplier) {
|
3847 | supplierId = supplier.id;
|
3848 | should.exist(supplier);
|
3849 | return supplier.account.create({accountNo: 'a01', block: false})
|
3850 | .then(function(account) {
|
3851 | return supplier.account.get();
|
3852 | })
|
3853 | .then(function(act) {
|
3854 | accountId = act.id;
|
3855 | should.exist(act);
|
3856 | act.should.be.an.instanceOf(Account);
|
3857 | should.exist(act.block);
|
3858 | act.block.should.be.false;
|
3859 | supplier.account().id.should.eql(act.id);
|
3860 | act.supplierName.should.equal(supplier.name);
|
3861 | done();
|
3862 | });
|
3863 | })
|
3864 | .catch(done);
|
3865 | });
|
3866 | });
|
3867 |
|
3868 | bdd.itIf(connectorCapabilities.supportUpdateWithoutId !== false,
|
3869 | 'should find record that match scope with promises', function(done) {
|
3870 | Account.updateAll({block: true})
|
3871 | .then(function() {
|
3872 | return Supplier.findById(supplierId);
|
3873 | })
|
3874 | .then(function(supplier) {
|
3875 | return supplier.account.get();
|
3876 | })
|
3877 | .then(function(account) {
|
3878 | should.not.exist(account);
|
3879 | done();
|
3880 | })
|
3881 | .catch(function(err) {
|
3882 | done();
|
3883 | });
|
3884 | });
|
3885 | });
|
3886 |
|
3887 | describe('hasOne with non standard id', function() {
|
3888 | let Supplier, Account;
|
3889 | let supplierId, accountId;
|
3890 |
|
3891 | before(function() {
|
3892 | Supplier = db.define('Supplier', {
|
3893 | sid: {
|
3894 | type: String,
|
3895 | id: true,
|
3896 | generated: true,
|
3897 | },
|
3898 | name: String,
|
3899 | });
|
3900 | Account = db.define('Account', {
|
3901 | accid: {
|
3902 | type: String,
|
3903 | id: true,
|
3904 | generated: false,
|
3905 | },
|
3906 | supplierName: String,
|
3907 | });
|
3908 | });
|
3909 |
|
3910 | it('can be declared with non standard foreignKey', function() {
|
3911 | Supplier.hasOne(Account, {
|
3912 | properties: {name: 'supplierName'},
|
3913 | foreignKey: 'sid',
|
3914 | });
|
3915 | Object.keys((new Account()).toObject()).should.containEql('sid');
|
3916 | (new Supplier()).account.should.be.an.instanceOf(Function);
|
3917 | });
|
3918 |
|
3919 | it('can be used to query data', function(done) {
|
3920 | db.automigrate(['Supplier', 'Account'], function() {
|
3921 | Supplier.create({name: 'Supplier 1'}, function(e, supplier) {
|
3922 | supplierId = supplier.sid;
|
3923 | should.not.exist(e);
|
3924 | should.exist(supplier);
|
3925 | supplier.account.create({accid: 'a01'}, function(err, account) {
|
3926 | supplier.account(function(e, act) {
|
3927 | accountId = act.accid;
|
3928 | should.not.exist(e);
|
3929 | should.exist(act);
|
3930 | act.should.be.an.instanceOf(Account);
|
3931 | supplier.account().accid.should.eql(act.accid);
|
3932 | act.supplierName.should.equal(supplier.name);
|
3933 | done();
|
3934 | });
|
3935 | });
|
3936 | });
|
3937 | });
|
3938 | });
|
3939 |
|
3940 | it('should destroy the related item on scope', function(done) {
|
3941 | Supplier.findById(supplierId, function(e, supplier) {
|
3942 | should.not.exist(e);
|
3943 | should.exist(supplier);
|
3944 | supplier.account.destroy(function(err) {
|
3945 | should.not.exist(e);
|
3946 | done();
|
3947 | });
|
3948 | });
|
3949 | });
|
3950 |
|
3951 | bdd.itIf(connectorCapabilities.cloudantCompatible !== false,
|
3952 | 'should get the related item on scope - verify', function(done) {
|
3953 | Supplier.findById(supplierId, function(e, supplier) {
|
3954 | should.not.exist(e);
|
3955 | should.exist(supplier);
|
3956 | supplier.account(function(err, act) {
|
3957 | should.not.exist(e);
|
3958 | should.not.exist(act);
|
3959 | done();
|
3960 | });
|
3961 | });
|
3962 | });
|
3963 |
|
3964 | it('should have deleted related item', function(done) {
|
3965 | Supplier.findById(supplierId, function(e, supplier) {
|
3966 | should.not.exist(e);
|
3967 | should.exist(supplier);
|
3968 | done();
|
3969 | });
|
3970 | });
|
3971 | });
|
3972 |
|
3973 | describe('hasOne with primaryKey different from model PK', function() {
|
3974 | let CompanyBoard, Boss;
|
3975 | let companyBoardId, bossId;
|
3976 |
|
3977 | before(function() {
|
3978 | CompanyBoard = db.define('CompanyBoard', {
|
3979 | membersNumber: Number,
|
3980 | companyId: String,
|
3981 | });
|
3982 | Boss = db.define('Boss', {
|
3983 | id: {type: String, id: true, generated: false},
|
3984 | boardMembersNumber: Number,
|
3985 | companyId: String,
|
3986 | });
|
3987 | });
|
3988 |
|
3989 | it('relation can be declared with primaryKey', function() {
|
3990 | CompanyBoard.hasOne(Boss, {
|
3991 | properties: {membersNumber: 'boardMembersNumber'},
|
3992 | primaryKey: 'companyId',
|
3993 | foreignKey: 'companyId',
|
3994 | });
|
3995 | Object.keys((new Boss()).toObject()).should.containEql('companyId');
|
3996 | (new CompanyBoard()).boss.should.be.an.instanceOf(Function);
|
3997 | });
|
3998 |
|
3999 | it('can be used to query data', function(done) {
|
4000 | db.automigrate(['CompanyBoard', 'Boss'], function() {
|
4001 | CompanyBoard.create({membersNumber: 7, companyId: 'Company1'}, function(e, companyBoard) {
|
4002 | companyBoardId = companyBoard.id;
|
4003 | should.not.exist(e);
|
4004 | should.exist(companyBoard);
|
4005 | companyBoard.boss.create({id: 'bossa01'}, function(err, account) {
|
4006 | companyBoard.boss(function(e, boss) {
|
4007 | bossId = boss.id;
|
4008 | should.not.exist(e);
|
4009 | should.exist(boss);
|
4010 | boss.should.be.an.instanceOf(Boss);
|
4011 | companyBoard.boss().id.should.eql(boss.id);
|
4012 | boss.boardMembersNumber.should.eql(companyBoard.membersNumber);
|
4013 | boss.companyId.should.eql(companyBoard.companyId);
|
4014 | done();
|
4015 | });
|
4016 | });
|
4017 | });
|
4018 | });
|
4019 | });
|
4020 |
|
4021 | it('should destroy the related item on scope', function(done) {
|
4022 | CompanyBoard.findById(companyBoardId, function(e, companyBoard) {
|
4023 | should.not.exist(e);
|
4024 | should.exist(companyBoard);
|
4025 | companyBoard.boss.destroy(function(err) {
|
4026 | should.not.exist(e);
|
4027 | done();
|
4028 | });
|
4029 | });
|
4030 | });
|
4031 |
|
4032 | it('should get the related item on scope - verify', function(done) {
|
4033 | CompanyBoard.findById(companyBoardId, function(e, companyBoard) {
|
4034 | should.not.exist(e);
|
4035 | should.exist(companyBoard);
|
4036 | companyBoard.boss(function(err, act) {
|
4037 | should.not.exist(e);
|
4038 | should.not.exist(act);
|
4039 | done();
|
4040 | });
|
4041 | });
|
4042 | });
|
4043 | });
|
4044 |
|
4045 | describe('hasMany with primaryKey different from model PK', function() {
|
4046 | let Employee, Boss;
|
4047 | const COMPANY_ID = 'Company1';
|
4048 |
|
4049 | before(function() {
|
4050 | Employee = db.define('Employee', {name: String, companyId: String});
|
4051 | Boss = db.define('Boss', {address: String, companyId: String});
|
4052 | });
|
4053 |
|
4054 | it('relation can be declared with primaryKey', function() {
|
4055 | Boss.hasMany(Employee, {
|
4056 | primaryKey: 'companyId',
|
4057 | foreignKey: 'companyId',
|
4058 | });
|
4059 | (new Boss()).employees.should.be.an.instanceOf(Function);
|
4060 | });
|
4061 |
|
4062 | it('can be used to query employees for boss', function() {
|
4063 | return db.automigrate(['Employee', 'Boss']).then(function() {
|
4064 | return Boss.create({address: 'testAddress', companyId: COMPANY_ID})
|
4065 | .then(function(boss) {
|
4066 | should.exist(boss);
|
4067 | should.exist(boss.employees);
|
4068 | return boss.employees.create([{name: 'a01'}, {name: 'a02'}])
|
4069 | .then(function(employees) {
|
4070 | should.exists(employees);
|
4071 | return boss.employees();
|
4072 | }).then(function(employees) {
|
4073 | const employee = employees[0];
|
4074 | should.exist(employee);
|
4075 | employees.length.should.equal(2);
|
4076 | employee.should.be.an.instanceOf(Employee);
|
4077 | employee.companyId.should.eql(boss.companyId);
|
4078 | return employees;
|
4079 | });
|
4080 | });
|
4081 | });
|
4082 | });
|
4083 |
|
4084 | it('can be used to query employees for boss2', function() {
|
4085 | return db.automigrate(['Employee', 'Boss']).then(function() {
|
4086 | return Boss.create({address: 'testAddress', companyId: COMPANY_ID})
|
4087 | .then(function(boss) {
|
4088 | return Employee.create({name: 'a01', companyId: COMPANY_ID})
|
4089 | .then(function(employee) {
|
4090 | should.exist(employee);
|
4091 | return boss.employees.find();
|
4092 | }).then(function(employees) {
|
4093 | should.exists(employees);
|
4094 | employees.length.should.equal(1);
|
4095 | });
|
4096 | });
|
4097 | });
|
4098 | });
|
4099 | });
|
4100 |
|
4101 | describe('belongsTo with primaryKey different from model PK', function() {
|
4102 | let Employee, Boss;
|
4103 | const COMPANY_ID = 'Company1';
|
4104 | let bossId;
|
4105 |
|
4106 | before(function() {
|
4107 | Employee = db.define('Employee', {name: String, companyId: String});
|
4108 | Boss = db.define('Boss', {address: String, companyId: String});
|
4109 | });
|
4110 |
|
4111 | it('relation can be declared with primaryKey', function() {
|
4112 | Employee.belongsTo(Boss, {
|
4113 | primaryKey: 'companyId',
|
4114 | foreignKey: 'companyId',
|
4115 | });
|
4116 | (new Employee()).boss.should.be.an.instanceOf(Function);
|
4117 | });
|
4118 |
|
4119 | it('can be used to query data', function() {
|
4120 | return db.automigrate(['Employee', 'Boss']).then(function() {
|
4121 | return Boss.create({address: 'testAddress', companyId: COMPANY_ID})
|
4122 | .then(function(boss) {
|
4123 | bossId = boss.id;
|
4124 | return Employee.create({name: 'a', companyId: COMPANY_ID});
|
4125 | })
|
4126 | .then(function(employee) {
|
4127 | should.exists(employee);
|
4128 | return employee.boss.get();
|
4129 | })
|
4130 | .then(function(boss) {
|
4131 | should.exists(boss);
|
4132 | boss.id.should.eql(bossId);
|
4133 | });
|
4134 | });
|
4135 | });
|
4136 | });
|
4137 |
|
4138 | describe('hasAndBelongsToMany', function() {
|
4139 | let Article, TagName, ArticleTag;
|
4140 | it('can be declared', function(done) {
|
4141 | Article = db.define('Article', {title: String});
|
4142 | TagName = db.define('TagName', {name: String, flag: String});
|
4143 | Article.hasAndBelongsToMany('tagNames');
|
4144 | ArticleTag = db.models.ArticleTagName;
|
4145 | db.automigrate(['Article', 'TagName', 'ArticleTagName'], done);
|
4146 | });
|
4147 |
|
4148 | it('should allow to create instances on scope', function(done) {
|
4149 | Article.create(function(e, article) {
|
4150 | article.tagNames.create({name: 'popular'}, function(e, t) {
|
4151 | t.should.be.an.instanceOf(TagName);
|
4152 | ArticleTag.findOne(function(e, at) {
|
4153 | should.exist(at);
|
4154 | at.tagNameId.toString().should.eql(t.id.toString());
|
4155 | at.articleId.toString().should.eql(article.id.toString());
|
4156 | done();
|
4157 | });
|
4158 | });
|
4159 | });
|
4160 | });
|
4161 |
|
4162 | it('should allow to fetch scoped instances', function(done) {
|
4163 | Article.findOne(function(e, article) {
|
4164 | article.tagNames(function(e, tags) {
|
4165 | should.not.exist(e);
|
4166 | should.exist(tags);
|
4167 |
|
4168 | article.tagNames().should.eql(tags);
|
4169 |
|
4170 | done();
|
4171 | });
|
4172 | });
|
4173 | });
|
4174 |
|
4175 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
4176 | 'should destroy all related instances', function(done) {
|
4177 | Article.create(function(err, article) {
|
4178 | if (err) return done(err);
|
4179 | article.tagNames.create({name: 'popular'}, function(err, t) {
|
4180 | if (err) return done(err);
|
4181 | article.tagNames.destroyAll(function(err) {
|
4182 | if (err) return done(err);
|
4183 | article.tagNames(true, function(err, list) {
|
4184 | if (err) return done(err);
|
4185 | list.should.have.length(0);
|
4186 | done();
|
4187 | });
|
4188 | });
|
4189 | });
|
4190 | });
|
4191 | });
|
4192 |
|
4193 | it('should allow to add connection with instance', function(done) {
|
4194 | Article.findOne(function(e, article) {
|
4195 | TagName.create({name: 'awesome'}, function(e, tag) {
|
4196 | article.tagNames.add(tag, function(e, at) {
|
4197 | should.not.exist(e);
|
4198 | should.exist(at);
|
4199 | at.should.be.an.instanceOf(ArticleTag);
|
4200 | at.tagNameId.should.eql(tag.id);
|
4201 | at.articleId.should.eql(article.id);
|
4202 | done();
|
4203 | });
|
4204 | });
|
4205 | });
|
4206 | });
|
4207 |
|
4208 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
4209 | 'should allow to remove connection with instance', function(done) {
|
4210 | Article.findOne(function(e, article) {
|
4211 | article.tagNames(function(e, tags) {
|
4212 | const len = tags.length;
|
4213 | tags.should.not.be.empty;
|
4214 | article.tagNames.remove(tags[0], function(e) {
|
4215 | should.not.exist(e);
|
4216 | article.tagNames(true, function(e, tags) {
|
4217 | tags.should.have.lengthOf(len - 1);
|
4218 | done();
|
4219 | });
|
4220 | });
|
4221 | });
|
4222 | });
|
4223 | });
|
4224 |
|
4225 | it('should allow to create instances on scope with promises', function(done) {
|
4226 | db.automigrate(['Article', 'TagName', 'ArticleTagName'], function() {
|
4227 | Article.create()
|
4228 | .then(function(article) {
|
4229 | return article.tagNames.create({name: 'popular'})
|
4230 | .then(function(t) {
|
4231 | t.should.be.an.instanceOf(TagName);
|
4232 | return ArticleTag.findOne()
|
4233 | .then(function(at) {
|
4234 | should.exist(at);
|
4235 | at.tagNameId.toString().should.eql(t.id.toString());
|
4236 | at.articleId.toString().should.eql(article.id.toString());
|
4237 | done();
|
4238 | });
|
4239 | });
|
4240 | }).catch(done);
|
4241 | });
|
4242 | });
|
4243 |
|
4244 | it('should allow to fetch scoped instances with promises', function(done) {
|
4245 | Article.findOne()
|
4246 | .then(function(article) {
|
4247 | return article.tagNames.find()
|
4248 | .then(function(tags) {
|
4249 | should.exist(tags);
|
4250 | article.tagNames().should.eql(tags);
|
4251 | done();
|
4252 | });
|
4253 | }).catch(done);
|
4254 | });
|
4255 |
|
4256 | it('should allow to add connection with instance with promises', function(done) {
|
4257 | Article.findOne()
|
4258 | .then(function(article) {
|
4259 | return TagName.create({name: 'awesome'})
|
4260 | .then(function(tag) {
|
4261 | return article.tagNames.add(tag)
|
4262 | .then(function(at) {
|
4263 | should.exist(at);
|
4264 | at.should.be.an.instanceOf(ArticleTag);
|
4265 | at.tagNameId.should.eql(tag.id);
|
4266 | at.articleId.should.eql(article.id);
|
4267 | done();
|
4268 | });
|
4269 | });
|
4270 | })
|
4271 | .catch(done);
|
4272 | });
|
4273 |
|
4274 | bdd.itIf(connectorCapabilities.deleteWithOtherThanId !== false,
|
4275 | 'should allow to remove connection with instance with promises', function(done) {
|
4276 | Article.findOne()
|
4277 | .then(function(article) {
|
4278 | return article.tagNames.find()
|
4279 | .then(function(tags) {
|
4280 | const len = tags.length;
|
4281 | tags.should.not.be.empty;
|
4282 | return article.tagNames.remove(tags[0])
|
4283 | .then(function() {
|
4284 | return article.tagNames.find();
|
4285 | })
|
4286 | .then(function(tags) {
|
4287 | tags.should.have.lengthOf(len - 1);
|
4288 | done();
|
4289 | });
|
4290 | });
|
4291 | })
|
4292 | .catch(done);
|
4293 | });
|
4294 |
|
4295 | it('should set targetClass on scope property', function() {
|
4296 | should.equal(Article.prototype.tagNames._targetClass, 'TagName');
|
4297 | });
|
4298 |
|
4299 | it('should apply inclusion fields to the target model', function(done) {
|
4300 | Article.create({title: 'a1'}, function(e, article) {
|
4301 | should.not.exist(e);
|
4302 | article.tagNames.create({name: 't1', flag: '1'}, function(e, t) {
|
4303 | should.not.exist(e);
|
4304 | Article.find({
|
4305 | where: {id: article.id},
|
4306 | include: {relation: 'tagNames', scope: {fields: ['name']}}},
|
4307 | function(e, articles) {
|
4308 | should.not.exist(e);
|
4309 | articles.should.have.property('length', 1);
|
4310 | const a = articles[0].toJSON();
|
4311 | a.should.have.property('title', 'a1');
|
4312 | a.should.have.property('tagNames');
|
4313 | a.tagNames.should.have.property('length', 1);
|
4314 | const n = a.tagNames[0];
|
4315 | n.should.have.property('name', 't1');
|
4316 | n.should.have.property('flag', undefined);
|
4317 | n.id.should.eql(t.id);
|
4318 | done();
|
4319 | });
|
4320 | });
|
4321 | });
|
4322 | });
|
4323 |
|
4324 | it('should apply inclusion where to the target model', function(done) {
|
4325 | Article.create({title: 'a2'}, function(e, article) {
|
4326 | should.not.exist(e);
|
4327 | article.tagNames.create({name: 't2', flag: '2'}, function(e, t2) {
|
4328 | should.not.exist(e);
|
4329 | article.tagNames.create({name: 't3', flag: '3'}, function(e, t3) {
|
4330 | Article.find({
|
4331 | where: {id: article.id},
|
4332 | include: {relation: 'tagNames', scope: {where: {flag: '2'}}}},
|
4333 | function(e, articles) {
|
4334 | should.not.exist(e);
|
4335 | articles.should.have.property('length', 1);
|
4336 | const a = articles[0].toJSON();
|
4337 | a.should.have.property('title', 'a2');
|
4338 | a.should.have.property('tagNames');
|
4339 | a.tagNames.should.have.property('length', 1);
|
4340 | const n = a.tagNames[0];
|
4341 | n.should.have.property('name', 't2');
|
4342 | n.should.have.property('flag', '2');
|
4343 | n.id.should.eql(t2.id);
|
4344 | done();
|
4345 | });
|
4346 | });
|
4347 | });
|
4348 | });
|
4349 | });
|
4350 | });
|
4351 |
|
4352 | describe('embedsOne', function() {
|
4353 | let person;
|
4354 | let Passport;
|
4355 | let Other;
|
4356 |
|
4357 | before(function() {
|
4358 | tmp = getTransientDataSource();
|
4359 | Person = db.define('Person', {name: String});
|
4360 | Passport = tmp.define('Passport',
|
4361 | {name: {type: 'string', required: true}},
|
4362 | {idInjection: false});
|
4363 | Address = tmp.define('Address', {street: String}, {idInjection: false});
|
4364 | Other = db.define('Other', {name: String});
|
4365 | Person.embedsOne(Passport, {
|
4366 | default: {name: 'Anonymous'},
|
4367 | methods: {check: function() { return true; }},
|
4368 | options: {
|
4369 | property: {
|
4370 | postgresql: {
|
4371 | columnName: 'passport_item',
|
4372 | },
|
4373 | },
|
4374 | },
|
4375 | });
|
4376 | });
|
4377 |
|
4378 | it('can be declared using embedsOne method', function(done) {
|
4379 | Person.embedsOne(Address);
|
4380 | db.automigrate(['Person'], done);
|
4381 | });
|
4382 |
|
4383 | it('should have setup a property and accessor', function() {
|
4384 | const p = new Person();
|
4385 | p.passport.should.be.an.object;
|
4386 | p.passportItem.should.be.a.function;
|
4387 | p.passportItem.create.should.be.a.function;
|
4388 | p.passportItem.build.should.be.a.function;
|
4389 | p.passportItem.destroy.should.be.a.function;
|
4390 | });
|
4391 |
|
4392 | it('respects property options on the embedded property', function() {
|
4393 | Person.definition.properties.passport.should.have.property('postgresql');
|
4394 | Person.definition.properties.passport.postgresql.should.eql({columnName: 'passport_item'});
|
4395 | });
|
4396 |
|
4397 | it('should setup a custom method on accessor', function() {
|
4398 | const rel = Person.relations['passportItem'];
|
4399 | rel.defineMethod('other', function() {
|
4400 | return true;
|
4401 | });
|
4402 | });
|
4403 |
|
4404 | it('should have setup a custom method on accessor', function() {
|
4405 | const p = new Person();
|
4406 | p.passportItem.check.should.be.a.function;
|
4407 | p.passportItem.check().should.be.true;
|
4408 | p.passportItem.other.should.be.a.function;
|
4409 | p.passportItem.other().should.be.true;
|
4410 | });
|
4411 |
|
4412 | it('should behave properly without default or being set', function(done) {
|
4413 | const p = new Person();
|
4414 | should.not.exist(p.address);
|
4415 | const a = p.addressItem();
|
4416 | should.not.exist(a);
|
4417 | Person.create({}, function(err, p) {
|
4418 | should.not.exist(p.address);
|
4419 | const a = p.addressItem();
|
4420 | should.not.exist(a);
|
4421 | done();
|
4422 | });
|
4423 | });
|
4424 |
|
4425 | it('should return an instance with default values', function() {
|
4426 | const p = new Person();
|
4427 | p.passport.toObject().should.eql({name: 'Anonymous'});
|
4428 | p.passportItem().should.equal(p.passport);
|
4429 | p.passportItem(function(err, passport) {
|
4430 | should.not.exist(err);
|
4431 | passport.should.equal(p.passport);
|
4432 | });
|
4433 | });
|
4434 |
|
4435 | it('should embed a model instance', function() {
|
4436 | const p = new Person();
|
4437 | p.passportItem(new Passport({name: 'Fred'}));
|
4438 | p.passport.toObject().should.eql({name: 'Fred'});
|
4439 | p.passport.should.be.an.instanceOf(Passport);
|
4440 | });
|
4441 |
|
4442 | it('should not embed an invalid model type', function() {
|
4443 | const p = new Person();
|
4444 | p.passportItem(new Other());
|
4445 | p.passport.toObject().should.eql({name: 'Anonymous'});
|
4446 | p.passport.should.be.an.instanceOf(Passport);
|
4447 | });
|
4448 |
|
4449 | let personId;
|
4450 | it('should create an embedded item on scope', function(done) {
|
4451 | Person.create({name: 'Fred'}, function(err, p) {
|
4452 | if (err) return done(err);
|
4453 | personId = p.id;
|
4454 | p.passportItem.create({name: 'Fredric'}, function(err, passport) {
|
4455 | if (err) return done(err);
|
4456 | p.passport.toObject().should.eql({name: 'Fredric'});
|
4457 | p.passport.should.be.an.instanceOf(Passport);
|
4458 | done();
|
4459 | });
|
4460 | });
|
4461 | });
|
4462 |
|
4463 | it('should get an embedded item on scope', function(done) {
|
4464 | Person.findById(personId, function(err, p) {
|
4465 | if (err) return done(err);
|
4466 | const passport = p.passportItem();
|
4467 | passport.toObject().should.eql({name: 'Fredric'});
|
4468 | passport.should.be.an.instanceOf(Passport);
|
4469 | passport.should.equal(p.passport);
|
4470 | passport.should.equal(p.passportItem.value());
|
4471 | done();
|
4472 | });
|
4473 | });
|
4474 |
|
4475 | it('should validate an embedded item on scope - on creation', function(done) {
|
4476 | const p = new Person({name: 'Fred'});
|
4477 | p.passportItem.create({}, function(err, passport) {
|
4478 | should.exist(err);
|
4479 | err.name.should.equal('ValidationError');
|
4480 | err.details.messages.name.should.eql(['can\'t be blank']);
|
4481 | done();
|
4482 | });
|
4483 | });
|
4484 |
|
4485 | it('should validate an embedded item on scope - on update', function(done) {
|
4486 | Person.findById(personId, function(err, p) {
|
4487 | const passport = p.passportItem();
|
4488 | passport.name = null;
|
4489 | p.save(function(err) {
|
4490 | should.exist(err);
|
4491 | err.name.should.equal('ValidationError');
|
4492 | err.details.messages.passportItem
|
4493 | .should.eql(['is invalid: `name` can\'t be blank']);
|
4494 | done();
|
4495 | });
|
4496 | });
|
4497 | });
|
4498 |
|
4499 | it('should update an embedded item on scope', function(done) {
|
4500 | Person.findById(personId, function(err, p) {
|
4501 | p.passportItem.update({name: 'Freddy'}, function(err, passport) {
|
4502 | if (err) return done(err);
|
4503 | passport = p.passportItem();
|
4504 | passport.toObject().should.eql({name: 'Freddy'});
|
4505 | passport.should.be.an.instanceOf(Passport);
|
4506 | passport.should.equal(p.passport);
|
4507 | done();
|
4508 | });
|
4509 | });
|
4510 | });
|
4511 |
|
4512 | it('should get an embedded item on scope - verify', function(done) {
|
4513 | Person.findById(personId, function(err, p) {
|
4514 | if (err) return done(err);
|
4515 | const passport = p.passportItem();
|
4516 | passport.toObject().should.eql({name: 'Freddy'});
|
4517 | done();
|
4518 | });
|
4519 | });
|
4520 |
|
4521 | it('should destroy an embedded item on scope', function(done) {
|
4522 | Person.findById(personId, function(err, p) {
|
4523 | p.passportItem.destroy(function(err) {
|
4524 | if (err) return done(err);
|
4525 | should.equal(p.passport, null);
|
4526 | done();
|
4527 | });
|
4528 | });
|
4529 | });
|
4530 |
|
4531 |
|
4532 | it('should get an embedded item on scope - verify', function(done) {
|
4533 | Person.findById(personId, function(err, p) {
|
4534 | if (err) return done(err);
|
4535 | should.equal(p.passport, null);
|
4536 | done();
|
4537 | });
|
4538 | });
|
4539 |
|
4540 | it('should save an unsaved model', function(done) {
|
4541 | const p = new Person({name: 'Fred'});
|
4542 | p.isNewRecord().should.be.true;
|
4543 | p.passportItem.create({name: 'Fredric'}, function(err, passport) {
|
4544 | if (err) return done(err);
|
4545 | p.passport.should.equal(passport);
|
4546 | p.isNewRecord().should.be.false;
|
4547 | done();
|
4548 | });
|
4549 | });
|
4550 |
|
4551 | it('should create an embedded item on scope with promises', function(done) {
|
4552 | Person.create({name: 'Fred'})
|
4553 | .then(function(p) {
|
4554 | personId = p.id;
|
4555 | p.passportItem.create({name: 'Fredric'})
|
4556 | .then(function(passport) {
|
4557 | p.passport.toObject().should.eql({name: 'Fredric'});
|
4558 | p.passport.should.be.an.instanceOf(Passport);
|
4559 | done();
|
4560 | });
|
4561 | }).catch(done);
|
4562 | });
|
4563 |
|
4564 | it('should get an embedded item on scope with promises', function(done) {
|
4565 | Person.findById(personId)
|
4566 | .then(function(p) {
|
4567 | const passport = p.passportItem();
|
4568 | passport.toObject().should.eql({name: 'Fredric'});
|
4569 | passport.should.be.an.instanceOf(Passport);
|
4570 | passport.should.equal(p.passport);
|
4571 | passport.should.equal(p.passportItem.value());
|
4572 | done();
|
4573 | }).catch(done);
|
4574 | });
|
4575 |
|
4576 | it('should validate an embedded item on scope with promises - on creation', function(done) {
|
4577 | const p = new Person({name: 'Fred'});
|
4578 | p.passportItem.create({})
|
4579 | .then(function(passport) {
|
4580 | should.not.exist(passport);
|
4581 | done();
|
4582 | })
|
4583 | .catch(function(err) {
|
4584 | should.exist(err);
|
4585 | err.name.should.equal('ValidationError');
|
4586 | err.details.messages.name.should.eql(['can\'t be blank']);
|
4587 | done();
|
4588 | }).catch(done);
|
4589 | });
|
4590 |
|
4591 | it('should validate an embedded item on scope with promises - on update', function(done) {
|
4592 | Person.findById(personId)
|
4593 | .then(function(p) {
|
4594 | const passport = p.passportItem();
|
4595 | passport.name = null;
|
4596 | return p.save()
|
4597 | .then(function(p) {
|
4598 | should.not.exist(p);
|
4599 | done();
|
4600 | })
|
4601 | .catch(function(err) {
|
4602 | should.exist(err);
|
4603 | err.name.should.equal('ValidationError');
|
4604 | err.details.messages.passportItem
|
4605 | .should.eql(['is invalid: `name` can\'t be blank']);
|
4606 | done();
|
4607 | });
|
4608 | }).catch(done);
|
4609 | });
|
4610 |
|
4611 | it('should update an embedded item on scope with promises', function(done) {
|
4612 | Person.findById(personId)
|
4613 | .then(function(p) {
|
4614 | return p.passportItem.update({name: 'Jason'})
|
4615 | .then(function(passport) {
|
4616 | passport = p.passportItem();
|
4617 | passport.toObject().should.eql({name: 'Jason'});
|
4618 | passport.should.be.an.instanceOf(Passport);
|
4619 | passport.should.equal(p.passport);
|
4620 | done();
|
4621 | });
|
4622 | }).catch(done);
|
4623 | });
|
4624 |
|
4625 | it('should get an embedded item on scope with promises - verify', function(done) {
|
4626 | Person.findById(personId)
|
4627 | .then(function(p) {
|
4628 | const passport = p.passportItem();
|
4629 | passport.toObject().should.eql({name: 'Jason'});
|
4630 | done();
|
4631 | }).catch(done);
|
4632 | });
|
4633 |
|
4634 | it('should destroy an embedded item on scope with promises', function(done) {
|
4635 | Person.findById(personId)
|
4636 | .then(function(p) {
|
4637 | return p.passportItem.destroy()
|
4638 | .then(function() {
|
4639 | should.equal(p.passport, null);
|
4640 | done();
|
4641 | });
|
4642 | }).catch(done);
|
4643 | });
|
4644 |
|
4645 |
|
4646 | it('should get an embedded item on scope with promises - verify', function(done) {
|
4647 | Person.findById(personId)
|
4648 | .then(function(p) {
|
4649 | should.equal(p.passport, null);
|
4650 | done();
|
4651 | }).catch(done);
|
4652 | });
|
4653 |
|
4654 | it('should also save changes when directly saving the embedded model', function(done) {
|
4655 |
|
4656 | const originalHasPK = Passport.definition.hasPK;
|
4657 | Passport.definition.hasPK = function() { return true; };
|
4658 | Person.findById(personId)
|
4659 | .then(function(p) {
|
4660 | return p.passportItem.create({name: 'Mitsos'});
|
4661 | })
|
4662 | .then(function(passport) {
|
4663 | passport.name = 'Jim';
|
4664 | return passport.save();
|
4665 | })
|
4666 | .then(function() {
|
4667 | return Person.findById(personId);
|
4668 | })
|
4669 | .then(function(person) {
|
4670 | person.passportItem().toObject().should.eql({name: 'Jim'});
|
4671 |
|
4672 | Passport.definition.hasPK = originalHasPK;
|
4673 | done();
|
4674 | })
|
4675 | .catch(function(err) {
|
4676 | Passport.definition.hasPK = originalHasPK;
|
4677 | done(err);
|
4678 | });
|
4679 | });
|
4680 |
|
4681 | it('should delete the embedded document and also update parent', function(done) {
|
4682 | const originalHasPK = Passport.definition.hasPK;
|
4683 | Passport.definition.hasPK = function() { return true; };
|
4684 | Person.findById(personId)
|
4685 | .then(function(p) {
|
4686 | return p.passportItem().destroy();
|
4687 | })
|
4688 | .then(function() {
|
4689 | return Person.findById(personId);
|
4690 | })
|
4691 | .then(function(person) {
|
4692 | person.should.have.property('passport', null);
|
4693 | done();
|
4694 | })
|
4695 | .catch(function(err) {
|
4696 | Passport.definition.hasPK = originalHasPK;
|
4697 | done(err);
|
4698 | });
|
4699 | });
|
4700 | });
|
4701 |
|
4702 | describe('embedsOne - persisted model', function() {
|
4703 |
|
4704 |
|
4705 |
|
4706 | let Passport, Person;
|
4707 | before(function() {
|
4708 | db = getMemoryDataSource();
|
4709 | Person = db.define('Person', {name: String});
|
4710 | Passport = db.define('Passport',
|
4711 | {name: {type: 'string', required: true}});
|
4712 | });
|
4713 |
|
4714 | it('can be declared using embedsOne method', function(done) {
|
4715 | Person.embedsOne(Passport, {
|
4716 | options: {persistent: true},
|
4717 | });
|
4718 | db.automigrate(['Person', 'Passport'], done);
|
4719 | });
|
4720 |
|
4721 | it('should create an item - to offset id', function(done) {
|
4722 | Passport.create({name: 'Wilma'}, function(err, p) {
|
4723 | if (err) return done(err);
|
4724 | p.id.should.equal(1);
|
4725 | p.name.should.equal('Wilma');
|
4726 | done();
|
4727 | });
|
4728 | });
|
4729 |
|
4730 | it('should create an embedded item on scope', function(done) {
|
4731 | Person.create({name: 'Fred'}, function(err, p) {
|
4732 | if (err) return done(err);
|
4733 | p.passportItem.create({name: 'Fredric'}, function(err, passport) {
|
4734 | if (err) return done(err);
|
4735 | p.passport.id.should.eql(2);
|
4736 | p.passport.name.should.equal('Fredric');
|
4737 | done();
|
4738 | });
|
4739 | });
|
4740 | });
|
4741 |
|
4742 | it('should create an embedded item on scope with promises', function(done) {
|
4743 | Person.create({name: 'Barney'})
|
4744 | .then(function(p) {
|
4745 | return p.passportItem.create({name: 'Barnabus'})
|
4746 | .then(function(passport) {
|
4747 | p.passport.id.should.eql(3);
|
4748 | p.passport.name.should.equal('Barnabus');
|
4749 | done();
|
4750 | });
|
4751 | }).catch(done);
|
4752 | });
|
4753 | });
|
4754 |
|
4755 | describe('embedsOne - generated id', function() {
|
4756 | let Passport;
|
4757 | before(function() {
|
4758 | tmp = getTransientDataSource();
|
4759 | Person = db.define('Person', {name: String});
|
4760 | Passport = tmp.define('Passport',
|
4761 | {
|
4762 | id: {type: 'string', id: true, generated: true},
|
4763 | name: {type: 'string', required: true},
|
4764 | });
|
4765 | });
|
4766 |
|
4767 | it('can be declared using embedsOne method', function(done) {
|
4768 | Person.embedsOne(Passport);
|
4769 | db.automigrate(['Person'], done);
|
4770 | });
|
4771 |
|
4772 | it('should create an embedded item on scope', function(done) {
|
4773 | Person.create({name: 'Fred'}, function(err, p) {
|
4774 | if (err) return done(err);
|
4775 | p.passportItem.create({name: 'Fredric'}, function(err, passport) {
|
4776 | if (err) return done(err);
|
4777 | passport.id.should.match(/^[0-9a-fA-F]{24}$/);
|
4778 | p.passport.name.should.equal('Fredric');
|
4779 | done();
|
4780 | });
|
4781 | });
|
4782 | });
|
4783 | });
|
4784 |
|
4785 | describe('embedsMany', function() {
|
4786 | let address1, address2;
|
4787 |
|
4788 | before(function(done) {
|
4789 | tmp = getTransientDataSource({defaultIdType: Number});
|
4790 | Person = db.define('Person', {name: String});
|
4791 | Address = tmp.define('Address', {street: String});
|
4792 | Address.validatesPresenceOf('street');
|
4793 |
|
4794 | db.automigrate(['Person'], done);
|
4795 | });
|
4796 |
|
4797 | it('can be declared', function(done) {
|
4798 | Person.embedsMany(Address, {
|
4799 | options: {
|
4800 | property: {
|
4801 | postgresql: {
|
4802 | dataType: 'json',
|
4803 | },
|
4804 | },
|
4805 | },
|
4806 | });
|
4807 | db.automigrate(['Person'], done);
|
4808 | });
|
4809 |
|
4810 | it('should have setup embedded accessor/scope', function() {
|
4811 | const p = new Person({name: 'Fred'});
|
4812 | p.addresses.should.be.an.array;
|
4813 | p.addresses.should.have.length(0);
|
4814 | p.addressList.should.be.a.function;
|
4815 | p.addressList.findById.should.be.a.function;
|
4816 | p.addressList.updateById.should.be.a.function;
|
4817 | p.addressList.destroy.should.be.a.function;
|
4818 | p.addressList.exists.should.be.a.function;
|
4819 | p.addressList.create.should.be.a.function;
|
4820 | p.addressList.build.should.be.a.function;
|
4821 | });
|
4822 |
|
4823 | it('should create embedded items on scope', function(done) {
|
4824 | Person.create({name: 'Fred'}, function(err, p) {
|
4825 | p.addressList.create({street: 'Street 1'}, function(err, address) {
|
4826 | if (err) return done(err);
|
4827 | address1 = address;
|
4828 | should.exist(address1.id);
|
4829 | address1.street.should.equal('Street 1');
|
4830 | done();
|
4831 | });
|
4832 | });
|
4833 | });
|
4834 |
|
4835 | it('respects property options on the embedded property', function() {
|
4836 | Person.definition.properties.addresses.should.have.property('postgresql');
|
4837 | Person.definition.properties.addresses.postgresql.should.eql({dataType: 'json'});
|
4838 | });
|
4839 |
|
4840 |
|
4841 | it('should create embedded items on scope', function(done) {
|
4842 | Person.findOne(function(err, p) {
|
4843 | p.addressList.create({street: 'Street 2'}, function(err, address) {
|
4844 | if (err) return done(err);
|
4845 | address2 = address;
|
4846 | should.exist(address2.id);
|
4847 | address2.street.should.equal('Street 2');
|
4848 | done();
|
4849 | });
|
4850 | });
|
4851 | });
|
4852 |
|
4853 | it('should return embedded items from scope', function(done) {
|
4854 | Person.findOne(function(err, p) {
|
4855 | p.addressList(function(err, addresses) {
|
4856 | if (err) return done(err);
|
4857 |
|
4858 | const list = p.addressList();
|
4859 | list.should.equal(addresses);
|
4860 | list.should.equal(p.addresses);
|
4861 |
|
4862 | p.addressList.value().should.equal(list);
|
4863 |
|
4864 | addresses.should.have.length(2);
|
4865 | addresses[0].id.should.eql(address1.id);
|
4866 | addresses[0].street.should.equal('Street 1');
|
4867 | addresses[1].id.should.eql(address2.id);
|
4868 | addresses[1].street.should.equal('Street 2');
|
4869 | done();
|
4870 | });
|
4871 | });
|
4872 | });
|
4873 |
|
4874 | it('should filter embedded items on scope', function(done) {
|
4875 | Person.findOne(function(err, p) {
|
4876 | p.addressList({where: {street: 'Street 2'}}, function(err, addresses) {
|
4877 | if (err) return done(err);
|
4878 | addresses.should.have.length(1);
|
4879 | addresses[0].id.should.eql(address2.id);
|
4880 | addresses[0].street.should.equal('Street 2');
|
4881 | done();
|
4882 | });
|
4883 | });
|
4884 | });
|
4885 |
|
4886 | it('should validate embedded items', function(done) {
|
4887 | Person.findOne(function(err, p) {
|
4888 | p.addressList.create({}, function(err, address) {
|
4889 | should.exist(err);
|
4890 | should.not.exist(address);
|
4891 | err.name.should.equal('ValidationError');
|
4892 | err.details.codes.street.should.eql(['presence']);
|
4893 | done();
|
4894 | });
|
4895 | });
|
4896 | });
|
4897 |
|
4898 | it('should find embedded items by id', function(done) {
|
4899 | Person.findOne(function(err, p) {
|
4900 | p.addressList.findById(address2.id, function(err, address) {
|
4901 | address.should.be.instanceof(Address);
|
4902 | address.id.should.eql(address2.id);
|
4903 | address.street.should.equal('Street 2');
|
4904 | done();
|
4905 | });
|
4906 | });
|
4907 | });
|
4908 |
|
4909 | it('should check if item exists', function(done) {
|
4910 | Person.findOne(function(err, p) {
|
4911 | p.addressList.exists(address2.id, function(err, exists) {
|
4912 | if (err) return done(err);
|
4913 | exists.should.be.true;
|
4914 | done();
|
4915 | });
|
4916 | });
|
4917 | });
|
4918 |
|
4919 | it('should update embedded items by id', function(done) {
|
4920 | Person.findOne(function(err, p) {
|
4921 | p.addressList.updateById(address2.id, {street: 'New Street'}, function(err, address) {
|
4922 | address.should.be.instanceof(Address);
|
4923 | address.id.should.eql(address2.id);
|
4924 | address.street.should.equal('New Street');
|
4925 | done();
|
4926 | });
|
4927 | });
|
4928 | });
|
4929 |
|
4930 | it('should validate the update of embedded items', function(done) {
|
4931 | Person.findOne(function(err, p) {
|
4932 | p.addressList.updateById(address2.id, {street: null}, function(err, address) {
|
4933 | err.name.should.equal('ValidationError');
|
4934 | err.details.codes.street.should.eql(['presence']);
|
4935 | done();
|
4936 | });
|
4937 | });
|
4938 | });
|
4939 |
|
4940 | it('should find embedded items by id - verify', function(done) {
|
4941 | Person.findOne(function(err, p) {
|
4942 | p.addressList.findById(address2.id, function(err, address) {
|
4943 | address.should.be.instanceof(Address);
|
4944 | address.id.should.eql(address2.id);
|
4945 | address.street.should.equal('New Street');
|
4946 | done();
|
4947 | });
|
4948 | });
|
4949 | });
|
4950 |
|
4951 | it('should have accessors: at, get, set', function(done) {
|
4952 | Person.findOne(function(err, p) {
|
4953 | p.addressList.at(0).id.should.eql(address1.id);
|
4954 | p.addressList.get(address1.id).id.should.eql(address1.id);
|
4955 | p.addressList.set(address1.id, {street: 'Changed 1'});
|
4956 | p.addresses[0].street.should.equal('Changed 1');
|
4957 | p.addressList.at(1).id.should.eql(address2.id);
|
4958 | p.addressList.get(address2.id).id.should.eql(address2.id);
|
4959 | p.addressList.set(address2.id, {street: 'Changed 2'});
|
4960 | p.addresses[1].street.should.equal('Changed 2');
|
4961 | done();
|
4962 | });
|
4963 | });
|
4964 |
|
4965 | it('should remove embedded items by id', function(done) {
|
4966 | Person.findOne(function(err, p) {
|
4967 | p.addresses.should.have.length(2);
|
4968 | p.addressList.destroy(address1.id, function(err) {
|
4969 | if (err) return done(err);
|
4970 | p.addresses.should.have.length(1);
|
4971 | done();
|
4972 | });
|
4973 | });
|
4974 | });
|
4975 |
|
4976 | it('should have removed embedded items - verify', function(done) {
|
4977 | Person.findOne(function(err, p) {
|
4978 | p.addresses.should.have.length(1);
|
4979 | done();
|
4980 | });
|
4981 | });
|
4982 |
|
4983 | it('should pass options when removed by id', function(done) {
|
4984 | const verifyOptions = function(ctx, next) {
|
4985 | if (!ctx.options || !ctx.options.verify) {
|
4986 | return next(new Error('options or options.verify is missing'));
|
4987 | }
|
4988 | return next();
|
4989 | };
|
4990 | Person.observe('before save', verifyOptions);
|
4991 | Person.findOne(function(err, p) {
|
4992 | p.addressList.create({street: 'options 1'}, {verify: true}, function(err, address) {
|
4993 | if (err) {
|
4994 | Person.clearObservers('before save');
|
4995 | return done(err);
|
4996 | }
|
4997 | p.addressList.destroy(address.id, {verify: true}, function(err) {
|
4998 | if (err) {
|
4999 | Person.clearObservers('before save');
|
5000 | return done(err);
|
5001 | }
|
5002 | Person.findById(p.id, function(err, verify) {
|
5003 | if (err) {
|
5004 | Person.clearObservers('before save');
|
5005 | return done(err);
|
5006 | }
|
5007 | verify.addresses.should.have.length(1);
|
5008 | Person.clearObservers('before save');
|
5009 | done();
|
5010 | });
|
5011 | });
|
5012 | });
|
5013 | });
|
5014 | });
|
5015 |
|
5016 | it('should pass options when removed by where', function(done) {
|
5017 | const verifyOptions = function(ctx, next) {
|
5018 | if (!ctx.options || !ctx.options.verify) {
|
5019 | return next(new Error('options or options.verify is missing'));
|
5020 | }
|
5021 | return next();
|
5022 | };
|
5023 | Person.observe('before save', verifyOptions);
|
5024 | Person.findOne(function(err, p) {
|
5025 | p.addressList.create({street: 'options 2'}, {verify: true}, function(err, address) {
|
5026 | if (err) {
|
5027 | Person.clearObservers('before save');
|
5028 | return done(err);
|
5029 | }
|
5030 | p.addressList.destroyAll({street: 'options 2'}, {verify: true}, function(err) {
|
5031 | if (err) {
|
5032 | Person.clearObservers('before save');
|
5033 | return done(err);
|
5034 | }
|
5035 | Person.findById(p.id, function(err, verify) {
|
5036 | if (err) {
|
5037 | Person.clearObservers('before save');
|
5038 | return done(err);
|
5039 | }
|
5040 | verify.addresses.should.have.length(1);
|
5041 | Person.clearObservers('before save');
|
5042 | done();
|
5043 | });
|
5044 | });
|
5045 | });
|
5046 | });
|
5047 | });
|
5048 |
|
5049 |
|
5050 | it('should create embedded items on scope', function(done) {
|
5051 | Person.findOne(function(err, p) {
|
5052 | p.addressList.create({street: 'Street 3'}, function(err, address) {
|
5053 | if (err) return done(err);
|
5054 | address.street.should.equal('Street 3');
|
5055 | done();
|
5056 | });
|
5057 | });
|
5058 | });
|
5059 |
|
5060 | it('should remove embedded items - filtered', function(done) {
|
5061 | Person.findOne(function(err, p) {
|
5062 | p.addresses.should.have.length(2);
|
5063 | p.addressList.destroyAll({street: 'Street 3'}, function(err) {
|
5064 | if (err) return done(err);
|
5065 | p.addresses.should.have.length(1);
|
5066 | done();
|
5067 | });
|
5068 | });
|
5069 | });
|
5070 |
|
5071 | it('should remove all embedded items', function(done) {
|
5072 | Person.findOne(function(err, p) {
|
5073 | p.addresses.should.have.length(1);
|
5074 | p.addressList.destroyAll(function(err) {
|
5075 | if (err) return done(err);
|
5076 | p.addresses.should.have.length(0);
|
5077 | done();
|
5078 | });
|
5079 | });
|
5080 | });
|
5081 |
|
5082 | it('should have removed all embedded items - verify', function(done) {
|
5083 | Person.findOne(function(err, p) {
|
5084 | p.addresses.should.have.length(0);
|
5085 | done();
|
5086 | });
|
5087 | });
|
5088 |
|
5089 | it('should save an unsaved model', function(done) {
|
5090 | const p = new Person({name: 'Fred'});
|
5091 | p.isNewRecord().should.be.true;
|
5092 | p.addressList.create({street: 'Street 4'}, function(err, address) {
|
5093 | if (err) return done(err);
|
5094 | address.street.should.equal('Street 4');
|
5095 | p.isNewRecord().should.be.false;
|
5096 | done();
|
5097 | });
|
5098 | });
|
5099 | });
|
5100 |
|
5101 | describe('embedsMany - omit default value for embedded item', function() {
|
5102 | before(function(done) {
|
5103 | tmp = getTransientDataSource({defaultIdType: Number});
|
5104 | Person = db.define('Person', {name: String});
|
5105 | Address = tmp.define('Address', {street: String});
|
5106 | Address.validatesPresenceOf('street');
|
5107 |
|
5108 | db.automigrate(['Person'], done);
|
5109 | });
|
5110 |
|
5111 | it('can be declared', function(done) {
|
5112 | Person.embedsMany(Address, {
|
5113 | options: {
|
5114 | omitDefaultEmbeddedItem: true,
|
5115 | property: {
|
5116 | postgresql: {
|
5117 | dataType: 'json',
|
5118 | },
|
5119 | },
|
5120 | },
|
5121 | });
|
5122 | db.automigrate(['Person'], done);
|
5123 | });
|
5124 |
|
5125 | it('should not set default value for embedded item', function() {
|
5126 | const p = new Person({name: 'Fred'});
|
5127 | p.should.have.property('addresses', undefined);
|
5128 | });
|
5129 |
|
5130 | it('should create embedded items on scope', function(done) {
|
5131 | Person.create({name: 'Fred'}, function(err, p) {
|
5132 | p.addressList.create({street: 'Street 1'}, function(err, address) {
|
5133 | if (err) return done(err);
|
5134 | should.exist(address.id);
|
5135 | address.street.should.equal('Street 1');
|
5136 | p.addresses.should.be.array;
|
5137 | p.addresses.should.have.length(1);
|
5138 | done();
|
5139 | });
|
5140 | });
|
5141 | });
|
5142 |
|
5143 | it('should build embedded items', function(done) {
|
5144 | Person.findOne(function(err, p) {
|
5145 | p.addresses.should.have.length(1);
|
5146 | p.addressList.build({id: 'home', street: 'Home'});
|
5147 | p.addressList.build({id: 'work', street: 'Work'});
|
5148 | p.addresses.should.have.length(3);
|
5149 | done();
|
5150 | });
|
5151 | });
|
5152 |
|
5153 | it('should not create embedded from attributes - relation name', function(done) {
|
5154 | const addresses = [
|
5155 | {id: 'home', street: 'Home Street'},
|
5156 | {id: 'work', street: 'Work Street'},
|
5157 | ];
|
5158 | Person.create({name: 'Wilma', addressList: addresses}, function(err, p) {
|
5159 | if (err) return done(err);
|
5160 | p.should.have.property('addresses', undefined);
|
5161 | done();
|
5162 | });
|
5163 | });
|
5164 | });
|
5165 |
|
5166 | describe('embedsMany - numeric ids + forceId', function() {
|
5167 | before(function(done) {
|
5168 | tmp = getTransientDataSource();
|
5169 | Person = db.define('Person', {name: String});
|
5170 | Address = tmp.define('Address', {
|
5171 | id: {type: Number, id: true},
|
5172 | street: String,
|
5173 | });
|
5174 |
|
5175 | db.automigrate(['Person'], done);
|
5176 | });
|
5177 |
|
5178 | it('can be declared', function(done) {
|
5179 | Person.embedsMany(Address, {options: {forceId: true}});
|
5180 | db.automigrate(['Person'], done);
|
5181 | });
|
5182 |
|
5183 | it('should create embedded items on scope', function(done) {
|
5184 | Person.create({name: 'Fred'}, function(err, p) {
|
5185 | p.addressList.create({street: 'Street 1'}, function(err, address) {
|
5186 | if (err) return done(err);
|
5187 | address.id.should.equal(1);
|
5188 | p.addressList.create({street: 'Street 2'}, function(err, address) {
|
5189 | address.id.should.equal(2);
|
5190 | p.addressList.create({id: 12345, street: 'Street 3'}, function(err, address) {
|
5191 | address.id.should.equal(3);
|
5192 | done();
|
5193 | });
|
5194 | });
|
5195 | });
|
5196 | });
|
5197 | });
|
5198 | });
|
5199 |
|
5200 | describe('embedsMany - explicit ids', function() {
|
5201 | before(function(done) {
|
5202 | tmp = getTransientDataSource();
|
5203 | Person = db.define('Person', {name: String});
|
5204 | Address = tmp.define('Address', {street: String}, {forceId: false});
|
5205 | Address.validatesPresenceOf('street');
|
5206 |
|
5207 | db.automigrate(['Person'], done);
|
5208 | });
|
5209 |
|
5210 | it('can be declared', function(done) {
|
5211 | Person.embedsMany(Address);
|
5212 | db.automigrate(['Person'], done);
|
5213 | });
|
5214 |
|
5215 | it('should create embedded items on scope', function(done) {
|
5216 | Person.create({name: 'Fred'}, function(err, p) {
|
5217 | p.addressList.create({id: 'home', street: 'Street 1'}, function(err, address) {
|
5218 | if (err) return done(err);
|
5219 | p.addressList.create({id: 'work', street: 'Work Street 2'}, function(err, address) {
|
5220 | if (err) return done(err);
|
5221 | address.id.should.equal('work');
|
5222 | address.street.should.equal('Work Street 2');
|
5223 | done();
|
5224 | });
|
5225 | });
|
5226 | });
|
5227 | });
|
5228 |
|
5229 | it('should find embedded items by id', function(done) {
|
5230 | Person.findOne(function(err, p) {
|
5231 | p.addressList.findById('work', function(err, address) {
|
5232 | address.should.be.instanceof(Address);
|
5233 | address.id.should.equal('work');
|
5234 | address.street.should.equal('Work Street 2');
|
5235 | done();
|
5236 | });
|
5237 | });
|
5238 | });
|
5239 |
|
5240 | it('should check for duplicate ids', function(done) {
|
5241 | Person.findOne(function(err, p) {
|
5242 | p.addressList.create({id: 'home', street: 'Invalid'}, function(err, addresses) {
|
5243 | should.exist(err);
|
5244 | err.name.should.equal('ValidationError');
|
5245 | err.details.codes.addresses.should.eql(['uniqueness']);
|
5246 | done();
|
5247 | });
|
5248 | });
|
5249 | });
|
5250 |
|
5251 | it('should update embedded items by id', function(done) {
|
5252 | Person.findOne(function(err, p) {
|
5253 | p.addressList.updateById('home', {street: 'New Street'}, function(err, address) {
|
5254 | address.should.be.instanceof(Address);
|
5255 | address.id.should.equal('home');
|
5256 | address.street.should.equal('New Street');
|
5257 | done();
|
5258 | });
|
5259 | });
|
5260 | });
|
5261 |
|
5262 | it('should remove embedded items by id', function(done) {
|
5263 | Person.findOne(function(err, p) {
|
5264 | p.addresses.should.have.length(2);
|
5265 | p.addressList.destroy('home', function(err) {
|
5266 | if (err) return done(err);
|
5267 | p.addresses.should.have.length(1);
|
5268 | done();
|
5269 | });
|
5270 | });
|
5271 | });
|
5272 |
|
5273 | it('should have embedded items - verify', function(done) {
|
5274 | Person.findOne(function(err, p) {
|
5275 | p.addresses.should.have.length(1);
|
5276 | done();
|
5277 | });
|
5278 | });
|
5279 |
|
5280 | it('should validate all embedded items', function(done) {
|
5281 | const addresses = [];
|
5282 | addresses.push({id: 'home', street: 'Home Street'});
|
5283 | addresses.push({id: 'work', street: ''});
|
5284 | Person.create({name: 'Wilma', addresses: addresses}, function(err, p) {
|
5285 | err.name.should.equal('ValidationError');
|
5286 | err.details.messages.addresses.should.eql([
|
5287 | 'contains invalid item: `work` (`street` can\'t be blank)',
|
5288 | ]);
|
5289 | done();
|
5290 | });
|
5291 | });
|
5292 |
|
5293 | it('should build embedded items', function(done) {
|
5294 | Person.create({name: 'Wilma'}, function(err, p) {
|
5295 | p.addressList.build({id: 'home', street: 'Home'});
|
5296 | p.addressList.build({id: 'work', street: 'Work'});
|
5297 | p.addresses.should.have.length(2);
|
5298 | p.save(function(err, p) {
|
5299 | done();
|
5300 | });
|
5301 | });
|
5302 | });
|
5303 |
|
5304 |
|
5305 | it('should have embedded items - verify', function(done) {
|
5306 | Person.findOne({where: {name: 'Wilma'}}, function(err, p) {
|
5307 | p.name.should.equal('Wilma');
|
5308 | p.addresses.should.have.length(2);
|
5309 | p.addresses[0].id.should.equal('home');
|
5310 | p.addresses[0].street.should.equal('Home');
|
5311 | p.addresses[1].id.should.equal('work');
|
5312 | p.addresses[1].street.should.equal('Work');
|
5313 | done();
|
5314 | });
|
5315 | });
|
5316 |
|
5317 | it('should have accessors: at, get, set', function(done) {
|
5318 | Person.findOne({where: {name: 'Wilma'}}, function(err, p) {
|
5319 | p.name.should.equal('Wilma');
|
5320 | p.addresses.should.have.length(2);
|
5321 | p.addressList.at(0).id.should.equal('home');
|
5322 | p.addressList.get('home').id.should.equal('home');
|
5323 | p.addressList.set('home', {id: 'den'}).id.should.equal('den');
|
5324 | p.addressList.at(1).id.should.equal('work');
|
5325 | p.addressList.get('work').id.should.equal('work');
|
5326 | p.addressList.set('work', {id: 'factory'}).id.should.equal('factory');
|
5327 | done();
|
5328 | });
|
5329 | });
|
5330 |
|
5331 | it('should create embedded from attributes - property name', function(done) {
|
5332 | const addresses = [
|
5333 | {id: 'home', street: 'Home Street'},
|
5334 | {id: 'work', street: 'Work Street'},
|
5335 | ];
|
5336 | Person.create({name: 'Wilma', addresses: addresses}, function(err, p) {
|
5337 | if (err) return done(err);
|
5338 | p.addressList.at(0).id.should.equal('home');
|
5339 | p.addressList.at(1).id.should.equal('work');
|
5340 | done();
|
5341 | });
|
5342 | });
|
5343 |
|
5344 | it('should not create embedded from attributes - relation name', function(done) {
|
5345 | const addresses = [
|
5346 | {id: 'home', street: 'Home Street'},
|
5347 | {id: 'work', street: 'Work Street'},
|
5348 | ];
|
5349 | Person.create({name: 'Wilma', addressList: addresses}, function(err, p) {
|
5350 | if (err) return done(err);
|
5351 | p.addresses.should.have.length(0);
|
5352 | done();
|
5353 | });
|
5354 | });
|
5355 |
|
5356 | it('should create embedded items with auto-generated id', function(done) {
|
5357 | Person.create({name: 'Wilma'}, function(err, p) {
|
5358 | p.addressList.create({street: 'Home Street 1'}, function(err, address) {
|
5359 | if (err) return done(err);
|
5360 | address.id.should.match(/^[0-9a-fA-F]{24}$/);
|
5361 | address.street.should.equal('Home Street 1');
|
5362 | done();
|
5363 | });
|
5364 | });
|
5365 | });
|
5366 | });
|
5367 |
|
5368 | describe('embedsMany - persisted model', function() {
|
5369 | let address0, address1, address2;
|
5370 | let person;
|
5371 |
|
5372 |
|
5373 |
|
5374 |
|
5375 |
|
5376 | before(function(done) {
|
5377 | db = getMemoryDataSource();
|
5378 | Person = db.define('Person', {name: String});
|
5379 | Address = db.define('Address', {street: String});
|
5380 | Address.validatesPresenceOf('street');
|
5381 |
|
5382 | db.automigrate(['Person', 'Address'], done);
|
5383 | });
|
5384 |
|
5385 | it('can be declared', function(done) {
|
5386 |
|
5387 |
|
5388 | Person.embedsMany(Address, {
|
5389 | scope: {order: 'street'},
|
5390 | options: {persistent: true},
|
5391 | });
|
5392 | db.automigrate(['Person', 'Address'], done);
|
5393 | });
|
5394 |
|
5395 | it('should create individual items (0)', function(done) {
|
5396 | Address.create({street: 'Street 0'}, function(err, inst) {
|
5397 | inst.id.should.equal(1);
|
5398 | address0 = inst;
|
5399 | done();
|
5400 | });
|
5401 | });
|
5402 |
|
5403 | it('should create individual items (1)', function(done) {
|
5404 | Address.create({street: 'Street 1'}, function(err, inst) {
|
5405 | inst.id.should.equal(2);
|
5406 | address1 = inst;
|
5407 | done();
|
5408 | });
|
5409 | });
|
5410 |
|
5411 | it('should create individual items (2)', function(done) {
|
5412 | Address.create({street: 'Street 2'}, function(err, inst) {
|
5413 | inst.id.should.equal(3);
|
5414 | address2 = inst;
|
5415 | done();
|
5416 | });
|
5417 | });
|
5418 |
|
5419 | it('should create individual items (3)', function(done) {
|
5420 | Address.create({street: 'Street 3'}, function(err, inst) {
|
5421 | inst.id.should.equal(4);
|
5422 | done();
|
5423 | });
|
5424 | });
|
5425 |
|
5426 | it('should add embedded items on scope', function(done) {
|
5427 | Person.create({name: 'Fred'}, function(err, p) {
|
5428 | person = p;
|
5429 | p.addressList.create(address1.toObject(), function(err, address) {
|
5430 | if (err) return done(err);
|
5431 | address.id.should.eql(2);
|
5432 | address.street.should.equal('Street 1');
|
5433 | p.addressList.create(address2.toObject(), function(err, address) {
|
5434 | if (err) return done(err);
|
5435 | address.id.should.eql(3);
|
5436 | address.street.should.equal('Street 2');
|
5437 | done();
|
5438 | });
|
5439 | });
|
5440 | });
|
5441 | });
|
5442 |
|
5443 | it('should create embedded items on scope', function(done) {
|
5444 | Person.findById(person.id, function(err, p) {
|
5445 | p.addressList.create({street: 'Street 4'}, function(err, address) {
|
5446 | if (err) return done(err);
|
5447 | address.id.should.equal(5);
|
5448 | address.street.should.equal('Street 4');
|
5449 | done();
|
5450 | });
|
5451 | });
|
5452 | });
|
5453 |
|
5454 | it('should have embedded items on scope', function(done) {
|
5455 | Person.findById(person.id, function(err, p) {
|
5456 | p.addressList(function(err, addresses) {
|
5457 | if (err) return done(err);
|
5458 | addresses.should.have.length(3);
|
5459 | addresses[0].street.should.equal('Street 1');
|
5460 | addresses[1].street.should.equal('Street 2');
|
5461 | addresses[2].street.should.equal('Street 4');
|
5462 | done();
|
5463 | });
|
5464 | });
|
5465 | });
|
5466 |
|
5467 | it('should validate embedded items on scope - id', function(done) {
|
5468 | Person.create({name: 'Wilma'}, function(err, p) {
|
5469 | p.addressList.create({id: null, street: 'Street 1'}, function(err, address) {
|
5470 | if (err) return done(err);
|
5471 | address.street.should.equal('Street 1');
|
5472 | done();
|
5473 | });
|
5474 | });
|
5475 | });
|
5476 |
|
5477 | it('should validate embedded items on scope - street', function(done) {
|
5478 | const newId = uid.fromConnector(db) || 1234;
|
5479 | Person.create({name: 'Wilma'}, function(err, p) {
|
5480 | p.addressList.create({id: newId}, function(err, address) {
|
5481 | should.exist(err);
|
5482 | err.name.should.equal('ValidationError');
|
5483 | err.details.codes.street.should.eql(['presence']);
|
5484 | let expected = 'The `Address` instance is not valid. ';
|
5485 | expected += 'Details: `street` can\'t be blank (value: undefined).';
|
5486 | err.message.should.equal(expected);
|
5487 | done();
|
5488 | });
|
5489 | });
|
5490 | });
|
5491 | });
|
5492 |
|
5493 | describe('embedsMany - relations, scope and properties', function() {
|
5494 | let category, job1, job2, job3;
|
5495 |
|
5496 | before(function() {
|
5497 | Category = db.define('Category', {name: String});
|
5498 | Job = db.define('Job', {name: String});
|
5499 | Link = db.define('Link', {name: String, notes: String}, {forceId: false});
|
5500 | });
|
5501 |
|
5502 | it('can be declared', function(done) {
|
5503 | Category.embedsMany(Link, {
|
5504 | as: 'items',
|
5505 | scope: {include: 'job'},
|
5506 | options: {belongsTo: 'job'},
|
5507 | });
|
5508 | Link.belongsTo(Job, {
|
5509 | foreignKey: 'id',
|
5510 | properties: {id: 'id', name: 'name'},
|
5511 | options: {invertProperties: true},
|
5512 | });
|
5513 | db.automigrate(['Category', 'Job', 'Link'], function() {
|
5514 | Job.create({name: 'Job 0'}, done);
|
5515 | });
|
5516 | });
|
5517 |
|
5518 | it('should setup related items', function(done) {
|
5519 | Job.create({name: 'Job 1'}, function(err, p) {
|
5520 | if (err) return done(err);
|
5521 | job1 = p;
|
5522 | Job.create({name: 'Job 2'}, function(err, p) {
|
5523 | if (err) return done(err);
|
5524 | job2 = p;
|
5525 | Job.create({name: 'Job 3'}, function(err, p) {
|
5526 | if (err) return done(err);
|
5527 | job3 = p;
|
5528 | done();
|
5529 | });
|
5530 | });
|
5531 | });
|
5532 | });
|
5533 |
|
5534 | it('should associate items on scope', function(done) {
|
5535 | Category.create({name: 'Category A'}, function(err, cat) {
|
5536 | if (err) return done(err);
|
5537 | let link = cat.items.build();
|
5538 | link.job(job1);
|
5539 | link = cat.items.build();
|
5540 | link.job(job2);
|
5541 | cat.save(function(err, cat) {
|
5542 | if (err) return done(err);
|
5543 | let job = cat.items.at(0);
|
5544 | job.should.be.instanceof(Link);
|
5545 | job.should.not.have.property('jobId');
|
5546 | job.id.should.eql(job1.id);
|
5547 | job.name.should.equal(job1.name);
|
5548 | job = cat.items.at(1);
|
5549 | job.id.should.eql(job2.id);
|
5550 | job.name.should.equal(job2.name);
|
5551 | done();
|
5552 | });
|
5553 | });
|
5554 | });
|
5555 |
|
5556 | it('should include related items on scope', function(done) {
|
5557 | Category.findOne(function(err, cat) {
|
5558 | if (err) return done(err);
|
5559 | cat.links.should.have.length(2);
|
5560 |
|
5561 |
|
5562 | cat.items.at(0).should.be.instanceof(Link);
|
5563 | cat.items.at(0).id.should.eql(job1.id);
|
5564 | cat.items.at(0).name.should.equal(job1.name);
|
5565 | cat.items.at(1).id.should.eql(job2.id);
|
5566 | cat.items.at(1).name.should.equal(job2.name);
|
5567 |
|
5568 |
|
5569 | should.not.exist(cat.items.at(0).job());
|
5570 | should.not.exist(cat.items.at(1).job());
|
5571 |
|
5572 | cat.items(function(err, items) {
|
5573 | if (err) return done(err);
|
5574 | cat.items.at(0).job().should.be.instanceof(Job);
|
5575 | cat.items.at(1).job().should.be.instanceof(Job);
|
5576 | cat.items.at(1).job().name.should.equal('Job 2');
|
5577 | done();
|
5578 | });
|
5579 | });
|
5580 | });
|
5581 |
|
5582 | it('should remove embedded items by id', function(done) {
|
5583 | Category.findOne(function(err, cat) {
|
5584 | if (err) return done(err);
|
5585 | cat.links.should.have.length(2);
|
5586 | cat.items.destroy(job1.id, function(err) {
|
5587 | if (err) return done(err);
|
5588 | if (err) return done(err);
|
5589 | cat.links.should.have.length(1);
|
5590 | done();
|
5591 | });
|
5592 | });
|
5593 | });
|
5594 |
|
5595 | it('should find items on scope', function(done) {
|
5596 | Category.findOne(function(err, cat) {
|
5597 | if (err) return done(err);
|
5598 | cat.links.should.have.length(1);
|
5599 | cat.items.at(0).id.should.eql(job2.id);
|
5600 | cat.items.at(0).name.should.equal(job2.name);
|
5601 |
|
5602 |
|
5603 | should.not.exist(cat.items.at(0).job());
|
5604 |
|
5605 | cat.items(function(err, items) {
|
5606 | if (err) return done(err);
|
5607 | cat.items.at(0).job().should.be.instanceof(Job);
|
5608 | cat.items.at(0).job().name.should.equal('Job 2');
|
5609 | done();
|
5610 | });
|
5611 | });
|
5612 | });
|
5613 |
|
5614 | it('should add related items to scope', function(done) {
|
5615 | Category.findOne(function(err, cat) {
|
5616 | if (err) return done(err);
|
5617 | cat.links.should.have.length(1);
|
5618 | cat.items.add(job3, function(err, link) {
|
5619 | if (err) return done(err);
|
5620 | link.should.be.instanceof(Link);
|
5621 | link.id.should.eql(job3.id);
|
5622 | link.name.should.equal('Job 3');
|
5623 |
|
5624 | cat.links.should.have.length(2);
|
5625 | done();
|
5626 | });
|
5627 | });
|
5628 | });
|
5629 |
|
5630 |
|
5631 | it('should find items on scope', function(done) {
|
5632 | Category.findOne(function(err, cat) {
|
5633 | if (err) return done(err);
|
5634 | cat.links.should.have.length(2);
|
5635 |
|
5636 | cat.items.at(0).should.be.instanceof(Link);
|
5637 | cat.items.at(0).id.should.eql(job2.id);
|
5638 | cat.items.at(0).name.should.equal(job2.name);
|
5639 | cat.items.at(1).id.should.eql(job3.id);
|
5640 | cat.items.at(1).name.should.equal(job3.name);
|
5641 |
|
5642 | done();
|
5643 | });
|
5644 | });
|
5645 |
|
5646 | it('should remove embedded items by reference id', function(done) {
|
5647 | Category.findOne(function(err, cat) {
|
5648 | if (err) return done(err);
|
5649 | cat.links.should.have.length(2);
|
5650 | cat.items.remove(job2.id, function(err) {
|
5651 | if (err) return done(err);
|
5652 | if (err) return done(err);
|
5653 | cat.links.should.have.length(1);
|
5654 | done();
|
5655 | });
|
5656 | });
|
5657 | });
|
5658 |
|
5659 | it('should have removed embedded items by reference id', function(done) {
|
5660 | Category.findOne(function(err, cat) {
|
5661 | if (err) return done(err);
|
5662 | cat.links.should.have.length(1);
|
5663 | done();
|
5664 | });
|
5665 | });
|
5666 |
|
5667 | let jobId;
|
5668 |
|
5669 | it('should create items on scope', function(done) {
|
5670 | Category.create({name: 'Category B'}, function(err, cat) {
|
5671 | if (err) return done(err);
|
5672 | category = cat;
|
5673 | const link = cat.items.build({notes: 'Some notes...'});
|
5674 | link.job.create({name: 'Job 1'}, function(err, p) {
|
5675 | if (err) return done(err);
|
5676 | jobId = p.id;
|
5677 | cat.links[0].id.should.eql(p.id);
|
5678 | cat.links[0].name.should.equal('Job 1');
|
5679 | cat.links[0].notes.should.equal('Some notes...');
|
5680 | cat.items.at(0).should.equal(cat.links[0]);
|
5681 | done();
|
5682 | });
|
5683 | });
|
5684 | });
|
5685 |
|
5686 |
|
5687 | it('should find items on scope', function(done) {
|
5688 | Category.findById(category.id, function(err, cat) {
|
5689 | if (err) return done(err);
|
5690 | cat.name.should.equal('Category B');
|
5691 | cat.links.toObject().should.eql([
|
5692 | {id: jobId, name: 'Job 1', notes: 'Some notes...'},
|
5693 | ]);
|
5694 | cat.items.at(0).should.equal(cat.links[0]);
|
5695 | cat.items(function(err, items) {
|
5696 | if (err) return done(err);
|
5697 | items.should.be.an.array;
|
5698 | items.should.have.length(1);
|
5699 | items[0].job(function(err, p) {
|
5700 | p.name.should.equal('Job 1');
|
5701 | done();
|
5702 | });
|
5703 | });
|
5704 | });
|
5705 | });
|
5706 |
|
5707 | it('should update items on scope - and save parent', function(done) {
|
5708 | Category.findById(category.id, function(err, cat) {
|
5709 | if (err) return done(err);
|
5710 | const link = cat.items.at(0);
|
5711 |
|
5712 |
|
5713 |
|
5714 | cat.items.updateById(link.id, {notes: 'Updated notes...'}, function(err, link) {
|
5715 | if (err) return done(err);
|
5716 | link.notes.should.equal('Updated notes...');
|
5717 | done();
|
5718 | });
|
5719 | });
|
5720 | });
|
5721 |
|
5722 | it('should find items on scope - verify update', function(done) {
|
5723 | Category.findById(category.id, function(err, cat) {
|
5724 | if (err) return done(err);
|
5725 | cat.name.should.equal('Category B');
|
5726 | cat.links.toObject().should.eql([
|
5727 | {id: jobId, name: 'Job 1', notes: 'Updated notes...'},
|
5728 | ]);
|
5729 | done();
|
5730 | });
|
5731 | });
|
5732 |
|
5733 | it('should remove items from scope - and save parent', function(done) {
|
5734 | Category.findById(category.id, function(err, cat) {
|
5735 | if (err) return done(err);
|
5736 | cat.items.at(0).destroy(function(err, link) {
|
5737 | if (err) return done(err);
|
5738 | cat.links.should.have.lengthOf(0);
|
5739 | done();
|
5740 | });
|
5741 | });
|
5742 | });
|
5743 |
|
5744 | it('should find items on scope - verify destroy', function(done) {
|
5745 | Category.findById(category.id, function(err, cat) {
|
5746 | if (err) return done(err);
|
5747 | cat.name.should.equal('Category B');
|
5748 | cat.links.should.have.lengthOf(0);
|
5749 | done();
|
5750 | });
|
5751 | });
|
5752 | });
|
5753 |
|
5754 | describe('embedsMany - polymorphic relations', function() {
|
5755 | let person1, person2;
|
5756 |
|
5757 | before(function(done) {
|
5758 | tmp = getTransientDataSource();
|
5759 |
|
5760 | Book = db.define('Book', {name: String});
|
5761 | Author = db.define('Author', {name: String});
|
5762 | Reader = db.define('Reader', {name: String});
|
5763 |
|
5764 | Link = tmp.define('Link', {
|
5765 | id: {type: Number, id: true},
|
5766 | name: String, notes: String,
|
5767 | });
|
5768 | Link.validatesPresenceOf('linkedId');
|
5769 | Link.validatesPresenceOf('linkedType');
|
5770 |
|
5771 | db.automigrate(['Book', 'Author', 'Reader'], done);
|
5772 | });
|
5773 |
|
5774 | it('can be declared', function(done) {
|
5775 | const idType = db.connector.getDefaultIdType();
|
5776 |
|
5777 | Book.embedsMany(Link, {as: 'people',
|
5778 | polymorphic: 'linked',
|
5779 | scope: {include: 'linked'},
|
5780 | });
|
5781 | Link.belongsTo('linked', {
|
5782 | polymorphic: {idType: idType},
|
5783 | properties: {name: 'name'},
|
5784 | options: {invertProperties: true},
|
5785 | });
|
5786 | db.automigrate(['Book', 'Author', 'Reader'], done);
|
5787 | });
|
5788 |
|
5789 | it('should setup related items', function(done) {
|
5790 | Author.create({name: 'Author 1'}, function(err, p) {
|
5791 | person1 = p;
|
5792 | Reader.create({name: 'Reader 1'}, function(err, p) {
|
5793 | person2 = p;
|
5794 | done();
|
5795 | });
|
5796 | });
|
5797 | });
|
5798 |
|
5799 | it('should create items on scope', function(done) {
|
5800 | Book.create({name: 'Book'}, function(err, book) {
|
5801 | let link = book.people.build({notes: 'Something ...'});
|
5802 | link.linked(person1);
|
5803 | link = book.people.build();
|
5804 | link.linked(person2);
|
5805 | book.save(function(err, book) {
|
5806 | if (err) return done(err);
|
5807 |
|
5808 | let link = book.people.at(0);
|
5809 | link.should.be.instanceof(Link);
|
5810 | link.id.should.equal(1);
|
5811 | link.linkedId.should.eql(person1.id);
|
5812 | link.linkedType.should.equal('Author');
|
5813 | link.name.should.equal('Author 1');
|
5814 |
|
5815 | link = book.people.at(1);
|
5816 | link.should.be.instanceof(Link);
|
5817 | link.id.should.equal(2);
|
5818 | link.linkedId.should.eql(person2.id);
|
5819 | link.linkedType.should.equal('Reader');
|
5820 | link.name.should.equal('Reader 1');
|
5821 |
|
5822 | done();
|
5823 | });
|
5824 | });
|
5825 | });
|
5826 |
|
5827 | it('should include related items on scope', function(done) {
|
5828 | Book.findOne(function(err, book) {
|
5829 | book.links.should.have.length(2);
|
5830 |
|
5831 | let link = book.people.at(0);
|
5832 | link.should.be.instanceof(Link);
|
5833 | link.id.should.eql(1);
|
5834 | link.linkedId.should.eql(person1.id);
|
5835 | link.linkedType.should.equal('Author');
|
5836 | link.notes.should.equal('Something ...');
|
5837 |
|
5838 | link = book.people.at(1);
|
5839 | link.should.be.instanceof(Link);
|
5840 | link.id.should.eql(2);
|
5841 | link.linkedId.should.eql(person2.id);
|
5842 | link.linkedType.should.equal('Reader');
|
5843 |
|
5844 |
|
5845 | should.not.exist(book.people.at(0).linked());
|
5846 | should.not.exist(book.people.at(1).linked());
|
5847 |
|
5848 | book.people(function(err, people) {
|
5849 | people[0].linked().should.be.instanceof(Author);
|
5850 | people[0].linked().name.should.equal('Author 1');
|
5851 | people[1].linked().should.be.instanceof(Reader);
|
5852 | people[1].linked().name.should.equal('Reader 1');
|
5853 | done();
|
5854 | });
|
5855 | });
|
5856 | });
|
5857 |
|
5858 | bdd.itIf(connectorCapabilities.supportInclude === true,
|
5859 | 'should include nested related items on scope', function(done) {
|
5860 |
|
5861 |
|
5862 |
|
5863 |
|
5864 |
|
5865 | Book.find({include: 'people'}, function(err, books) {
|
5866 | const obj = books[0].toObject();
|
5867 |
|
5868 | obj.should.have.property('links');
|
5869 | obj.should.have.property('people');
|
5870 |
|
5871 | obj.links.should.have.length(2);
|
5872 | obj.links[0].name.should.be.oneOf('Author 1', 'Reader 1');
|
5873 | obj.links[1].name.should.be.oneOf('Author 1', 'Reader 1');
|
5874 |
|
5875 | obj.people.should.have.length(2);
|
5876 |
|
5877 | obj.people[0].name.should.equal('Author 1');
|
5878 | obj.people[0].notes.should.equal('Something ...');
|
5879 |
|
5880 | obj.people[0].linked.name.should.equal('Author 1');
|
5881 | obj.people[1].linked.name.should.equal('Reader 1');
|
5882 |
|
5883 | done();
|
5884 | });
|
5885 | });
|
5886 | });
|
5887 |
|
5888 | describe('referencesMany', function() {
|
5889 | let job1, job2, job3;
|
5890 |
|
5891 | before(function(done) {
|
5892 | Category = db.define('Category', {name: String});
|
5893 | Job = db.define('Job', {name: String});
|
5894 |
|
5895 | db.automigrate(['Job', 'Category'], done);
|
5896 | });
|
5897 |
|
5898 | it('can be declared', function(done) {
|
5899 | const reverse = function(cb) {
|
5900 | cb = cb || createPromiseCallback();
|
5901 | const modelInstance = this.modelInstance;
|
5902 | const fk = this.definition.keyFrom;
|
5903 | const ids = modelInstance[fk] || [];
|
5904 | modelInstance.updateAttribute(fk, ids.reverse(), function(err, inst) {
|
5905 | cb(err, inst[fk] || []);
|
5906 | });
|
5907 | return cb.promise;
|
5908 | };
|
5909 |
|
5910 | reverse.shared = true;
|
5911 | reverse.http = {verb: 'put', path: '/jobs/reverse'};
|
5912 |
|
5913 | Category.referencesMany(Job, {scopeMethods: {
|
5914 | reverse: reverse,
|
5915 | }});
|
5916 |
|
5917 | Category.prototype['__reverse__jobs'].should.be.a.function;
|
5918 | should.exist(Category.prototype['__reverse__jobs'].shared);
|
5919 | Category.prototype['__reverse__jobs'].http.should.eql(reverse.http);
|
5920 |
|
5921 | db.automigrate(['Job', 'Category'], done);
|
5922 | });
|
5923 |
|
5924 | it('should setup test records', function(done) {
|
5925 | Job.create({name: 'Job 1'}, function(err, p) {
|
5926 | job1 = p;
|
5927 | Job.create({name: 'Job 3'}, function(err, p) {
|
5928 | job3 = p;
|
5929 | done();
|
5930 | });
|
5931 | });
|
5932 | });
|
5933 |
|
5934 | it('should create record on scope', function(done) {
|
5935 | Category.create({name: 'Category A'}, function(err, cat) {
|
5936 | cat.jobIds.should.be.an.array;
|
5937 | cat.jobIds.should.have.length(0);
|
5938 | cat.jobs.create({name: 'Job 2'}, function(err, p) {
|
5939 | if (err) return done(err);
|
5940 | cat.jobIds.should.have.lengthOf(1);
|
5941 | cat.jobIds[0].should.eql(p.id);
|
5942 | p.name.should.equal('Job 2');
|
5943 | job2 = p;
|
5944 | done();
|
5945 | });
|
5946 | });
|
5947 | });
|
5948 |
|
5949 | it('should not allow duplicate record on scope', function(done) {
|
5950 | Category.findOne(function(err, cat) {
|
5951 | cat.jobIds = [job2.id, job2.id];
|
5952 | cat.save(function(err, p) {
|
5953 | should.exist(err);
|
5954 | err.name.should.equal('ValidationError');
|
5955 | err.details.codes.jobs.should.eql(['uniqueness']);
|
5956 | done();
|
5957 | });
|
5958 | });
|
5959 | });
|
5960 |
|
5961 | it('should find items on scope', function(done) {
|
5962 | Category.findOne(function(err, cat) {
|
5963 | cat.jobIds.should.have.lengthOf(1);
|
5964 | cat.jobIds[0].should.eql(job2.id);
|
5965 | cat.jobs(function(err, jobs) {
|
5966 | if (err) return done(err);
|
5967 | const p = jobs[0];
|
5968 | p.id.should.eql(job2.id);
|
5969 | p.name.should.equal('Job 2');
|
5970 | done();
|
5971 | });
|
5972 | });
|
5973 | });
|
5974 |
|
5975 | it('should find items on scope - findById', function(done) {
|
5976 | Category.findOne(function(err, cat) {
|
5977 | cat.jobIds.should.have.lengthOf(1);
|
5978 | cat.jobIds[0].should.eql(job2.id);
|
5979 | cat.jobs.findById(job2.id, function(err, p) {
|
5980 | if (err) return done(err);
|
5981 | p.should.be.instanceof(Job);
|
5982 | p.id.should.eql(job2.id);
|
5983 | p.name.should.equal('Job 2');
|
5984 | done();
|
5985 | });
|
5986 | });
|
5987 | });
|
5988 |
|
5989 | it('should check if a record exists on scope', function(done) {
|
5990 | Category.findOne(function(err, cat) {
|
5991 | cat.jobs.exists(job2.id, function(err, exists) {
|
5992 | if (err) return done(err);
|
5993 | should.exist(exists);
|
5994 | done();
|
5995 | });
|
5996 | });
|
5997 | });
|
5998 |
|
5999 | it('should update a record on scope', function(done) {
|
6000 | Category.findOne(function(err, cat) {
|
6001 | const attrs = {name: 'Job 2 - edit'};
|
6002 | cat.jobs.updateById(job2.id, attrs, function(err, p) {
|
6003 | if (err) return done(err);
|
6004 | p.name.should.equal(attrs.name);
|
6005 | done();
|
6006 | });
|
6007 | });
|
6008 | });
|
6009 |
|
6010 | it('should get a record by index - at', function(done) {
|
6011 | Category.findOne(function(err, cat) {
|
6012 | cat.jobs.at(0, function(err, p) {
|
6013 | if (err) return done(err);
|
6014 | p.should.be.instanceof(Job);
|
6015 | p.id.should.eql(job2.id);
|
6016 | p.name.should.equal('Job 2 - edit');
|
6017 | done();
|
6018 | });
|
6019 | });
|
6020 | });
|
6021 |
|
6022 | it('should add a record to scope - object', function(done) {
|
6023 | Category.findOne(function(err, cat) {
|
6024 | cat.jobs.add(job1, function(err, prod) {
|
6025 | if (err) return done(err);
|
6026 | cat.jobIds[0].should.eql(job2.id);
|
6027 | cat.jobIds[1].should.eql(job1.id);
|
6028 | prod.id.should.eql(job1.id);
|
6029 | prod.should.have.property('name');
|
6030 | done();
|
6031 | });
|
6032 | });
|
6033 | });
|
6034 |
|
6035 |
|
6036 | it('should add a record to scope - object', function(done) {
|
6037 | Category.findOne(function(err, cat) {
|
6038 | cat.jobs.add(job3.id, function(err, prod) {
|
6039 | if (err) return done(err);
|
6040 | const expected = [job2.id, job1.id, job3.id];
|
6041 | cat.jobIds[0].should.eql(expected[0]);
|
6042 | cat.jobIds[1].should.eql(expected[1]);
|
6043 | cat.jobIds[2].should.eql(expected[2]);
|
6044 | prod.id.should.eql(job3.id);
|
6045 | prod.should.have.property('name');
|
6046 | done();
|
6047 | });
|
6048 | });
|
6049 | });
|
6050 |
|
6051 |
|
6052 | it('should find items on scope - findById', function(done) {
|
6053 | Category.findOne(function(err, cat) {
|
6054 | cat.jobs.findById(job3.id, function(err, p) {
|
6055 | if (err) return done(err);
|
6056 | p.id.should.eql(job3.id);
|
6057 | p.name.should.equal('Job 3');
|
6058 | done();
|
6059 | });
|
6060 | });
|
6061 | });
|
6062 |
|
6063 | it('should find items on scope - filter', function(done) {
|
6064 | Category.findOne(function(err, cat) {
|
6065 | const filter = {where: {name: 'Job 1'}};
|
6066 | cat.jobs(filter, function(err, jobs) {
|
6067 | if (err) return done(err);
|
6068 | jobs.should.have.length(1);
|
6069 | const p = jobs[0];
|
6070 | p.id.should.eql(job1.id);
|
6071 | p.name.should.equal('Job 1');
|
6072 | done();
|
6073 | });
|
6074 | });
|
6075 | });
|
6076 |
|
6077 | it('should remove items from scope', function(done) {
|
6078 | Category.findOne(function(err, cat) {
|
6079 | cat.jobs.remove(job1.id, function(err, ids) {
|
6080 | if (err) return done(err);
|
6081 | const expected = [job2.id, job3.id];
|
6082 | cat.jobIds[0].should.eql(expected[0]);
|
6083 | cat.jobIds[1].should.eql(expected[1]);
|
6084 | cat.jobIds[0].should.eql(ids[0]);
|
6085 | cat.jobIds[1].should.eql(ids[1]);
|
6086 | done();
|
6087 | });
|
6088 | });
|
6089 | });
|
6090 |
|
6091 | it('should find items on scope - verify', function(done) {
|
6092 | Category.findOne(function(err, cat) {
|
6093 | const expected = [job2.id, job3.id];
|
6094 | cat.jobIds[0].should.eql(expected[0]);
|
6095 | cat.jobIds[1].should.eql(expected[1]);
|
6096 | cat.jobs(function(err, jobs) {
|
6097 | if (err) return done(err);
|
6098 | jobs.should.have.length(2);
|
6099 | jobs[0].id.should.eql(job2.id);
|
6100 | jobs[1].id.should.eql(job3.id);
|
6101 | done();
|
6102 | });
|
6103 | });
|
6104 | });
|
6105 |
|
6106 | bdd.itIf(connectorCapabilities.adhocSort !== false,
|
6107 | 'should find items on scope and ordered them by name DESC', function(done) {
|
6108 | Category.find(function(err, categories) {
|
6109 | categories.should.have.length(1);
|
6110 | categories[0].jobs({order: 'name DESC'}, function(err, jobs) {
|
6111 | if (err) return done(err);
|
6112 | jobs.should.have.length(2);
|
6113 | jobs[0].id.should.eql(job3.id);
|
6114 | jobs[1].id.should.eql(job2.id);
|
6115 | done();
|
6116 | });
|
6117 | });
|
6118 | });
|
6119 |
|
6120 | bdd.itIf(connectorCapabilities.adhocSort !== false,
|
6121 | 'should allow custom scope methods - reverse', function(done) {
|
6122 | Category.findOne(function(err, cat) {
|
6123 | cat.jobs.reverse(function(err, ids) {
|
6124 | const expected = [job3.id, job2.id];
|
6125 | ids.toArray().should.eql(expected);
|
6126 | cat.jobIds.toArray().should.eql(expected);
|
6127 | done();
|
6128 | });
|
6129 | });
|
6130 | });
|
6131 |
|
6132 | bdd.itIf(connectorCapabilities.adhocSort === false,
|
6133 | 'should allow custom scope methods - reverse', function(done) {
|
6134 | Category.findOne(function(err, cat) {
|
6135 | cat.jobs.reverse(function(err, ids) {
|
6136 | const expected = [job3.id, job2.id];
|
6137 | ids[0].should.be.oneOf(expected);
|
6138 | ids[1].should.be.oneOf(expected);
|
6139 | cat.jobIds[0].should.be.oneOf(expected);
|
6140 | cat.jobIds[1].should.be.oneOf(expected);
|
6141 | done();
|
6142 | });
|
6143 | });
|
6144 | });
|
6145 |
|
6146 | bdd.itIf(connectorCapabilities.supportInclude === true,
|
6147 | 'should include related items from scope', function(done) {
|
6148 | Category.find({include: 'jobs'}, function(err, categories) {
|
6149 | categories.should.have.length(1);
|
6150 | const cat = categories[0].toObject();
|
6151 | cat.name.should.equal('Category A');
|
6152 | cat.jobs.should.have.length(2);
|
6153 | cat.jobs[0].id.should.eql(job3.id);
|
6154 | cat.jobs[1].id.should.eql(job2.id);
|
6155 | done();
|
6156 | });
|
6157 | });
|
6158 |
|
6159 | it('should destroy items from scope - destroyById', function(done) {
|
6160 | Category.findOne(function(err, cat) {
|
6161 | cat.jobs.destroy(job2.id, function(err) {
|
6162 | if (err) return done(err);
|
6163 | cat.jobIds.should.have.lengthOf(1);
|
6164 | cat.jobIds[0].should.eql(job3.id);
|
6165 | Job.exists(job2.id, function(err, exists) {
|
6166 | if (err) return done(err);
|
6167 | should.exist(exists);
|
6168 | exists.should.be.false;
|
6169 | done();
|
6170 | });
|
6171 | });
|
6172 | });
|
6173 | });
|
6174 |
|
6175 |
|
6176 | it('should find items on scope - verify', function(done) {
|
6177 | Category.findOne(function(err, cat) {
|
6178 | cat.jobIds.should.have.lengthOf(1);
|
6179 | cat.jobIds[0].should.eql(job3.id);
|
6180 | cat.jobs(function(err, jobs) {
|
6181 | if (err) return done(err);
|
6182 | jobs.should.have.length(1);
|
6183 | jobs[0].id.should.eql(job3.id);
|
6184 | done();
|
6185 | });
|
6186 | });
|
6187 | });
|
6188 |
|
6189 | it('should setup test records with promises', function(done) {
|
6190 | db.automigrate(['Job', 'Category'], function() {
|
6191 | return Job.create({name: 'Job 1'})
|
6192 | .then(function(p) {
|
6193 | job1 = p;
|
6194 | return Job.create({name: 'Job 3'});
|
6195 | })
|
6196 | .then(function(p) {
|
6197 | job3 = p;
|
6198 | done();
|
6199 | }).catch(done);
|
6200 | });
|
6201 | });
|
6202 |
|
6203 | it('should create record on scope with promises', function(done) {
|
6204 | Category.create({name: 'Category A'})
|
6205 | .then(function(cat) {
|
6206 | cat.jobIds.should.be.an.array;
|
6207 | cat.jobIds.should.have.length(0);
|
6208 | return cat.jobs.create({name: 'Job 2'})
|
6209 | .then(function(p) {
|
6210 | cat.jobIds.should.have.length(1);
|
6211 | cat.jobIds[0].should.eql(p.id);
|
6212 | p.name.should.equal('Job 2');
|
6213 | job2 = p;
|
6214 | done();
|
6215 | });
|
6216 | }).catch(done);
|
6217 | });
|
6218 |
|
6219 | it('should not allow duplicate record on scope with promises', function(done) {
|
6220 | Category.findOne()
|
6221 | .then(function(cat) {
|
6222 | cat.jobIds = [job2.id, job2.id];
|
6223 | return cat.save();
|
6224 | })
|
6225 | .then(
|
6226 | function(p) { done(new Error('save() should have failed')); },
|
6227 | function(err) {
|
6228 | err.name.should.equal('ValidationError');
|
6229 | err.details.codes.jobs.should.eql(['uniqueness']);
|
6230 | done();
|
6231 | },
|
6232 | );
|
6233 | });
|
6234 |
|
6235 | bdd.itIf(connectorCapabilities.adhocSort !== false,
|
6236 | 'should find items on scope with promises', function(done) {
|
6237 | Category.findOne()
|
6238 | .then(function(cat) {
|
6239 | cat.jobIds.toArray().should.eql([job2.id]);
|
6240 | return cat.jobs.find();
|
6241 | })
|
6242 | .then(function(jobs) {
|
6243 | const p = jobs[0];
|
6244 | p.id.should.eql(job2.id);
|
6245 | p.name.should.equal('Job 2');
|
6246 | done();
|
6247 | })
|
6248 | .catch(done);
|
6249 | });
|
6250 |
|
6251 | bdd.itIf(connectorCapabilities.adhocSort === false,
|
6252 | 'should find items on scope with promises', function(done) {
|
6253 | const theExpectedIds = [job1.id, job2.id, job3.id];
|
6254 | const theExpectedNames = ['Job 1', 'Job 2', 'Job 3'];
|
6255 | Category.findOne()
|
6256 | .then(function(cat) {
|
6257 | cat.jobIds[0].should.be.oneOf(theExpectedIds);
|
6258 | return cat.jobs.find();
|
6259 | })
|
6260 | .then(function(jobs) {
|
6261 | const p = jobs[0];
|
6262 | p.id.should.be.oneOf(theExpectedIds);
|
6263 | p.name.should.be.oneOf(theExpectedNames);
|
6264 | done();
|
6265 | })
|
6266 | .catch(done);
|
6267 | });
|
6268 |
|
6269 | it('should find items on scope with promises - findById', function(done) {
|
6270 | Category.findOne()
|
6271 | .then(function(cat) {
|
6272 | cat.jobIds.should.have.lengthOf(1);
|
6273 | cat.jobIds[0].should.eql(job2.id);
|
6274 | return cat.jobs.findById(job2.id);
|
6275 | })
|
6276 | .then(function(p) {
|
6277 | p.should.be.instanceof(Job);
|
6278 | p.id.should.eql(job2.id);
|
6279 | p.name.should.equal('Job 2');
|
6280 | done();
|
6281 | })
|
6282 | .catch(done);
|
6283 | });
|
6284 |
|
6285 | it('should check if a record exists on scope with promises', function(done) {
|
6286 | Category.findOne()
|
6287 | .then(function(cat) {
|
6288 | return cat.jobs.exists(job2.id)
|
6289 | .then(function(exists) {
|
6290 | should.exist(exists);
|
6291 | done();
|
6292 | });
|
6293 | }).catch(done);
|
6294 | });
|
6295 |
|
6296 | it('should update a record on scope with promises', function(done) {
|
6297 | Category.findOne()
|
6298 | .then(function(cat) {
|
6299 | const attrs = {name: 'Job 2 - edit'};
|
6300 | return cat.jobs.updateById(job2.id, attrs)
|
6301 | .then(function(p) {
|
6302 | p.name.should.equal(attrs.name);
|
6303 | done();
|
6304 | });
|
6305 | })
|
6306 | .catch(done);
|
6307 | });
|
6308 |
|
6309 | it('should get a record by index with promises - at', function(done) {
|
6310 | Category.findOne()
|
6311 | .then(function(cat) {
|
6312 | return cat.jobs.at(0);
|
6313 | })
|
6314 | .then(function(p) {
|
6315 | p.should.be.instanceof(Job);
|
6316 | p.id.should.eql(job2.id);
|
6317 | p.name.should.equal('Job 2 - edit');
|
6318 | done();
|
6319 | })
|
6320 | .catch(done);
|
6321 | });
|
6322 |
|
6323 | it('should add a record to scope with promises - object', function(done) {
|
6324 | Category.findOne()
|
6325 | .then(function(cat) {
|
6326 | return cat.jobs.add(job1)
|
6327 | .then(function(prod) {
|
6328 | const expected = [job2.id, job1.id];
|
6329 | cat.jobIds.should.have.lengthOf(expected.length);
|
6330 | cat.jobIds.should.containDeep(expected);
|
6331 | prod.id.should.eql(job1.id);
|
6332 | prod.should.have.property('name');
|
6333 | done();
|
6334 | });
|
6335 | })
|
6336 | .catch(done);
|
6337 | });
|
6338 |
|
6339 |
|
6340 | it('should add a record to scope with promises - object', function(done) {
|
6341 | Category.findOne()
|
6342 | .then(function(cat) {
|
6343 | return cat.jobs.add(job3.id)
|
6344 | .then(function(prod) {
|
6345 | const expected = [job2.id, job1.id, job3.id];
|
6346 | cat.jobIds.should.have.lengthOf(expected.length);
|
6347 | cat.jobIds.should.containDeep(expected);
|
6348 | prod.id.should.eql(job3.id);
|
6349 | prod.should.have.property('name');
|
6350 | done();
|
6351 | });
|
6352 | })
|
6353 | .catch(done);
|
6354 | });
|
6355 |
|
6356 |
|
6357 | it('should find items on scope with promises - findById', function(done) {
|
6358 | Category.findOne()
|
6359 | .then(function(cat) {
|
6360 | return cat.jobs.findById(job3.id);
|
6361 | })
|
6362 | .then(function(p) {
|
6363 | p.id.should.eql(job3.id);
|
6364 | p.name.should.equal('Job 3');
|
6365 | done();
|
6366 | })
|
6367 | .catch(done);
|
6368 | });
|
6369 |
|
6370 | it('should find items on scope with promises - filter', function(done) {
|
6371 | Category.findOne()
|
6372 | .then(function(cat) {
|
6373 | const filter = {where: {name: 'Job 1'}};
|
6374 | return cat.jobs.find(filter);
|
6375 | })
|
6376 | .then(function(jobs) {
|
6377 | jobs.should.have.length(1);
|
6378 | const p = jobs[0];
|
6379 | p.id.should.eql(job1.id);
|
6380 | p.name.should.equal('Job 1');
|
6381 | done();
|
6382 | })
|
6383 | .catch(done);
|
6384 | });
|
6385 |
|
6386 | it('should remove items from scope with promises', function(done) {
|
6387 | Category.findOne()
|
6388 | .then(function(cat) {
|
6389 | return cat.jobs.remove(job1.id)
|
6390 | .then(function(ids) {
|
6391 | const expected = [job2.id, job3.id];
|
6392 | cat.jobIds.should.have.lengthOf(expected.length);
|
6393 | cat.jobIds.should.containDeep(expected);
|
6394 | cat.jobIds.should.eql(ids);
|
6395 | done();
|
6396 | });
|
6397 | })
|
6398 | .catch(done);
|
6399 | });
|
6400 |
|
6401 | it('should find items on scope with promises - verify', function(done) {
|
6402 | Category.findOne()
|
6403 | .then(function(cat) {
|
6404 | const expected = [job2.id, job3.id];
|
6405 | cat.jobIds.should.have.lengthOf(expected.length);
|
6406 | cat.jobIds.should.containDeep(expected);
|
6407 | return cat.jobs.find();
|
6408 | })
|
6409 | .then(function(jobs) {
|
6410 | jobs.should.have.length(2);
|
6411 | jobs[0].id.should.eql(job2.id);
|
6412 | jobs[1].id.should.eql(job3.id);
|
6413 | done();
|
6414 | })
|
6415 | .catch(done);
|
6416 | });
|
6417 |
|
6418 | bdd.itIf(connectorCapabilities.adhocSort !== false,
|
6419 | 'should find items on scope and ordered them by name DESC', function(done) {
|
6420 | Category.find()
|
6421 | .then(function(categories) {
|
6422 | categories.should.have.length(1);
|
6423 | return categories[0].jobs.find({order: 'name DESC'});
|
6424 | })
|
6425 | .then(function(jobs) {
|
6426 | jobs.should.have.length(2);
|
6427 | jobs[0].id.should.eql(job3.id);
|
6428 | jobs[1].id.should.eql(job2.id);
|
6429 | done();
|
6430 | })
|
6431 | .catch(done);
|
6432 | });
|
6433 |
|
6434 | bdd.itIf(connectorCapabilities.adhocSort !== false,
|
6435 | 'should allow custom scope methods with promises - reverse', function(done) {
|
6436 | Category.findOne()
|
6437 | .then(function(cat) {
|
6438 | return cat.jobs.reverse()
|
6439 | .then(function(ids) {
|
6440 | const expected = [job3.id, job2.id];
|
6441 | ids.toArray().should.eql(expected);
|
6442 | cat.jobIds.toArray().should.eql(expected);
|
6443 | done();
|
6444 | });
|
6445 | })
|
6446 | .catch(done);
|
6447 | });
|
6448 |
|
6449 | bdd.itIf(connectorCapabilities.adhocSort === true &&
|
6450 | connectorCapabilities.supportInclude === true,
|
6451 | 'should include related items from scope with promises', function(done) {
|
6452 | Category.find({include: 'jobs'})
|
6453 | .then(function(categories) {
|
6454 | categories.should.have.length(1);
|
6455 | const cat = categories[0].toObject();
|
6456 | cat.name.should.equal('Category A');
|
6457 | cat.jobs.should.have.length(2);
|
6458 | cat.jobs[0].id.should.eql(job3.id);
|
6459 | cat.jobs[1].id.should.eql(job2.id);
|
6460 | done();
|
6461 | }).catch(done);
|
6462 | });
|
6463 |
|
6464 | it('should destroy items from scope with promises - destroyById', function(done) {
|
6465 | Category.findOne()
|
6466 | .then(function(cat) {
|
6467 | return cat.jobs.destroy(job2.id)
|
6468 | .then(function() {
|
6469 | const expected = [job3.id];
|
6470 | if (connectorCapabilities.adhocSort !== false) {
|
6471 | cat.jobIds.toArray().should.eql(expected);
|
6472 | } else {
|
6473 | cat.jobIds.toArray().should.containDeep(expected);
|
6474 | }
|
6475 | return Job.exists(job2.id);
|
6476 | })
|
6477 | .then(function(exists) {
|
6478 | should.exist(exists);
|
6479 | exists.should.be.false;
|
6480 | done();
|
6481 | });
|
6482 | })
|
6483 | .catch(done);
|
6484 | });
|
6485 |
|
6486 |
|
6487 | it('should find items on scope with promises - verify', function(done) {
|
6488 | Category.findOne()
|
6489 | .then(function(cat) {
|
6490 | const expected = [job3.id];
|
6491 | cat.jobIds.should.have.lengthOf(expected.length);
|
6492 | cat.jobIds.should.containDeep(expected);
|
6493 | return cat.jobs.find();
|
6494 | })
|
6495 | .then(function(jobs) {
|
6496 | jobs.should.have.length(1);
|
6497 | jobs[0].id.should.eql(job3.id);
|
6498 | done();
|
6499 | })
|
6500 | .catch(done);
|
6501 | });
|
6502 | });
|
6503 |
|
6504 | describe('custom relation/scope methods', function() {
|
6505 | let categoryId;
|
6506 |
|
6507 | before(function(done) {
|
6508 | Category = db.define('Category', {name: String});
|
6509 | Job = db.define('Job', {name: String});
|
6510 |
|
6511 | db.automigrate(['Job', 'Category'], done);
|
6512 | });
|
6513 |
|
6514 | it('can be declared', function(done) {
|
6515 | const relation = Category.hasMany(Job);
|
6516 |
|
6517 | const summarize = function(cb) {
|
6518 | cb = cb || createPromiseCallback();
|
6519 | const modelInstance = this.modelInstance;
|
6520 | this.fetch(function(err, items) {
|
6521 | if (err) return cb(err, []);
|
6522 | const summary = items.map(function(item) {
|
6523 | const obj = item.toObject();
|
6524 | obj.categoryName = modelInstance.name;
|
6525 | return obj;
|
6526 | });
|
6527 | cb(null, summary);
|
6528 | });
|
6529 | return cb.promise;
|
6530 | };
|
6531 |
|
6532 | summarize.shared = true;
|
6533 | summarize.http = {verb: 'get', path: '/jobs/summary'};
|
6534 |
|
6535 | relation.defineMethod('summarize', summarize);
|
6536 |
|
6537 | Category.prototype['__summarize__jobs'].should.be.a.function;
|
6538 | should.exist(Category.prototype['__summarize__jobs'].shared);
|
6539 | Category.prototype['__summarize__jobs'].http.should.eql(summarize.http);
|
6540 |
|
6541 | db.automigrate(['Job', 'Category'], done);
|
6542 | });
|
6543 |
|
6544 | it('should setup test records', function(done) {
|
6545 | Category.create({name: 'Category A'}, function(err, cat) {
|
6546 | categoryId = cat.id;
|
6547 | cat.jobs.create({name: 'Job 1'}, function(err, p) {
|
6548 | cat.jobs.create({name: 'Job 2'}, function(err, p) {
|
6549 | done();
|
6550 | });
|
6551 | });
|
6552 | });
|
6553 | });
|
6554 |
|
6555 | it('should allow custom scope methods - summarize', function(done) {
|
6556 | const categoryIdStr = categoryId.toString();
|
6557 | const expected = [
|
6558 | {name: 'Job 1', categoryId: categoryIdStr, categoryName: 'Category A'},
|
6559 | {name: 'Job 2', categoryId: categoryIdStr, categoryName: 'Category A'},
|
6560 | ];
|
6561 |
|
6562 | Category.findOne(function(err, cat) {
|
6563 | cat.jobs.summarize(function(err, summary) {
|
6564 | if (err) return done(err);
|
6565 | const result = summary.map(function(item) {
|
6566 | delete item.id;
|
6567 | item.categoryId = item.categoryId.toString();
|
6568 | return item;
|
6569 | });
|
6570 |
|
6571 | result.should.containDeep(expected);
|
6572 | expected.should.containDeep(result);
|
6573 | done();
|
6574 | });
|
6575 | });
|
6576 | });
|
6577 |
|
6578 | it('should allow custom scope methods with promises - summarize', function(done) {
|
6579 | const categoryIdStr = categoryId.toString();
|
6580 | const expected = [
|
6581 | {name: 'Job 1', categoryId: categoryIdStr, categoryName: 'Category A'},
|
6582 | {name: 'Job 2', categoryId: categoryIdStr, categoryName: 'Category A'},
|
6583 | ];
|
6584 |
|
6585 | Category.findOne()
|
6586 | .then(function(cat) {
|
6587 | return cat.jobs.summarize();
|
6588 | })
|
6589 | .then(function(summary) {
|
6590 | const result = summary.map(function(item) {
|
6591 | delete item.id;
|
6592 | item.categoryId = item.categoryId.toString();
|
6593 | return item;
|
6594 | });
|
6595 |
|
6596 | result.should.containDeep(expected);
|
6597 | expected.should.containDeep(result);
|
6598 | done();
|
6599 | })
|
6600 | .catch(done);
|
6601 | });
|
6602 | });
|
6603 |
|
6604 | describe('relation names', function() {
|
6605 | it('throws error when a relation name is `trigger`', function() {
|
6606 | Chapter = db.define('Chapter', {name: String});
|
6607 |
|
6608 | (function() {
|
6609 | db.define(
|
6610 | 'Book',
|
6611 | {name: String},
|
6612 | {
|
6613 | relations: {
|
6614 | trigger: {
|
6615 | model: 'Chapter',
|
6616 | type: 'hasMany',
|
6617 | },
|
6618 | },
|
6619 | },
|
6620 | );
|
6621 | }).should.throw('Invalid relation name: trigger');
|
6622 | });
|
6623 | });
|
6624 |
|
6625 | describe('polymorphic hasMany - revert', function() {
|
6626 | before(function(done) {
|
6627 | Picture = db.define('Picture', {name: String});
|
6628 | Author = db.define('Author', {name: String});
|
6629 | PictureLink = db.define('PictureLink', {});
|
6630 | Author.hasMany(Picture, {through: PictureLink, polymorphic: 'imageable', invert: true});
|
6631 | Picture.hasMany(Author, {through: PictureLink, polymorphic: 'imageable'});
|
6632 | db.automigrate(['Picture', 'Author', 'PictureLink'], done);
|
6633 | });
|
6634 | it('should properly query through an inverted relationship', function(done) {
|
6635 | Author.create({name: 'Steve'}, function(err, author) {
|
6636 | if (err) {
|
6637 | return done(err);
|
6638 | }
|
6639 | author.pictures.create({name: 'Steve pic 1'}, function(err, pic) {
|
6640 | if (err) {
|
6641 | return done(err);
|
6642 | }
|
6643 | Author.findOne({include: 'pictures'}, function(err, author) {
|
6644 | if (err) {
|
6645 | return done(err);
|
6646 | }
|
6647 | author.pictures().length.should.eql(1);
|
6648 | author.pictures()[0].name.should.eql('Steve pic 1');
|
6649 | done();
|
6650 | });
|
6651 | });
|
6652 | });
|
6653 | });
|
6654 | });
|
6655 | });
|