Skip to content

Commit 5a64274

Browse files
author
Lee Richmond
committed
Add isMarkedForDisassociation()
This replicates all the logic from isMarkedForDestruction, but sends the 'dissassociate' method instead of the 'destroy' method. The biggest thing that might look off here is some duplicative specs, but I'm OK with this for now.
1 parent 4914f9a commit 5a64274

File tree

6 files changed

+165
-9
lines changed

6 files changed

+165
-9
lines changed

src/model.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default class Model {
3838
__meta__: Object | void = null;
3939
_persisted: boolean = false;
4040
_markedForDestruction: boolean = false;
41+
_markedForDisassociation: boolean = false;
4142
klass: typeof Model;
4243

4344
static attributeList = [];
@@ -233,6 +234,15 @@ export default class Model {
233234
}
234235
}
235236

237+
isMarkedForDisassociation(val? : boolean) : boolean {
238+
if (val != undefined) {
239+
this._markedForDisassociation = val;
240+
return val;
241+
} else {
242+
return this._markedForDisassociation;
243+
}
244+
}
245+
236246
fromJsonapi(resource: japiResource, payload: japiDoc, includeDirective: Object = {}) : any {
237247
return deserializeInstance(this, resource, payload, includeDirective);
238248
}

src/util/deserialize.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,15 @@ class Deserializer {
112112
if (relatedObjects) {
113113
if (Array.isArray(relatedObjects)) {
114114
relatedObjects.forEach((relatedObject, index) => {
115-
if (relatedObject.isMarkedForDestruction()) {
115+
if (relatedObject.isMarkedForDestruction() || relatedObject.isMarkedForDisassociation()) {
116116
model[key].splice(index, 1);
117117
} else {
118118
this._removeDeletions(relatedObject, includeDirective[key] || {});
119119
}
120120
});
121121
} else {
122122
let relatedObject = relatedObjects;
123-
if (relatedObject.isMarkedForDestruction()) {
123+
if (relatedObject.isMarkedForDestruction() || relatedObject.isMarkedForDisassociation()) {
124124
model[key] = null;
125125
} else {
126126
this._removeDeletions(relatedObject, includeDirective[key] || {});

src/util/dirty-check.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class DirtyChecker {
2727

2828
// Either:
2929
// * attributes changed
30-
// * marked for destruction
30+
// * marked for destruction / disassociation
3131
// * not persisted (and thus must be send to server)
3232
// * not itself dirty, but has nested relations that are dirty
3333
check(relationships: Object | Array<any> | string = {}) : boolean {
@@ -37,6 +37,7 @@ class DirtyChecker {
3737
return this._hasDirtyAttributes() ||
3838
this._hasDirtyRelationships(includeHash) ||
3939
this.model.isMarkedForDestruction() ||
40+
this.model.isMarkedForDisassociation() ||
4041
this._isUnpersisted()
4142
}
4243

src/util/write-payload.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ export default class WritePayload {
3535
if (relatedObjects) {
3636
if (Array.isArray(relatedObjects)) {
3737
relatedObjects.forEach((relatedObject, index) => {
38-
if (relatedObject.isMarkedForDestruction()) {
38+
if (relatedObject.isMarkedForDestruction() || relatedObject.isMarkedForDisassociation()) {
3939
model[key].splice(index, 1);
4040
} else {
4141
this.removeDeletions(relatedObject, nested);
4242
}
4343
});
4444
} else {
4545
let relatedObject = relatedObjects;
46-
if (relatedObject.isMarkedForDestruction()) {
46+
if (relatedObject.isMarkedForDestruction() || relatedObject.isMarkedForDisassociation()) {
4747
model[key] = null;
4848
} else {
4949
this.removeDeletions(relatedObject, nested);
@@ -161,8 +161,10 @@ export default class WritePayload {
161161
if (model.isPersisted()) {
162162
if (model.isMarkedForDestruction()) {
163163
method = 'destroy';
164+
} else if (model.isMarkedForDisassociation()) {
165+
method = 'disassociate';
164166
} else {
165-
method = 'update'
167+
method = 'update';
166168
}
167169
} else {
168170
method = 'create';

test/integration/nested-persistence-test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,33 @@ describe('nested persistence', function() {
302302
});
303303

304304
describe('basic nested disassociate', function() {
305-
xit('sends the correct payload', function() {
305+
beforeEach(function() {
306+
seedPersistedData();
307+
});
308+
309+
it('sends the correct payload', function(done) {
310+
instance.books[0].isMarkedForDisassociation(true);
311+
instance.books[0].genre.isMarkedForDisassociation(true);
312+
instance.save({ with: { books: 'genre' } }).then((response) => {
313+
expect(putPayloads[0]).to.deep.equal(expectedUpdatePayload('disassociate'));
314+
done();
315+
});
316+
});
317+
318+
it('removes the associated has_many data', function(done) {
319+
instance.books[0].isMarkedForDisassociation(true);
320+
instance.save({ with: 'books' }).then((response) => {
321+
expect(instance.books.length).to.eq(0);
322+
done();
323+
});
324+
});
325+
326+
it('removes the associated belongs_to data', function(done) {
327+
instance.books[0].genre.isMarkedForDisassociation(true);
328+
instance.save({ with: { books: 'genre' } }).then((response) => {
329+
expect(instance.books[0].genre).to.eq(null);
330+
done();
331+
});
306332
});
307333
});
308334
});

test/unit/model-test.ts

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,41 @@ describe('Model', function() {
432432
});
433433
});
434434

435+
describe('when a has-many is marked for disassociation', function() {
436+
let newDoc, instance, book;
437+
438+
beforeEach(function() {
439+
book = new Book({ id: '1' });
440+
book.isPersisted(true);
441+
book.isMarkedForDisassociation(true);
442+
443+
instance = new Author({ books: [book] });
444+
445+
newDoc = {
446+
data: {
447+
id: '1',
448+
type: 'authors'
449+
}
450+
}
451+
});
452+
453+
describe('when the relation is part of the include directive', function() {
454+
it('is removed from the array', function() {
455+
expect(instance.books.length).to.eq(1);
456+
instance.fromJsonapi(newDoc.data, newDoc, { books: {} });
457+
expect(instance.books.length).to.eq(0);
458+
});
459+
});
460+
461+
describe('when the relation is not part of the include directive', function() {
462+
it('is NOT removed from the array', function() {
463+
expect(instance.books.length).to.eq(1);
464+
instance.fromJsonapi(newDoc.data, newDoc, {});
465+
expect(instance.books.length).to.eq(1);
466+
});
467+
});
468+
});
469+
435470
describe('when a belongs-to is marked for destruction', function() {
436471
let newDoc, instance, book;
437472

@@ -484,6 +519,59 @@ describe('Model', function() {
484519
});
485520
});
486521
});
522+
523+
describe('when a belongs-to is marked for disassociation', function() {
524+
let newDoc, instance, book;
525+
526+
beforeEach(function() {
527+
let genre = new Genre({ id: '1' });
528+
genre.isPersisted(true);
529+
genre.isMarkedForDisassociation(true);
530+
531+
book = new Book({ id: '1', genre: genre });
532+
book.isPersisted(true);
533+
534+
instance = new Author({ books: [book] });
535+
536+
newDoc = {
537+
data: {
538+
id: '1',
539+
type: 'authors'
540+
},
541+
relationships: {
542+
books: {
543+
data: [
544+
{ id: '1', type: 'books' }
545+
]
546+
}
547+
},
548+
included: [
549+
{
550+
id: '1',
551+
type: 'books',
552+
attributes: { title: 'whatever' }
553+
}
554+
]
555+
}
556+
});
557+
558+
it('is set to null', function() {
559+
expect(instance.books[0].genre).to.be.instanceof(Genre);
560+
instance.fromJsonapi(newDoc.data, newDoc, { books: { genre: {} }});
561+
expect(instance.books[0].genre).to.eq(null);
562+
});
563+
564+
describe('within a nested destruction', function() {
565+
beforeEach(function() {
566+
book.isMarkedForDisassociation(true);
567+
});
568+
569+
it('is removed via the parent', function() {
570+
instance.fromJsonapi(newDoc.data, newDoc, { books: { genre: {} }});
571+
expect(instance.books.length).to.eq(0);
572+
});
573+
});
574+
});
487575
});
488576

489577
describe('isMarkedForDestruction', function() {
@@ -497,6 +585,17 @@ describe('Model', function() {
497585
});
498586
});
499587

588+
describe('isMarkedForDisassociation', function() {
589+
it('toggles correctly', function() {
590+
instance = new Author();
591+
expect(instance.isMarkedForDisassociation()).to.eq(false)
592+
instance.isMarkedForDisassociation(true);
593+
expect(instance.isMarkedForDisassociation()).to.eq(true)
594+
instance.isMarkedForDisassociation(false);
595+
expect(instance.isMarkedForDisassociation()).to.eq(false)
596+
});
597+
});
598+
500599
describe('isDirty', function() {
501600
describe('when an attribute changes', function() {
502601
it('is marked as dirty', function() {
@@ -546,8 +645,12 @@ describe('Model', function() {
546645
});
547646

548647
describe('when marked for disassociation', function() {
549-
// disassociations not implemented yet
550-
xit('is dirty', function() {
648+
it('is dirty', function() {
649+
instance = new Author();
650+
instance.isPersisted(true);
651+
expect(instance.isDirty()).to.eq(false);
652+
instance.isMarkedForDisassociation(true);
653+
expect(instance.isDirty()).to.eq(true);
551654
});
552655
});
553656

@@ -659,6 +762,20 @@ describe('Model', function() {
659762
expect(instance.isDirty()).to.eq(false);
660763
});
661764
});
765+
766+
describe('when a hasMany relationship has a member marked for disassociation', function() {
767+
it('is dirty', function() {
768+
let book = new Book({ id: 1 });
769+
book.isPersisted(true);
770+
instance.books = [book];
771+
instance.isPersisted(true);
772+
773+
expect(instance.isDirty('books')).to.eq(false);
774+
book.isMarkedForDisassociation(true);
775+
expect(instance.isDirty('books')).to.eq(true);
776+
expect(instance.isDirty()).to.eq(false);
777+
});
778+
});
662779
});
663780
});
664781
});

0 commit comments

Comments
 (0)