From a29d080188d62bd8d40d82f407631940d420b268 Mon Sep 17 00:00:00 2001 From: mariuskatcontentful <76522955+mariuskatcontentful@users.noreply.github.com> Date: Sat, 20 Jan 2024 12:13:43 +0000 Subject: [PATCH] Fix/stale relationship cache entries (#146) https://github.com/contentful/contentful-persistence.swift/pull/128 Co-authored-by: Maciej Czupryna --- .../project.pbxproj | 36 --- .../Relationships/Relationship.swift | 104 ++++++--- .../Relationships/RelationshipCache.swift | 4 +- .../Relationships/RelationshipChildId.swift | 23 +- .../Relationships/RelationshipData.swift | 208 ++++++------------ .../Relationships/RelationshipsManager.swift | 17 +- .../Relationships/ToManyRelationship.swift | 30 --- .../Relationships/ToOneRelationship.swift | 30 --- .../SynchronizationManager.swift | 57 ++--- .../RelationshipCacheTests.swift | 44 ++-- .../RelationshipChildIdTests.swift | 4 +- .../RelationshipManagerTests.swift | 89 +++++--- .../Relationships/RelationshipTests.swift | 39 ++-- .../ToManyRelationshipTests.swift | 20 -- .../ToOneRelationshipTests.swift | 20 -- 15 files changed, 293 insertions(+), 432 deletions(-) delete mode 100644 Sources/ContentfulPersistence/Relationships/ToManyRelationship.swift delete mode 100644 Sources/ContentfulPersistence/Relationships/ToOneRelationship.swift delete mode 100644 Tests/ContentfulPersistenceTests/Relationships/ToManyRelationshipTests.swift delete mode 100644 Tests/ContentfulPersistenceTests/Relationships/ToOneRelationshipTests.swift diff --git a/ContentfulPersistence.xcodeproj/project.pbxproj b/ContentfulPersistence.xcodeproj/project.pbxproj index 30b3fe8e..0db8bfd9 100644 --- a/ContentfulPersistence.xcodeproj/project.pbxproj +++ b/ContentfulPersistence.xcodeproj/project.pbxproj @@ -37,10 +37,6 @@ 5DD19BAC219F13730041F483 /* deleted-entry-initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DD19BAB219F13730041F483 /* deleted-entry-initial.json */; }; 5DD19BAD219F13730041F483 /* deleted-entry-initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DD19BAB219F13730041F483 /* deleted-entry-initial.json */; }; 5DD19BAE219F13730041F483 /* deleted-entry-initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 5DD19BAB219F13730041F483 /* deleted-entry-initial.json */; }; - 6D5CEDB924FC2493005E8B41 /* ToManyRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB524FC2493005E8B41 /* ToManyRelationship.swift */; }; - 6D5CEDBA24FC2493005E8B41 /* ToManyRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB524FC2493005E8B41 /* ToManyRelationship.swift */; }; - 6D5CEDBB24FC2493005E8B41 /* ToManyRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB524FC2493005E8B41 /* ToManyRelationship.swift */; }; - 6D5CEDBC24FC2493005E8B41 /* ToManyRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB524FC2493005E8B41 /* ToManyRelationship.swift */; }; 6D5CEDBD24FC2493005E8B41 /* RelationshipCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB624FC2493005E8B41 /* RelationshipCache.swift */; }; 6D5CEDBE24FC2493005E8B41 /* RelationshipCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB624FC2493005E8B41 /* RelationshipCache.swift */; }; 6D5CEDBF24FC2493005E8B41 /* RelationshipCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB624FC2493005E8B41 /* RelationshipCache.swift */; }; @@ -49,20 +45,10 @@ 6D5CEDC224FC2493005E8B41 /* Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB724FC2493005E8B41 /* Relationship.swift */; }; 6D5CEDC324FC2493005E8B41 /* Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB724FC2493005E8B41 /* Relationship.swift */; }; 6D5CEDC424FC2493005E8B41 /* Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB724FC2493005E8B41 /* Relationship.swift */; }; - 6D5CEDC524FC2493005E8B41 /* ToOneRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB824FC2493005E8B41 /* ToOneRelationship.swift */; }; - 6D5CEDC624FC2493005E8B41 /* ToOneRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB824FC2493005E8B41 /* ToOneRelationship.swift */; }; - 6D5CEDC724FC2493005E8B41 /* ToOneRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB824FC2493005E8B41 /* ToOneRelationship.swift */; }; - 6D5CEDC824FC2493005E8B41 /* ToOneRelationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDB824FC2493005E8B41 /* ToOneRelationship.swift */; }; 6D5CEDCA24FC24B1005E8B41 /* RelationshipsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDC924FC24B1005E8B41 /* RelationshipsManager.swift */; }; 6D5CEDCB24FC24B1005E8B41 /* RelationshipsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDC924FC24B1005E8B41 /* RelationshipsManager.swift */; }; 6D5CEDCC24FC24B1005E8B41 /* RelationshipsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDC924FC24B1005E8B41 /* RelationshipsManager.swift */; }; 6D5CEDCD24FC24B1005E8B41 /* RelationshipsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5CEDC924FC24B1005E8B41 /* RelationshipsManager.swift */; }; - 6D87842724FCD62C003E69CD /* ToOneRelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842624FCD62C003E69CD /* ToOneRelationshipTests.swift */; }; - 6D87842824FCD62C003E69CD /* ToOneRelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842624FCD62C003E69CD /* ToOneRelationshipTests.swift */; }; - 6D87842924FCD62C003E69CD /* ToOneRelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842624FCD62C003E69CD /* ToOneRelationshipTests.swift */; }; - 6D87842B24FCD747003E69CD /* ToManyRelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842A24FCD747003E69CD /* ToManyRelationshipTests.swift */; }; - 6D87842C24FCD747003E69CD /* ToManyRelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842A24FCD747003E69CD /* ToManyRelationshipTests.swift */; }; - 6D87842D24FCD747003E69CD /* ToManyRelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842A24FCD747003E69CD /* ToManyRelationshipTests.swift */; }; 6D87842F24FCD7C2003E69CD /* RelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842E24FCD7C2003E69CD /* RelationshipTests.swift */; }; 6D87843024FCD7C2003E69CD /* RelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842E24FCD7C2003E69CD /* RelationshipTests.swift */; }; 6D87843124FCD7C2003E69CD /* RelationshipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D87842E24FCD7C2003E69CD /* RelationshipTests.swift */; }; @@ -315,13 +301,9 @@ 5DA9AE0821A2B6AF0033BC4E /* deleted-asset-next.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "deleted-asset-next.json"; sourceTree = ""; }; 5DD19BA6219F0C940041F483 /* deleted-entry-next.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "deleted-entry-next.json"; sourceTree = ""; }; 5DD19BAB219F13730041F483 /* deleted-entry-initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "deleted-entry-initial.json"; sourceTree = ""; }; - 6D5CEDB524FC2493005E8B41 /* ToManyRelationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToManyRelationship.swift; sourceTree = ""; }; 6D5CEDB624FC2493005E8B41 /* RelationshipCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelationshipCache.swift; sourceTree = ""; }; 6D5CEDB724FC2493005E8B41 /* Relationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Relationship.swift; sourceTree = ""; }; - 6D5CEDB824FC2493005E8B41 /* ToOneRelationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToOneRelationship.swift; sourceTree = ""; }; 6D5CEDC924FC24B1005E8B41 /* RelationshipsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelationshipsManager.swift; sourceTree = ""; }; - 6D87842624FCD62C003E69CD /* ToOneRelationshipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToOneRelationshipTests.swift; sourceTree = ""; }; - 6D87842A24FCD747003E69CD /* ToManyRelationshipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToManyRelationshipTests.swift; sourceTree = ""; }; 6D87842E24FCD7C2003E69CD /* RelationshipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelationshipTests.swift; sourceTree = ""; }; 6D87843224FCDB7D003E69CD /* RelationshipCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelationshipCacheTests.swift; sourceTree = ""; }; 6D87843624FCEBC7003E69CD /* RelationshipChildId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelationshipChildId.swift; sourceTree = ""; }; @@ -489,8 +471,6 @@ 6D87843624FCEBC7003E69CD /* RelationshipChildId.swift */, 02E58FA02535C9E30039F3A5 /* RelationshipData.swift */, 6D5CEDC924FC24B1005E8B41 /* RelationshipsManager.swift */, - 6D5CEDB524FC2493005E8B41 /* ToManyRelationship.swift */, - 6D5CEDB824FC2493005E8B41 /* ToOneRelationship.swift */, ); path = Relationships; sourceTree = ""; @@ -499,8 +479,6 @@ isa = PBXGroup; children = ( 6D87842E24FCD7C2003E69CD /* RelationshipTests.swift */, - 6D87842A24FCD747003E69CD /* ToManyRelationshipTests.swift */, - 6D87842624FCD62C003E69CD /* ToOneRelationshipTests.swift */, 6D87843224FCDB7D003E69CD /* RelationshipCacheTests.swift */, 6D87843B24FD0069003E69CD /* RelationshipChildIdTests.swift */, 6D87843F24FD026F003E69CD /* RelationshipManagerTests.swift */, @@ -1188,10 +1166,8 @@ files = ( 6D5CEDCA24FC24B1005E8B41 /* RelationshipsManager.swift in Sources */, 6D5CEDBD24FC2493005E8B41 /* RelationshipCache.swift in Sources */, - 6D5CEDC524FC2493005E8B41 /* ToOneRelationship.swift in Sources */, 6D87843724FCEBC7003E69CD /* RelationshipChildId.swift in Sources */, 6D5CEDC124FC2493005E8B41 /* Relationship.swift in Sources */, - 6D5CEDB924FC2493005E8B41 /* ToManyRelationship.swift in Sources */, ED10E88C1E48AB840061741F /* CoreDataStore.swift in Sources */, ED8C1E721F603939001D059F /* SynchronizationManager+SeedDB.swift in Sources */, ED9C21FA1EF3DBD600882ABF /* PersistenceStore.swift in Sources */, @@ -1209,7 +1185,6 @@ ED10E8B81E48ACBD0061741F /* Asset+CoreDataProperties.swift in Sources */, ED10E8B71E48ACBD0061741F /* Asset.swift in Sources */, 6F388F5D22E73FE60080585F /* RichTextDocumentRecord.swift in Sources */, - 6D87842B24FCD747003E69CD /* ToManyRelationshipTests.swift in Sources */, ED10E8BA1E48ACBD0061741F /* Author+CoreDataProperties.swift in Sources */, ED10E8BE1E48ACBD0061741F /* Post+CoreDataProperties.swift in Sources */, 6FCA36BB22C65731004F9A5E /* RecordWithNonOptionalRelation.swift in Sources */, @@ -1236,7 +1211,6 @@ EDBDBFD51F28B23B00649F5A /* LocalizationTest.xcdatamodeld in Sources */, ED4023E620EA5858001C6BDD /* UnresolvedRelationshipCacheTests.swift in Sources */, ED25D7EA1F17706E00A6BA9A /* ComplexSyncInfo.swift in Sources */, - 6D87842724FCD62C003E69CD /* ToOneRelationshipTests.swift in Sources */, ED10E8C81E48BCAE0061741F /* SynchronizationManagerTests.swift in Sources */, 6F388F5922E73F270080585F /* RichTextDocumentTransformableTest.xcdatamodeld in Sources */, ED25D7F51F177D8F00A6BA9A /* ComplexTest.xcdatamodeld in Sources */, @@ -1254,10 +1228,8 @@ files = ( 6D5CEDCB24FC24B1005E8B41 /* RelationshipsManager.swift in Sources */, 6D5CEDBE24FC2493005E8B41 /* RelationshipCache.swift in Sources */, - 6D5CEDC624FC2493005E8B41 /* ToOneRelationship.swift in Sources */, 6D87843824FCEBC7003E69CD /* RelationshipChildId.swift in Sources */, 6D5CEDC224FC2493005E8B41 /* Relationship.swift in Sources */, - 6D5CEDBA24FC2493005E8B41 /* ToManyRelationship.swift in Sources */, ED10E8D71E48BF1D0061741F /* CoreDataStore.swift in Sources */, ED8C1E731F603939001D059F /* SynchronizationManager+SeedDB.swift in Sources */, ED9C21FB1EF3DBD600882ABF /* PersistenceStore.swift in Sources */, @@ -1274,10 +1246,8 @@ files = ( 6D5CEDCC24FC24B1005E8B41 /* RelationshipsManager.swift in Sources */, 6D5CEDBF24FC2493005E8B41 /* RelationshipCache.swift in Sources */, - 6D5CEDC724FC2493005E8B41 /* ToOneRelationship.swift in Sources */, 6D87843924FCEBC7003E69CD /* RelationshipChildId.swift in Sources */, 6D5CEDC324FC2493005E8B41 /* Relationship.swift in Sources */, - 6D5CEDBB24FC2493005E8B41 /* ToManyRelationship.swift in Sources */, ED10E9021E48CD1E0061741F /* CoreDataStore.swift in Sources */, ED8C1E741F603939001D059F /* SynchronizationManager+SeedDB.swift in Sources */, ED9C21FC1EF3DBD600882ABF /* PersistenceStore.swift in Sources */, @@ -1294,10 +1264,8 @@ files = ( 6D5CEDCD24FC24B1005E8B41 /* RelationshipsManager.swift in Sources */, 6D5CEDC024FC2493005E8B41 /* RelationshipCache.swift in Sources */, - 6D5CEDC824FC2493005E8B41 /* ToOneRelationship.swift in Sources */, 6D87843A24FCEBC7003E69CD /* RelationshipChildId.swift in Sources */, 6D5CEDC424FC2493005E8B41 /* Relationship.swift in Sources */, - 6D5CEDBC24FC2493005E8B41 /* ToManyRelationship.swift in Sources */, ED10E9081E48CD1F0061741F /* CoreDataStore.swift in Sources */, ED8C1E751F603939001D059F /* SynchronizationManager+SeedDB.swift in Sources */, ED9C21FD1EF3DBD600882ABF /* PersistenceStore.swift in Sources */, @@ -1315,7 +1283,6 @@ EDD8F4E71F5401E90000D3BB /* ComplexSyncTests.swift in Sources */, EDD8F51B1F5402200000D3BB /* Post.swift in Sources */, 6F388F5E22E73FE60080585F /* RichTextDocumentRecord.swift in Sources */, - 6D87842C24FCD747003E69CD /* ToManyRelationshipTests.swift in Sources */, EDD8F4EB1F5401EF0000D3BB /* TestHelpers.swift in Sources */, EDD8F4E51F5401E50000D3BB /* SynchronizationManagerTests.swift in Sources */, 6FCA36BC22C65731004F9A5E /* RecordWithNonOptionalRelation.swift in Sources */, @@ -1342,7 +1309,6 @@ EDD8F5181F5402200000D3BB /* Author+CoreDataProperties.swift in Sources */, ED4023E720EA5858001C6BDD /* UnresolvedRelationshipCacheTests.swift in Sources */, EDD8F5311F5402E60000D3BB /* ComplexTest.xcdatamodeld in Sources */, - 6D87842824FCD62C003E69CD /* ToOneRelationshipTests.swift in Sources */, EDD8F51D1F5402200000D3BB /* SyncInfo.swift in Sources */, 6F388F5A22E73F270080585F /* RichTextDocumentTransformableTest.xcdatamodeld in Sources */, EDD8F5061F5402040000D3BB /* ComplexSyncInfo+CoreDataProperties.swift in Sources */, @@ -1361,7 +1327,6 @@ EDD8F4E81F5401E90000D3BB /* ComplexSyncTests.swift in Sources */, EDD8F5251F5402200000D3BB /* Post.swift in Sources */, 6F388F5F22E73FE60080585F /* RichTextDocumentRecord.swift in Sources */, - 6D87842D24FCD747003E69CD /* ToManyRelationshipTests.swift in Sources */, EDD8F4EC1F5401EF0000D3BB /* TestHelpers.swift in Sources */, EDD8F4E61F5401E50000D3BB /* SynchronizationManagerTests.swift in Sources */, 6FCA36BD22C65731004F9A5E /* RecordWithNonOptionalRelation.swift in Sources */, @@ -1388,7 +1353,6 @@ EDD8F5211F5402200000D3BB /* Author.swift in Sources */, ED4023E820EA5858001C6BDD /* UnresolvedRelationshipCacheTests.swift in Sources */, EDD8F5221F5402200000D3BB /* Author+CoreDataProperties.swift in Sources */, - 6D87842924FCD62C003E69CD /* ToOneRelationshipTests.swift in Sources */, EDD8F5271F5402200000D3BB /* SyncInfo.swift in Sources */, 6F388F5B22E73F270080585F /* RichTextDocumentTransformableTest.xcdatamodeld in Sources */, EDD8F50E1F5402040000D3BB /* ComplexSyncInfo+CoreDataProperties.swift in Sources */, diff --git a/Sources/ContentfulPersistence/Relationships/Relationship.swift b/Sources/ContentfulPersistence/Relationships/Relationship.swift index abf78cbd..bc81b027 100644 --- a/Sources/ContentfulPersistence/Relationships/Relationship.swift +++ b/Sources/ContentfulPersistence/Relationships/Relationship.swift @@ -2,25 +2,74 @@ // ContentfulPersistenceSwift // -enum Relationship: Codable { +import Contentful + +/// Represents a relationship between two entries. +struct Relationship: Codable, Equatable, Identifiable { + + typealias ID = String + typealias ParentId = String + typealias FieldName = String + typealias LocaleCode = String? + + let id: ID + let parentType: ContentTypeId + let parentId: ParentId + let fieldName: FieldName + let children: RelationshipChildren + + var localeCode: LocaleCode { + Self.localeCode(for: children) + } + + init(parentType: ContentTypeId, parentId: ParentId, fieldName: FieldName, childId: RelationshipChildId) { + self.init(parentType: parentType, parentId: parentId, fieldName: fieldName, children: .one(childId)) + } + + init(parentType: ContentTypeId, parentId: ParentId, fieldName: FieldName, childIds: [RelationshipChildId]) { + self.init(parentType: parentType, parentId: parentId, fieldName: fieldName, children: .many(childIds)) + } + + private init(parentType: ContentTypeId, parentId: ParentId, fieldName: FieldName, children: RelationshipChildren) { + self.parentType = parentType + self.parentId = parentId + self.fieldName = fieldName + self.children = children + self.id = [parentType, parentId, fieldName, Self.localeCode(for: children) ?? "-"].joined(separator: ",") + } + + private static func localeCode(for children: RelationshipChildren) -> LocaleCode { + switch children { + case .one(let childId): + return childId.localeCode + case .many(let childIds): + return childIds.first?.localeCode + } + } + +} + +enum RelationshipChildren: Codable, Equatable { private enum CodingKeys: CodingKey { - case type + case kind + case value } - enum Error: Swift.Error { - case invalidRelationship + private enum Kind: String, Codable { + case one + case many } - case toOne(ToOneRelationship) - case toMany(ToManyRelationship) + case one(RelationshipChildId) + case many([RelationshipChildId]) - func value() -> T? { + var elements: [RelationshipChildId] { switch self { - case .toOne(let relationship): - return relationship as? T - case .toMany(let relationship): - return relationship as? T + case .one(let relationshipChildId): + return [relationshipChildId] + case .many(let relationshipChildIds): + return relationshipChildIds } } @@ -28,30 +77,27 @@ enum Relationship: Codable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let typeString = try container.decode(String.self, forKey: .type) - - switch RelationshipType(rawValue: typeString) { - case .toOne: - self = try .toOne(ToOneRelationship(from: decoder)) - case .toMany: - self = try .toMany(ToManyRelationship(from: decoder)) - case .none: - throw Error.invalidRelationship + let kind = try container.decode(Kind.self, forKey: .kind) + + switch kind { + case .one: + self = .one(try container.decode(RelationshipChildId.self, forKey: .value)) + case .many: + self = .many(try container.decode([RelationshipChildId].self, forKey: .value)) } } func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { - case .toOne(let relationship): - try relationship.encode(to: encoder) - case .toMany(let relationship): - try relationship.encode(to: encoder) + case .one(let childId): + try container.encode(Kind.one, forKey: .kind) + try container.encode(childId, forKey: .value) + case .many(let childIds): + try container.encode(Kind.many, forKey: .kind) + try container.encode(childIds, forKey: .value) } } -} - -enum RelationshipType: String, Codable { - case toOne - case toMany } diff --git a/Sources/ContentfulPersistence/Relationships/RelationshipCache.swift b/Sources/ContentfulPersistence/Relationships/RelationshipCache.swift index 407a800f..6212fd68 100644 --- a/Sources/ContentfulPersistence/Relationships/RelationshipCache.swift +++ b/Sources/ContentfulPersistence/Relationships/RelationshipCache.swift @@ -74,11 +74,11 @@ final class RelationshipCache { private func loadFromCache() -> RelationshipData { do { - guard let localURL = cacheUrl() else { return .empty } + guard let localURL = cacheUrl() else { return .init() } let data = try Data(contentsOf: localURL, options: []) return try JSONDecoder().decode(RelationshipData.self, from: data) } catch { - return .empty + return .init() } } } diff --git a/Sources/ContentfulPersistence/Relationships/RelationshipChildId.swift b/Sources/ContentfulPersistence/Relationships/RelationshipChildId.swift index 67d03781..a0e4c2c0 100644 --- a/Sources/ContentfulPersistence/Relationships/RelationshipChildId.swift +++ b/Sources/ContentfulPersistence/Relationships/RelationshipChildId.swift @@ -2,10 +2,11 @@ // ContentfulPersistence // -struct RelationshipChildId: Codable, Equatable { +struct RelationshipChildId: RawRepresentable, Codable, Equatable { - /// Id of entry that may include locale code. - let value: String + typealias RawValue = String + + let rawValue: RawValue /// Id without locale code. let id: String @@ -13,15 +14,25 @@ struct RelationshipChildId: Codable, Equatable { /// Locale code associated with the id. let localeCode: String? - init(value: String) { - self.value = value - (self.id, self.localeCode) = value.splitToIdAndLocaleCode() + init(rawValue: String) { + self.rawValue = rawValue + (self.id, self.localeCode) = rawValue.splitToIdAndLocaleCode() + } + + init(id: String, localeCode: String?) { + self.rawValue = [id, localeCode] + .compactMap { $0 } + .joined(separator: "_") + + self.id = id + self.localeCode = localeCode } } private extension String { func splitToIdAndLocaleCode() -> (String, String?) { + if let index = self.firstIndex(of: "_") { let localeCodeStartIndex = self.index(index, offsetBy: 1) return (String(self[self.startIndex..]() - // quick access to all field/locale relationships by child id - private(set) internal var toOneRelationShiptsByEntryId = [ChildId: [FieldLocaleKey: ToOneRelationship]]() - private(set) internal var toManyRelationShipsbyEntryId = [ChildId: [FieldLocaleKey: ToManyRelationship.Id]]() - private var toManyRelationShips = [ToManyRelationship.Id: ToManyRelationship]() - private var toOneRelationShips = Set() + private struct RelationshipKeyPath: Codable, Hashable { - var isEmpty: Bool { - toOneRelationShips.isEmpty && toManyRelationShipsbyEntryId.isEmpty - } + var parentId: Relationship.ParentId + var fieldId: FieldId - var count: Int { - toOneRelationShips.count + toManyRelationShips.count - } - - static let empty: RelationshipData = .init() - - mutating func append(_ relationship: Relationship) { - switch relationship { - case .toMany(let nested): - - let relationShipId = nested.id - guard toManyRelationShips[relationShipId] == nil else { return } - - let fieldKey = FieldLocaleKey(parentId: nested.parentId, field: nested.fieldName, locale: nested.childIds.first?.localeCode) - - let childIds = Set(nested.childIds.map { $0.id }) - // append quick access cache - var current = childIdsByParent[nested.parentId] ?? Set() - current.formUnion(nested.childIds.map { $0.id }) - childIdsByParent[nested.parentId] = current - - - toManyRelationShips[relationShipId] = nested - - for childId in childIds { - var relationsByFieldAndLocale = toManyRelationShipsbyEntryId[childId] ?? [:] - relationsByFieldAndLocale[fieldKey] = relationShipId - toManyRelationShipsbyEntryId[childId] = relationsByFieldAndLocale - } + init(parentId: Relationship.ParentId, fieldName: Relationship.FieldName, localeCode: Relationship.LocaleCode) { + let fieldId = "\(fieldName),\(localeCode ?? "-")" + self.init(parentId: parentId, fieldId: fieldId) + } - case .toOne(let nested): - guard !toOneRelationShips.contains(nested.id) else { return } - toOneRelationShips.insert(nested.id) - - let fieldKey = FieldLocaleKey(parentId: nested.parentId, field: nested.fieldName, locale: nested.childId.localeCode) - // append quick access cache - - var current = childIdsByParent[nested.parentId] ?? Set() - current.insert(nested.childId.id) - - childIdsByParent[nested.parentId] = current - - var relationsByFieldAndLocale = toOneRelationShiptsByEntryId[nested.childId.id] ?? [:] - relationsByFieldAndLocale[fieldKey] = nested - toOneRelationShiptsByEntryId[nested.childId.id] = relationsByFieldAndLocale + init(parentId: Relationship.ParentId, fieldId: FieldId) { + self.parentId = parentId + self.fieldId = fieldId } + } - mutating func delete(parentId: ParentId) { - guard var childIdsByParent = childIdsByParent[parentId] else { return } + var count: Int { + relationships.reduce(0) { $0 + $1.value.count } + } - for childId in childIdsByParent { + var isEmpty: Bool { + relationships.isEmpty + } - var emptyToOne = false - var emptyToMany = false + private var relationships: [Relationship.ParentId: [FieldId: Relationship]] = [:] + private var relationshipKeyPathsByChild: [RelationshipChildId.RawValue: Set] = [:] - if var toOne = toOneRelationShiptsByEntryId[childId] { - let keyForParent = toOne.keys.filter { $0.parentId == parentId } - keyForParent.forEach { - if let relation = toOne[$0] { - toOne[$0] = nil - toOneRelationShips.remove(relation.id) - } - } - emptyToOne = toOne.isEmpty - toOneRelationShiptsByEntryId[childId] = emptyToOne ? nil : toOne - } else { - emptyToOne = true - } + mutating func append(_ relationship: Relationship) { + let keyPath = Self.keyPath(for: relationship) + setRelationship(relationship, for: keyPath) + } - if var toMany = toManyRelationShipsbyEntryId[childId] { - let keyForParent = toMany.keys.filter { $0.parentId == parentId } - keyForParent.forEach { - if let relationId = toMany[$0] { - toMany[$0] = nil - toManyRelationShips.removeValue(forKey: relationId) - } - } - emptyToMany = toMany.isEmpty - toManyRelationShipsbyEntryId[childId] = emptyToMany ? nil : toMany - } else { - emptyToMany = true - } + mutating func delete(parentId: Relationship.ParentId) { + let keyPaths = relationships[parentId]?.keys + .map { RelationshipKeyPath(parentId: parentId, fieldId: $0) } ?? [] - // remove unused parent - if emptyToOne && emptyToMany { - childIdsByParent.remove(childId) - } + for keyPath in keyPaths { + setRelationship(nil, for: keyPath) } - - self.childIdsByParent[parentId] = childIdsByParent.isEmpty ? nil : childIdsByParent } - mutating func delete(parentId: ParentId, fieldName: String, localeCode: String?) { + mutating func delete(parentId: Relationship.ParentId, fieldName: Relationship.FieldName, localeCode: Relationship.LocaleCode) { + let keyPath = RelationshipKeyPath(parentId: parentId, fieldName: fieldName, localeCode: localeCode) + setRelationship(nil, for: keyPath) + } - guard var childIdsByParent = childIdsByParent[parentId] else { return } + func relationships(for childId: RelationshipChildId) -> [Relationship] { + relationshipKeyPathsByChild[childId.rawValue]? + .compactMap(relationship) ?? [] + } - let fieldKey = FieldLocaleKey(parentId: parentId, field: fieldName, locale: localeCode) + private func relationship(keyPath: RelationshipKeyPath) -> Relationship? { + relationships[keyPath.parentId]?[keyPath.fieldId] + } - for childId in childIdsByParent { - var emptyToOne = false - var emptyToMany = false + private mutating func setRelationship(_ relationship: Relationship?, for keyPath: RelationshipKeyPath) { + var relationshipsByFieldIdentifier = relationships[keyPath.parentId] ?? [:] - if var toOne = toOneRelationShiptsByEntryId[childId] { - if let relation = toOne[fieldKey] { - toOne[fieldKey] = nil - toOneRelationShips.remove(relation.id) - } - emptyToOne = toOne.isEmpty - toOneRelationShiptsByEntryId[childId] = emptyToOne ? nil : toOne - } else { - emptyToOne = true - } + let newChildIds = Set(relationship?.children.elements.map { $0.id } ?? []) - if var toMany = toManyRelationShipsbyEntryId[childId] { - if let relationId = toMany[fieldKey] { - toMany[fieldKey] = nil - toManyRelationShips.removeValue(forKey: relationId) - } - emptyToMany = toMany.isEmpty - toManyRelationShipsbyEntryId[childId] = emptyToMany ? nil : toMany - } else { - emptyToMany = true - } + if let existingRelationship = relationshipsByFieldIdentifier[keyPath.fieldId] { + let existingChildIds = Set(existingRelationship.children.elements.map { $0.id }) + let removedChildIds = existingChildIds.subtracting(newChildIds) - // remove unused parent - if emptyToOne && emptyToMany { - childIdsByParent.remove(childId) + for childId in removedChildIds { + if var keyPaths = relationshipKeyPathsByChild[childId] { + keyPaths.remove(keyPath) + relationshipKeyPathsByChild[childId] = keyPaths + } } } - self.childIdsByParent[parentId] = childIdsByParent.isEmpty ? nil : childIdsByParent - } + for childId in newChildIds { + var keyPaths = relationshipKeyPathsByChild[childId] ?? Set() + keyPaths.insert(keyPath) + relationshipKeyPathsByChild[childId] = keyPaths + } - func findToOne(childId: ChildId, localeCode: String?) -> [ToOneRelationship] { - guard let relations = toOneRelationShiptsByEntryId[childId] else { return [] } + relationshipsByFieldIdentifier[keyPath.fieldId] = relationship - return relations.keys - .filter { $0.locale == localeCode } - .compactMap { relations[$0] } + if relationshipsByFieldIdentifier.isEmpty { + relationships[keyPath.parentId] = nil + } else { + relationships[keyPath.parentId] = relationshipsByFieldIdentifier + } } - func findToMany(childId: ChildId, localeCode: String?) -> [ToManyRelationship] { - guard let relations = toManyRelationShipsbyEntryId[childId] else { return [] } - - return relations.keys - .filter { $0.locale == localeCode } - .compactMap { relations[$0] } - .compactMap { toManyRelationShips[$0] } + private static func keyPath(for relationship: Relationship) -> RelationshipKeyPath { + RelationshipKeyPath(parentId: relationship.parentId, fieldName: relationship.fieldName, localeCode: relationship.localeCode) } + } diff --git a/Sources/ContentfulPersistence/Relationships/RelationshipsManager.swift b/Sources/ContentfulPersistence/Relationships/RelationshipsManager.swift index 363a0ee5..760620bc 100644 --- a/Sources/ContentfulPersistence/Relationships/RelationshipsManager.swift +++ b/Sources/ContentfulPersistence/Relationships/RelationshipsManager.swift @@ -21,38 +21,37 @@ final class RelationshipsManager { /// Creates one-to-one relationship if does not exist yet. func cacheToOneRelationship( parent: EntryPersistable, - childId: String, + childId: RelationshipChildId, fieldName: String ) { let parentType = type(of: parent).contentTypeId - let relationship = ToOneRelationship( + let relationship = Relationship( parentType: parentType, parentId: parent.id, fieldName: fieldName, - childId: .init(value: childId) + childId: childId ) - cache.add(relationship: .toOne(relationship)) + cache.add(relationship: relationship) } func cacheToManyRelationship( parent: EntryPersistable, - childIds: [String], + childIds: [RelationshipChildId], fieldName: String ) { - let theChildIds: [RelationshipChildId] = childIds.map { .init(value: $0) } let parentType = type(of: parent).contentTypeId - let relationship = ToManyRelationship( + let relationship = Relationship( parentType: parentType, parentId: parent.id, fieldName: fieldName, - childIds: theChildIds + childIds: childIds ) - cache.add(relationship: .toMany(relationship)) + cache.add(relationship: relationship) } func delete(parentId: String) { diff --git a/Sources/ContentfulPersistence/Relationships/ToManyRelationship.swift b/Sources/ContentfulPersistence/Relationships/ToManyRelationship.swift deleted file mode 100644 index 74721b64..00000000 --- a/Sources/ContentfulPersistence/Relationships/ToManyRelationship.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// ContentfulPersistence -// - -/// Represents one-to-many relationship between two entries. -struct ToManyRelationship: Codable, Equatable, Hashable { - typealias Id = String - let id: Id - let type: RelationshipType - - /// `EntryPersistable.contentTypeId` - let parentType: String - - let parentId: String - let fieldName: String - let childIds: [RelationshipChildId] - - internal init(parentType: String, parentId: String, fieldName: String, childIds: [RelationshipChildId]) { - self.parentType = parentType - self.parentId = parentId - self.fieldName = fieldName - self.childIds = childIds - self.id = ([parentType, parentId, fieldName, childIds.first?.localeCode ?? "-"] + childIds.map { $0.id }.sorted()).joined(separator: ",") - self.type = .toMany - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} diff --git a/Sources/ContentfulPersistence/Relationships/ToOneRelationship.swift b/Sources/ContentfulPersistence/Relationships/ToOneRelationship.swift deleted file mode 100644 index 16beb907..00000000 --- a/Sources/ContentfulPersistence/Relationships/ToOneRelationship.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// ContentfulPersistence -// - -/// Represents one-to-one between two elements. -struct ToOneRelationship: Codable, Equatable, Identifiable, Hashable { - typealias Id = String - let id: Id - let type: RelationshipType - - /// `EntryPersistable.contentTypeId` - let parentType: String - - let parentId: String - let fieldName: String - let childId: RelationshipChildId - - internal init(parentType: String, parentId: String, fieldName: String, childId: RelationshipChildId) { - self.parentType = parentType - self.parentId = parentId - self.fieldName = fieldName - self.childId = childId - self.id = [parentType, parentId, fieldName, childId.id, childId.localeCode ?? "-"].joined(separator: ",") - self.type = .toOne - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} diff --git a/Sources/ContentfulPersistence/SynchronizationManager.swift b/Sources/ContentfulPersistence/SynchronizationManager.swift index 3fca3910..adbe6df9 100644 --- a/Sources/ContentfulPersistence/SynchronizationManager.swift +++ b/Sources/ContentfulPersistence/SynchronizationManager.swift @@ -255,9 +255,11 @@ public class SynchronizationManager: PersistenceIntegration { for (fieldName, relatedResourceId) in fields { // Resolve one-to-one link. if let identifier = relatedResourceId as? String { + let childId = RelationshipChildId(rawValue: identifier) + relationshipsManager.cacheToOneRelationship( parent: entryPersistable, - childId: identifier, + childId: childId, fieldName: fieldName ) @@ -269,9 +271,11 @@ public class SynchronizationManager: PersistenceIntegration { // Resolve one-to-many links array. if let identifiers = relatedResourceId as? [String] { + let childIds = identifiers.map(RelationshipChildId.init) + relationshipsManager.cacheToManyRelationship( parent: entryPersistable, - childIds: identifiers, + childIds: childIds, fieldName: fieldName ) @@ -311,45 +315,28 @@ public class SynchronizationManager: PersistenceIntegration { /// Find and update relationships where the entry should be set as a child. private func updateRelationships(with entry: EntryPersistable, cache: DataCache) { - updateToOneRelationships(with: entry, cache: cache) - updateToManyRelationships(with: entry, cache: cache) - } - - private func updateToOneRelationships(with entry: EntryPersistable, cache: DataCache) { - let filteredRelationships: [ToOneRelationship] = relationshipsManager.relationships.findToOne( - childId: entry.id, - localeCode: entry.localeCode - ) - - for relationship in filteredRelationships { - guard let parent = cache.entry(for: DataCache.cacheKey(for: relationship.parentId, localeCode: entry.localeCode)) else { - return - } - parent.setValue(entry, forKey: relationship.fieldName) - } - } - - private func updateToManyRelationships(with entry: EntryPersistable, cache: DataCache) { - let filteredRelationships: [ToManyRelationship] = relationshipsManager.relationships.findToMany( - childId: entry.id, - localeCode: entry.localeCode - ) + let filteredRelationships = relationshipsManager.relationships.relationships(for: .init(id: entry.id, localeCode: entry.localeCode)) for relationship in filteredRelationships { guard let parent = cache.entry(for: DataCache.cacheKey(for: relationship.parentId, localeCode: entry.localeCode)) else { return } - guard let collection = parent.value(forKey: relationship.fieldName) else { continue } - - if let set = collection as? NSSet { - let mutableSet = NSMutableSet(set: set) - mutableSet.add(entry) - parent.setValue(NSSet(set: mutableSet), forKey: relationship.fieldName) - } else if let set = collection as? NSOrderedSet { - var array = set.array - array.append(entry) - parent.setValue(NSOrderedSet(array: array), forKey: relationship.fieldName) + switch relationship.children { + case .one: + parent.setValue(entry, forKey: relationship.fieldName) + case .many: + guard let collection = parent.value(forKey: relationship.fieldName) else { continue } + + if let set = collection as? NSSet { + let mutableSet = NSMutableSet(set: set) + mutableSet.add(entry) + parent.setValue(NSSet(set: mutableSet), forKey: relationship.fieldName) + } else if let set = collection as? NSOrderedSet { + var array = set.array + array.append(entry) + parent.setValue(NSOrderedSet(array: array), forKey: relationship.fieldName) + } } } } diff --git a/Tests/ContentfulPersistenceTests/Relationships/RelationshipCacheTests.swift b/Tests/ContentfulPersistenceTests/Relationships/RelationshipCacheTests.swift index b99bd8c6..f4a01fae 100644 --- a/Tests/ContentfulPersistenceTests/Relationships/RelationshipCacheTests.swift +++ b/Tests/ContentfulPersistenceTests/Relationships/RelationshipCacheTests.swift @@ -13,16 +13,16 @@ class RelationshipCacheTests: XCTestCase { XCTAssertEqual(cache.relationships.count, 0) - cache.add(relationship: .toOne(makeToOne1())) - cache.add(relationship: .toOne(makeToOne2())) - cache.add(relationship: .toMany(makeToMany1())) + cache.add(relationship: makeToOne1()) + cache.add(relationship: makeToOne2()) + cache.add(relationship: makeToMany1()) cache.save() // Verify let verifyCache = RelationshipCache(cacheFileName: fileName) - verifyCache.add(relationship: .toOne(makeToOne1(localeCode: "en-US"))) + verifyCache.add(relationship: makeToOne1(localeCode: "en-US")) XCTAssertEqual(verifyCache.relationships.count, 4) } @@ -33,13 +33,13 @@ class RelationshipCacheTests: XCTestCase { XCTAssertEqual(cache.relationships.count, 0) - cache.add(relationship: .toOne(makeToOne1())) + cache.add(relationship: makeToOne1()) let toOne2 = makeToOne2() - cache.add(relationship: .toOne(toOne2)) + cache.add(relationship: toOne2) let toMany1 = makeToMany1() - cache.add(relationship: .toMany(toMany1)) + cache.add(relationship: toMany1) XCTAssertEqual(cache.relationships.count, 3) @@ -49,13 +49,13 @@ class RelationshipCacheTests: XCTestCase { cache.delete(parentId: toMany1.parentId) XCTAssertEqual(cache.relationships.count, 1) - cache.add(relationship: .toOne(toOne2)) - cache.add(relationship: .toMany(toMany1)) + cache.add(relationship: toOne2) + cache.add(relationship: toMany1) cache.delete( parentId: toOne2.parentId, fieldName: toOne2.fieldName, - localeCode: toOne2.childId.localeCode + localeCode: toOne2.localeCode ) XCTAssertEqual(cache.relationships.count, 2) @@ -63,7 +63,7 @@ class RelationshipCacheTests: XCTestCase { cache.delete( parentId: toMany1.parentId, fieldName: toMany1.fieldName, - localeCode: toMany1.childIds.first?.localeCode + localeCode: toMany1.localeCode ) XCTAssertEqual(cache.relationships.count, 1) @@ -77,8 +77,8 @@ class RelationshipCacheTests: XCTestCase { let toOne1a = makeToOne1(localeCode: "en-US") let toOne1b = makeToOne1(localeCode: "pl-PL") - cache.add(relationship: .toOne(toOne1a)) - cache.add(relationship: .toOne(toOne1b)) + cache.add(relationship: toOne1a) + cache.add(relationship: toOne1b) cache.delete( parentId: toOne1a.parentId, @@ -99,7 +99,7 @@ class RelationshipCacheTests: XCTestCase { cache.delete( parentId: toOne1a.parentId, fieldName: toOne1a.fieldName, - localeCode: toOne1a.childId.localeCode + localeCode: toOne1a.localeCode ) XCTAssertEqual(cache.relationships.count, 1) @@ -107,13 +107,13 @@ class RelationshipCacheTests: XCTestCase { cache.delete( parentId: toOne1b.parentId, fieldName: toOne1b.fieldName, - localeCode: toOne1b.childId.localeCode + localeCode: toOne1b.localeCode ) XCTAssertEqual(cache.relationships.count, 0) } - private func makeToOne1(localeCode: String? = nil) -> ToOneRelationship { + private func makeToOne1(localeCode: String? = nil) -> Relationship { var childId = "dog-1" if let localeCode = localeCode { childId += "_\(localeCode)" @@ -123,27 +123,27 @@ class RelationshipCacheTests: XCTestCase { parentType: "person", parentId: "person-1", fieldName: "dog", - childId: .init(value: childId) + childId: .init(rawValue: childId) ) } - private func makeToOne2() -> ToOneRelationship { + private func makeToOne2() -> Relationship { .init( parentType: "person", parentId: "person-2", fieldName: "cat", - childId: .init(value: "cat-1") + childId: .init(rawValue: "cat-1") ) } - private func makeToMany1() -> ToManyRelationship { + private func makeToMany1() -> Relationship { .init( parentType: "person", parentId: "person-3", fieldName: "things", childIds: [ - .init(value: "cat-1"), - .init(value: "dog-2") + .init(rawValue: "cat-1"), + .init(rawValue: "dog-2") ] ) } diff --git a/Tests/ContentfulPersistenceTests/Relationships/RelationshipChildIdTests.swift b/Tests/ContentfulPersistenceTests/Relationships/RelationshipChildIdTests.swift index 36886ddf..03b2ae84 100644 --- a/Tests/ContentfulPersistenceTests/Relationships/RelationshipChildIdTests.swift +++ b/Tests/ContentfulPersistenceTests/Relationships/RelationshipChildIdTests.swift @@ -13,7 +13,7 @@ class RelationshipChildIdTests: XCTestCase { let value = "\(id)_\(localeCode)" - let childId = RelationshipChildId(value: value) + let childId = RelationshipChildId(rawValue: value) XCTAssertEqual(childId.id, id) XCTAssertEqual(childId.localeCode, localeCode) } @@ -21,7 +21,7 @@ class RelationshipChildIdTests: XCTestCase { func test_id_isSet() { let id = "abc-def" - let childId = RelationshipChildId(value: id) + let childId = RelationshipChildId(rawValue: id) XCTAssertEqual(childId.id, id) XCTAssertNil(childId.localeCode) } diff --git a/Tests/ContentfulPersistenceTests/Relationships/RelationshipManagerTests.swift b/Tests/ContentfulPersistenceTests/Relationships/RelationshipManagerTests.swift index ccc325e9..1ffabb2f 100644 --- a/Tests/ContentfulPersistenceTests/Relationships/RelationshipManagerTests.swift +++ b/Tests/ContentfulPersistenceTests/Relationships/RelationshipManagerTests.swift @@ -16,7 +16,7 @@ class RelationshipManagerTests: XCTestCase { for _ in 0..<5 { manager.cacheToOneRelationship( parent: entry1, - childId: "dog-1", + childId: RelationshipChildId(rawValue: "dog-1"), fieldName: "dog" ) } @@ -28,7 +28,7 @@ class RelationshipManagerTests: XCTestCase { for _ in 0..<5 { manager.cacheToOneRelationship( parent: entry2, - childId: "dog-1", + childId: RelationshipChildId(rawValue: "dog-1"), fieldName: "dog" ) } @@ -39,9 +39,9 @@ class RelationshipManagerTests: XCTestCase { manager.cacheToManyRelationship( parent: entry1, childIds: [ - "cat-1", - "cat-2", - "cat-3" + RelationshipChildId(rawValue: "cat-1"), + RelationshipChildId(rawValue: "cat-2"), + RelationshipChildId(rawValue: "cat-3") ], fieldName: "cats" ) @@ -59,39 +59,68 @@ class RelationshipManagerTests: XCTestCase { XCTAssertEqual(manager.relationships.count, 0) } - private func makeToOne1(localeCode: String? = nil) -> ToOneRelationship { - var childId = "dog-1" - if let localeCode = localeCode { - childId += "_\(localeCode)" - } + func testStaleToOneRelationshipsAreRemoved() { + let manager = RelationshipsManager(cacheFileName: makeFileName()) + + let entry1 = EntryA(id: "person-1") + let dog1 = RelationshipChildId(rawValue: "dog-1") + let dog2 = RelationshipChildId(rawValue: "dog-2") - return .init( - parentType: "person", - parentId: "person-1", - fieldName: "dog", - childId: .init(value: childId) + manager.cacheToOneRelationship( + parent: entry1, + childId: dog1, + fieldName: "dog" ) - } - private func makeToOne2() -> ToOneRelationship { - .init( - parentType: "person", - parentId: "person-2", - fieldName: "cat", - childId: .init(value: "cat-1") + XCTAssertNotNil(manager.relationships.relationships(for: dog1)) + + manager.cacheToOneRelationship( + parent: entry1, + childId: dog2, + fieldName: "dog" ) + + XCTAssertTrue(manager.relationships.relationships(for: dog1).isEmpty) + XCTAssertFalse(manager.relationships.relationships(for: dog2).isEmpty) } - private func makeToMany1() -> ToManyRelationship { - .init( - parentType: "person", - parentId: "person-3", - fieldName: "things", + func testStaleToManyRelationshipsAreRemoved() { + let manager = RelationshipsManager(cacheFileName: makeFileName()) + + let entry1 = EntryA(id: "person-1") + let cat1 = RelationshipChildId(rawValue: "cat-1") + let cat2 = RelationshipChildId(rawValue: "cat-2") + let cat3 = RelationshipChildId(rawValue: "cat-3") + let cat4 = RelationshipChildId(rawValue: "cat-4") + + manager.cacheToManyRelationship( + parent: entry1, + childIds: [ + cat1, + cat2, + cat3 + ], + fieldName: "cats" + ) + + XCTAssertFalse(manager.relationships.relationships(for: cat1).isEmpty) + XCTAssertFalse(manager.relationships.relationships(for: cat2).isEmpty) + XCTAssertFalse(manager.relationships.relationships(for: cat3).isEmpty) + + manager.cacheToManyRelationship( + parent: entry1, childIds: [ - .init(value: "cat-1"), - .init(value: "dog-2") - ] + cat1, + cat2, + cat4 + ], + fieldName: "cats" ) + + XCTAssertFalse(manager.relationships.relationships(for: cat1).isEmpty) + XCTAssertFalse(manager.relationships.relationships(for: cat2).isEmpty) + XCTAssertTrue(manager.relationships.relationships(for: cat3).isEmpty) + XCTAssertFalse(manager.relationships.relationships(for: cat4).isEmpty) } private func makeFileName() -> String { diff --git a/Tests/ContentfulPersistenceTests/Relationships/RelationshipTests.swift b/Tests/ContentfulPersistenceTests/Relationships/RelationshipTests.swift index b75a5559..c98bfd11 100644 --- a/Tests/ContentfulPersistenceTests/Relationships/RelationshipTests.swift +++ b/Tests/ContentfulPersistenceTests/Relationships/RelationshipTests.swift @@ -7,31 +7,30 @@ import XCTest class RelationshipTests: XCTestCase { - func testToOneRelationshipValue() { - let nested = ToOneRelationship( - parentType: "1", - parentId: "2", - fieldName: "3", - childId: .init(value: "4") + func testInitWithOneChild() { + let child1 = RelationshipChildId(id: "child1", localeCode: nil) + let relationship = Relationship( + parentType: "parentType", + parentId: "parentId", + fieldName: "fieldName", + childId: child1 ) - let relationship = Relationship.toOne(nested) - - let value: ToOneRelationship? = relationship.value() - XCTAssertEqual(value, nested) + XCTAssertEqual(relationship.children, .one(child1)) } - func testToManyRelationshipValue() { - let nested = ToManyRelationship( - parentType: "1", - parentId: "2", - fieldName: "3", - childIds: [.init(value: "4"), .init(value: "5")] + func testInitWithManyChildren() { + let child1 = RelationshipChildId(id: "child1", localeCode: nil) + let child2 = RelationshipChildId(id: "child2", localeCode: nil) + let child3 = RelationshipChildId(id: "child3", localeCode: nil) + let relationship = Relationship( + parentType: "parentType", + parentId: "parentId", + fieldName: "fieldName", + childIds: [child1, child2, child3] ) - let relationship = Relationship.toMany(nested) - - let value: ToManyRelationship? = relationship.value() - XCTAssertEqual(value, nested) + XCTAssertEqual(relationship.children, .many([child1, child2, child3])) } + } diff --git a/Tests/ContentfulPersistenceTests/Relationships/ToManyRelationshipTests.swift b/Tests/ContentfulPersistenceTests/Relationships/ToManyRelationshipTests.swift deleted file mode 100644 index b7b02d02..00000000 --- a/Tests/ContentfulPersistenceTests/Relationships/ToManyRelationshipTests.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// ContentfulPersistence -// - -@testable import ContentfulPersistence -import XCTest - -class ToManyRelationshipTests: XCTestCase { - - func testRelationship() { - let relationship = ToManyRelationship( - parentType: "1", - parentId: "2", - fieldName: "3", - childIds: [.init(value: "4"), .init(value: "5")] - ) - - XCTAssertEqual(relationship.type, RelationshipType.toMany) - } -} diff --git a/Tests/ContentfulPersistenceTests/Relationships/ToOneRelationshipTests.swift b/Tests/ContentfulPersistenceTests/Relationships/ToOneRelationshipTests.swift deleted file mode 100644 index 8ef56fc8..00000000 --- a/Tests/ContentfulPersistenceTests/Relationships/ToOneRelationshipTests.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// ContentfulPersistence -// - -@testable import ContentfulPersistence -import XCTest - -class ToOneRelationshipTests: XCTestCase { - - func testRelationship() { - let relationship = ToOneRelationship( - parentType: "1", - parentId: "2", - fieldName: "3", - childId: .init(value: "4") - ) - - XCTAssertEqual(relationship.type, RelationshipType.toOne) - } -}