Skip to content

Commit 47af7c9

Browse files
authored
Merge pull request #11 from richmolj/disassociate
Add isMarkedForDisassociation()
2 parents 4914f9a + 5a64274 commit 47af7c9

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)