Skip to content

Commit

Permalink
fix(alias): Fix alias not enqueued when one object in collection chan…
Browse files Browse the repository at this point in the history
…ges (#60)
  • Loading branch information
pjechris authored Oct 23, 2023
1 parent 24903df commit 9b9d85b
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 16 deletions.
15 changes: 3 additions & 12 deletions Sources/CohesionKit/Identity/IdentityStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public class IdentityMap {
/// - Parameter named: the alias to look for
public func find<T: Identifiable>(named: AliasKey<T>) -> EntityObserver<T?> {
identityQueue.sync {
let node = refAliases[safe: named]
let node = refAliases[safe: named, onChange: registry.enqueueChange(for:)]
return EntityObserver(alias: node, registry: registry)
}
}
Expand All @@ -142,7 +142,7 @@ public class IdentityMap {
/// - Returns: an observer returning the alias value. Note that the value will be an Array
public func find<C: Collection>(named: AliasKey<C>) -> EntityObserver<C?> {
identityQueue.sync {
let node = refAliases[safe: named]
let node = refAliases[safe: named, onChange: registry.enqueueChange(for:)]
return EntityObserver(alias: node, registry: registry)
}
}
Expand Down Expand Up @@ -195,12 +195,11 @@ public class IdentityMap {
}

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

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

registry.enqueueChange(for: aliasNode)
logger?.didRegisterAlias(key)
}

Expand Down Expand Up @@ -274,8 +273,6 @@ extension IdentityMap {

update(&content)

_ = nodeStore(entity: content, modifiedAt: modifiedAt)

storeAlias(content: content, key: named, modifiedAt: modifiedAt)

return true
Expand All @@ -295,8 +292,6 @@ extension IdentityMap {

update(&content)

_ = nodeStore(entity: content, modifiedAt: modifiedAt)

storeAlias(content: content, key: named, modifiedAt: modifiedAt)

return true
Expand All @@ -317,8 +312,6 @@ extension IdentityMap {

update(&content)

_ = content.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }

storeAlias(content: content, key: named, modifiedAt: modifiedAt)

return true
Expand All @@ -339,8 +332,6 @@ extension IdentityMap {

update(&content)

_ = content.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }

storeAlias(content: content, key: named, modifiedAt: modifiedAt)

return true
Expand Down
2 changes: 1 addition & 1 deletion Sources/CohesionKit/Observer/ObserverRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,4 @@ extension ObserverRegistry {
hasher.combine(ObjectIdentifier(self))
}
}
}
}
28 changes: 28 additions & 0 deletions Sources/CohesionKit/Storage/AliasContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ struct AliasContainer<T>: Identifiable, Aggregate {

extension AliasContainer {
var nestedEntitiesKeyPaths: [PartialIdentifiableKeyPath<Self>] {
if let self = self as? AggregateKeyPathsEraser {
return self.erasedEntitiesKeyPaths as! [PartialIdentifiableKeyPath<Self>]
}

if let self = self as? IdentifiableKeyPathsEraser {
return self.erasedEntitiesKeyPaths as! [PartialIdentifiableKeyPath<Self>]
}

if let self = self as? CollectionAggregateKeyPathsEraser {
return self.erasedEntitiesKeyPaths as! [PartialIdentifiableKeyPath<Self>]
}

if let self = self as? CollectionIdentifiableKeyPathsEraser {
return self.erasedEntitiesKeyPaths as! [PartialIdentifiableKeyPath<Self>]
}
Expand All @@ -32,6 +40,26 @@ extension AliasContainer: IdentifiableKeyPathsEraser where T: Identifiable {
}
}

private protocol AggregateKeyPathsEraser {
var erasedEntitiesKeyPaths: [Any] { get }
}

extension AliasContainer: AggregateKeyPathsEraser where T: Aggregate {
var erasedEntitiesKeyPaths: [Any] {
[PartialIdentifiableKeyPath<Self>(\.content)]
}
}

private protocol CollectionAggregateKeyPathsEraser {
var erasedEntitiesKeyPaths: [Any] { get }
}

extension AliasContainer: CollectionAggregateKeyPathsEraser where T: MutableCollection, T.Element: Aggregate, T.Index: Hashable {
var erasedEntitiesKeyPaths: [Any] {
[PartialIdentifiableKeyPath<Self>(\.content)]
}
}

private protocol CollectionIdentifiableKeyPathsEraser {
var erasedEntitiesKeyPaths: [Any] { get }
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/CohesionKit/Storage/AliasStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ extension AliasStorage {
set { self[buildKey(for: T.self, key: aliasKey)] = newValue }
}

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

Expand Down
44 changes: 43 additions & 1 deletion Tests/CohesionKitTests/IdentityMapTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ extension IdentityMapTests {
}
}

func test_updateNamed_entityIsCollection_itEnqueuesNestedObjectsInRegistry() {
func test_updateNamed_entityIsAggregate_itEnqueuesNestedObjectsInRegistry() {
let registry = ObserverRegistryStub()
let identityMap = IdentityMap(registry: registry)
let initialValue = RootFixture(
Expand All @@ -367,6 +367,44 @@ extension IdentityMapTests {

XCTAssertTrue(registry.hasPendingChange(for: singleNodeUpdate))
}

func test_updateNamed_aliasIsAggregate_itEnqueuesAliasInRegistry() {
let registry = ObserverRegistryStub()
let identityMap = IdentityMap(registry: registry)
let initialValue = RootFixture(
id: 1,
primitive: "",
singleNode: .init(id: 1),
listNodes: []
)
let singleNodeUpdate = SingleNodeFixture(id: 1, primitive: "update")

_ = identityMap.store(entity: initialValue, named: .root)

registry.clearPendingChangesStub()

identityMap.update(named: .root) {
$0.singleNode = singleNodeUpdate
}

XCTAssertTrue(registry.hasPendingChange(for: AliasContainer<RootFixture>.self))
}

func test_update_entityIsIndirectlyUsedByAlias_itEnqueuesAliasInRegistry() {
let aggregate = RootFixture(id: 1, primitive: "", singleNode: SingleNodeFixture(id: 1), listNodes: [])
let registry = ObserverRegistryStub()
let identityMap = IdentityMap(registry: registry)

_ = identityMap.store(entities: [aggregate], named: .rootList)

registry.clearPendingChangesStub()

identityMap.update(SingleNodeFixture.self, id: 1) {
$0.primitive = "updated"
}

XCTAssertTrue(registry.hasPendingChange(for: AliasContainer<[RootFixture]>.self))
}
}

private extension AliasKey where T == SingleNodeFixture {
Expand All @@ -380,3 +418,7 @@ private extension AliasKey where T == [SingleNodeFixture] {
private extension AliasKey where T == RootFixture {
static let root = AliasKey(named: "root")
}

private extension AliasKey where T == [RootFixture] {
static let rootList = AliasKey(named: "root")
}

0 comments on commit 9b9d85b

Please sign in to comment.