Skip to content

Commit 9b9d85b

Browse files
author
pjechris
authored
fix(alias): Fix alias not enqueued when one object in collection changes (#60)
1 parent 24903df commit 9b9d85b

File tree

5 files changed

+77
-16
lines changed

5 files changed

+77
-16
lines changed

Sources/CohesionKit/Identity/IdentityStore.swift

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public class IdentityMap {
133133
/// - Parameter named: the alias to look for
134134
public func find<T: Identifiable>(named: AliasKey<T>) -> EntityObserver<T?> {
135135
identityQueue.sync {
136-
let node = refAliases[safe: named]
136+
let node = refAliases[safe: named, onChange: registry.enqueueChange(for:)]
137137
return EntityObserver(alias: node, registry: registry)
138138
}
139139
}
@@ -142,7 +142,7 @@ public class IdentityMap {
142142
/// - Returns: an observer returning the alias value. Note that the value will be an Array
143143
public func find<C: Collection>(named: AliasKey<C>) -> EntityObserver<C?> {
144144
identityQueue.sync {
145-
let node = refAliases[safe: named]
145+
let node = refAliases[safe: named, onChange: registry.enqueueChange(for:)]
146146
return EntityObserver(alias: node, registry: registry)
147147
}
148148
}
@@ -195,12 +195,11 @@ public class IdentityMap {
195195
}
196196

197197
private func storeAlias<T>(content: T, key: AliasKey<T>, modifiedAt: Stamp?) {
198-
let aliasNode = refAliases[safe: key]
198+
let aliasNode = refAliases[safe: key, onChange: registry.enqueueChange(for:)]
199199
let aliasContainer = AliasContainer(key: key, content: content)
200200

201201
_ = nodeStore(in: aliasNode, entity: aliasContainer, modifiedAt: modifiedAt)
202202

203-
registry.enqueueChange(for: aliasNode)
204203
logger?.didRegisterAlias(key)
205204
}
206205

@@ -274,8 +273,6 @@ extension IdentityMap {
274273

275274
update(&content)
276275

277-
_ = nodeStore(entity: content, modifiedAt: modifiedAt)
278-
279276
storeAlias(content: content, key: named, modifiedAt: modifiedAt)
280277

281278
return true
@@ -295,8 +292,6 @@ extension IdentityMap {
295292

296293
update(&content)
297294

298-
_ = nodeStore(entity: content, modifiedAt: modifiedAt)
299-
300295
storeAlias(content: content, key: named, modifiedAt: modifiedAt)
301296

302297
return true
@@ -317,8 +312,6 @@ extension IdentityMap {
317312

318313
update(&content)
319314

320-
_ = content.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }
321-
322315
storeAlias(content: content, key: named, modifiedAt: modifiedAt)
323316

324317
return true
@@ -339,8 +332,6 @@ extension IdentityMap {
339332

340333
update(&content)
341334

342-
_ = content.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }
343-
344335
storeAlias(content: content, key: named, modifiedAt: modifiedAt)
345336

346337
return true

Sources/CohesionKit/Observer/ObserverRegistry.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,4 @@ extension ObserverRegistry {
135135
hasher.combine(ObjectIdentifier(self))
136136
}
137137
}
138-
}
138+
}

Sources/CohesionKit/Storage/AliasContainer.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,18 @@ struct AliasContainer<T>: Identifiable, Aggregate {
1010

1111
extension AliasContainer {
1212
var nestedEntitiesKeyPaths: [PartialIdentifiableKeyPath<Self>] {
13+
if let self = self as? AggregateKeyPathsEraser {
14+
return self.erasedEntitiesKeyPaths as! [PartialIdentifiableKeyPath<Self>]
15+
}
16+
1317
if let self = self as? IdentifiableKeyPathsEraser {
1418
return self.erasedEntitiesKeyPaths as! [PartialIdentifiableKeyPath<Self>]
1519
}
1620

21+
if let self = self as? CollectionAggregateKeyPathsEraser {
22+
return self.erasedEntitiesKeyPaths as! [PartialIdentifiableKeyPath<Self>]
23+
}
24+
1725
if let self = self as? CollectionIdentifiableKeyPathsEraser {
1826
return self.erasedEntitiesKeyPaths as! [PartialIdentifiableKeyPath<Self>]
1927
}
@@ -32,6 +40,26 @@ extension AliasContainer: IdentifiableKeyPathsEraser where T: Identifiable {
3240
}
3341
}
3442

43+
private protocol AggregateKeyPathsEraser {
44+
var erasedEntitiesKeyPaths: [Any] { get }
45+
}
46+
47+
extension AliasContainer: AggregateKeyPathsEraser where T: Aggregate {
48+
var erasedEntitiesKeyPaths: [Any] {
49+
[PartialIdentifiableKeyPath<Self>(\.content)]
50+
}
51+
}
52+
53+
private protocol CollectionAggregateKeyPathsEraser {
54+
var erasedEntitiesKeyPaths: [Any] { get }
55+
}
56+
57+
extension AliasContainer: CollectionAggregateKeyPathsEraser where T: MutableCollection, T.Element: Aggregate, T.Index: Hashable {
58+
var erasedEntitiesKeyPaths: [Any] {
59+
[PartialIdentifiableKeyPath<Self>(\.content)]
60+
}
61+
}
62+
3563
private protocol CollectionIdentifiableKeyPathsEraser {
3664
var erasedEntitiesKeyPaths: [Any] { get }
3765
}

Sources/CohesionKit/Storage/AliasStorage.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ extension AliasStorage {
77
set { self[buildKey(for: T.self, key: aliasKey)] = newValue }
88
}
99

10-
subscript<T>(safe key: AliasKey<T>) -> EntityNode<AliasContainer<T>> {
10+
subscript<T>(safe key: AliasKey<T>, onChange onChange: ((EntityNode<AliasContainer<T>>) -> Void)? = nil) -> EntityNode<AliasContainer<T>> {
1111
mutating get {
12-
self[key: key, default: EntityNode(AliasContainer(key: key), modifiedAt: nil)]
12+
self[key: key, default: EntityNode(AliasContainer(key: key), modifiedAt: nil, onChange: onChange)]
1313
}
1414
}
1515

Tests/CohesionKitTests/IdentityMapTests.swift

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ extension IdentityMapTests {
346346
}
347347
}
348348

349-
func test_updateNamed_entityIsCollection_itEnqueuesNestedObjectsInRegistry() {
349+
func test_updateNamed_entityIsAggregate_itEnqueuesNestedObjectsInRegistry() {
350350
let registry = ObserverRegistryStub()
351351
let identityMap = IdentityMap(registry: registry)
352352
let initialValue = RootFixture(
@@ -367,6 +367,44 @@ extension IdentityMapTests {
367367

368368
XCTAssertTrue(registry.hasPendingChange(for: singleNodeUpdate))
369369
}
370+
371+
func test_updateNamed_aliasIsAggregate_itEnqueuesAliasInRegistry() {
372+
let registry = ObserverRegistryStub()
373+
let identityMap = IdentityMap(registry: registry)
374+
let initialValue = RootFixture(
375+
id: 1,
376+
primitive: "",
377+
singleNode: .init(id: 1),
378+
listNodes: []
379+
)
380+
let singleNodeUpdate = SingleNodeFixture(id: 1, primitive: "update")
381+
382+
_ = identityMap.store(entity: initialValue, named: .root)
383+
384+
registry.clearPendingChangesStub()
385+
386+
identityMap.update(named: .root) {
387+
$0.singleNode = singleNodeUpdate
388+
}
389+
390+
XCTAssertTrue(registry.hasPendingChange(for: AliasContainer<RootFixture>.self))
391+
}
392+
393+
func test_update_entityIsIndirectlyUsedByAlias_itEnqueuesAliasInRegistry() {
394+
let aggregate = RootFixture(id: 1, primitive: "", singleNode: SingleNodeFixture(id: 1), listNodes: [])
395+
let registry = ObserverRegistryStub()
396+
let identityMap = IdentityMap(registry: registry)
397+
398+
_ = identityMap.store(entities: [aggregate], named: .rootList)
399+
400+
registry.clearPendingChangesStub()
401+
402+
identityMap.update(SingleNodeFixture.self, id: 1) {
403+
$0.primitive = "updated"
404+
}
405+
406+
XCTAssertTrue(registry.hasPendingChange(for: AliasContainer<[RootFixture]>.self))
407+
}
370408
}
371409

372410
private extension AliasKey where T == SingleNodeFixture {
@@ -380,3 +418,7 @@ private extension AliasKey where T == [SingleNodeFixture] {
380418
private extension AliasKey where T == RootFixture {
381419
static let root = AliasKey(named: "root")
382420
}
421+
422+
private extension AliasKey where T == [RootFixture] {
423+
static let rootList = AliasKey(named: "root")
424+
}

0 commit comments

Comments
 (0)