Skip to content

Commit 0fe7a07

Browse files
committed
Make NewEntity override deleted
1 parent a9600a0 commit 0fe7a07

4 files changed

Lines changed: 65 additions & 22 deletions

File tree

src/SIL.Harmony.Tests/DataModelSimpleChanges.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,28 +190,46 @@ public async Task CanDeleteAnEntry()
190190
}
191191

192192
[Fact]
193-
public async Task CanModifyAnEntryAfterDelete()
193+
public async Task ApplyChange_ModifiesADeletedEntry()
194194
{
195195
await WriteNextChange(SetWord(_entity1Id, "test-value"));
196196
var deleteCommit = await WriteNextChange(new DeleteChange<Word>(_entity1Id));
197-
await WriteNextChange(SetWord(_entity1Id, "after-delete"));
197+
var newNoteChange = new SetWordNoteChange(_entity1Id, "note-after-delete");
198+
newNoteChange.SupportsApplyChange().Should().BeTrue();
199+
newNoteChange.SupportsNewEntity().Should().BeFalse(); // otherwise it will override the delete
200+
await WriteNextChange(newNoteChange);
198201
var snapshot = await DbContext.Snapshots.DefaultOrder().LastAsync();
199202
var word = snapshot.Entity.Is<Word>();
200-
word.Text.Should().Be("after-delete");
203+
word.Text.Should().Be("test-value");
204+
word.Note.Should().Be("note-after-delete");
201205
word.DeletedAt.Should().Be(deleteCommit.DateTime);
202206
}
203207

208+
[Fact]
209+
public async Task NewEntity_UndeletesAnEntry()
210+
{
211+
await WriteNextChange(SetWord(_entity1Id, "test-value"));
212+
await WriteNextChange(new DeleteChange<Word>(_entity1Id));
213+
var recreateChange = SetWord(_entity1Id, "after-undo-delete");
214+
recreateChange.SupportsNewEntity().Should().BeTrue();
215+
await WriteNextChange(recreateChange);
216+
var snapshot = await DbContext.Snapshots.DefaultOrder().LastAsync();
217+
var word = snapshot.Entity.Is<Word>();
218+
word.Text.Should().Be("after-undo-delete");
219+
word.DeletedAt.Should().Be(null);
220+
}
221+
204222
[Fact]
205223
public async Task ChangesToSnapshotsAreNotSaved()
206224
{
207225
await WriteNextChange(SetWord(_entity1Id, "test-value"));
208226
var word = await DataModel.GetLatest<Word>(_entity1Id);
209227
word!.Text.Should().Be("test-value");
210228
word.Note.Should().BeNull();
211-
229+
212230
//change made outside the model, should not be saved when writing the next change
213231
word.Note = "a note";
214-
232+
215233
var commit = await WriteNextChange(SetWord(_entity1Id, "after-change"));
216234
var objectSnapshot = commit.Snapshots.Should().ContainSingle().Subject;
217235
objectSnapshot.Entity.Is<Word>().Text.Should().Be("after-change");

src/SIL.Harmony.Tests/SnapshotTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using SIL.Harmony.Sample.Models;
2+
using SIL.Harmony.Sample.Changes;
3+
using SIL.Harmony.Changes;
24
using Microsoft.EntityFrameworkCore;
35

46
namespace SIL.Harmony.Tests;
@@ -19,7 +21,7 @@ public async Task MultipleChangesPreservesRootSnapshot()
1921
{
2022
var entityId = Guid.NewGuid();
2123
var commits = new List<Commit>();
22-
for (int i = 0; i < 4; i++)
24+
for (var i = 0; i < 4; i++)
2325
{
2426
commits.Add(await WriteChange(_localClientId,
2527
new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero).AddHours(i),
@@ -39,7 +41,7 @@ public async Task MultipleChangesPreservesSomeIntermediateSnapshots()
3941
{
4042
var entityId = Guid.NewGuid();
4143
var commits = new List<Commit>();
42-
for (int i = 0; i < 6; i++)
44+
for (var i = 0; i < 6; i++)
4345
{
4446
commits.Add(await WriteChange(_localClientId,
4547
new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero).AddHours(i),
@@ -135,7 +137,7 @@ await WriteNextChange(
135137
var tagCreation = await WriteNextChange(TagWord(wordId, tagId));
136138
await WriteChangeBefore(tagCreation, TagWord(wordId, tagId));
137139

138-
var word = await DataModel.QueryLatest<Word>(q=> q.Include(w => w.Tags)
140+
var word = await DataModel.QueryLatest<Word>(q => q.Include(w => w.Tags)
139141
.Where(w => w.Id == wordId)).FirstOrDefaultAsync();
140142
word.Should().NotBeNull();
141143
word.Tags.Should().BeEquivalentTo([new Tag { Id = tagId, Text = "tag-1" }]);

src/SIL.Harmony/Changes/Change.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public interface IChange
1616

1717
ValueTask ApplyChange(IObjectBase entity, IChangeContext context);
1818
ValueTask<IObjectBase> NewEntity(Commit commit, IChangeContext context);
19+
bool SupportsApplyChange();
20+
bool SupportsNewEntity();
1921
}
2022

2123
/// <summary>
@@ -44,7 +46,7 @@ async ValueTask<IObjectBase> IChange.NewEntity(Commit commit, IChangeContext con
4446

4547
public async ValueTask ApplyChange(IObjectBase entity, IChangeContext context)
4648
{
47-
if (this is CreateChange<T>)
49+
if (!SupportsApplyChange())
4850
return; // skip attempting to apply changes on CreateChange as it does not support apply changes
4951
if (entity.DbObject is T entityT)
5052
{
@@ -56,6 +58,16 @@ public async ValueTask ApplyChange(IObjectBase entity, IChangeContext context)
5658
}
5759
}
5860

61+
public virtual bool SupportsApplyChange()
62+
{
63+
return this is not CreateChange<T>;
64+
}
65+
66+
public virtual bool SupportsNewEntity()
67+
{
68+
return this is not EditChange<T>;
69+
}
70+
5971
[JsonIgnore]
6072
public Type EntityType => typeof(T);
6173
}

src/SIL.Harmony/SnapshotWorker.cs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,26 +81,37 @@ private async ValueTask ApplyCommitChanges(IEnumerable<Commit> commits, bool upd
8181
IObjectBase entity;
8282
var prevSnapshot = await GetSnapshot(commitChange.EntityId);
8383
var changeContext = new ChangeContext(commit, commitIndex, intermediateSnapshots, this, _crdtConfig);
84-
bool wasDeleted;
85-
if (prevSnapshot is not null)
84+
85+
if (prevSnapshot is null)
8686
{
87-
entity = prevSnapshot.Entity.Copy();
88-
wasDeleted = entity.DeletedAt.HasValue;
87+
// create brand new entity
88+
entity = await commitChange.Change.NewEntity(commit, changeContext);
8989
}
90-
else
90+
else if (prevSnapshot.EntityIsDeleted && commitChange.Change.SupportsNewEntity())
9191
{
92+
// revive deleted entity
9293
entity = await commitChange.Change.NewEntity(commit, changeContext);
93-
wasDeleted = false;
9494
}
95-
96-
await commitChange.Change.ApplyChange(entity, changeContext);
97-
98-
var deletedByChange = !wasDeleted && entity.DeletedAt.HasValue;
99-
if (deletedByChange)
95+
else if (commitChange.Change.SupportsApplyChange())
10096
{
101-
await MarkDeleted(entity.Id, changeContext);
97+
// update existing entity
98+
entity = prevSnapshot.Entity.Copy();
99+
var wasDeleted = prevSnapshot.EntityIsDeleted;
100+
await commitChange.Change.ApplyChange(entity, changeContext);
101+
var deletedByChange = !wasDeleted && entity.DeletedAt.HasValue;
102+
if (deletedByChange)
103+
{
104+
await MarkDeleted(entity.Id, changeContext);
105+
}
102106
}
103-
107+
else
108+
{
109+
// entity already exists (and is not deleted)
110+
// and change does not support updating existing entities
111+
// so do nothing
112+
continue;
113+
}
114+
104115
await GenerateSnapshotForEntity(entity, prevSnapshot, changeContext);
105116
}
106117
_newIntermediateSnapshots.AddRange(intermediateSnapshots.Values);

0 commit comments

Comments
 (0)