From b4db7aca2de2b89b3ec25509e96e9f8de368a956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20He=C3=9F?= Date: Thu, 15 Feb 2024 16:53:38 +0100 Subject: [PATCH] Fix Codable&RawRepresentable properties Should fix issue #29 This was missing overloads for RawRepresentable & Codable, not sure why this didn't produce an ambiguity warning. --- .../PersistentModel/PersistentModel+KVC.swift | 22 +++++ ...est.swift => CodablePropertiesTests.swift} | 1 + .../CodableRawRepresentableTests.swift | 44 +++++++++ .../Schemas/ToDoListSchema.swift | 91 +++++++++++++++++++ 4 files changed, 158 insertions(+) rename Tests/ManagedModelTests/{CodablePropertiesTest.swift => CodablePropertiesTests.swift} (99%) create mode 100644 Tests/ManagedModelTests/CodableRawRepresentableTests.swift create mode 100644 Tests/ManagedModelTests/Schemas/ToDoListSchema.swift diff --git a/Sources/ManagedModels/PersistentModel/PersistentModel+KVC.swift b/Sources/ManagedModels/PersistentModel/PersistentModel+KVC.swift index c83eb0d..62d1bfb 100644 --- a/Sources/ManagedModels/PersistentModel/PersistentModel+KVC.swift +++ b/Sources/ManagedModels/PersistentModel/PersistentModel+KVC.swift @@ -223,6 +223,28 @@ public extension PersistentModel { } return wrapped } + + // Overloads for RawRepresentables that are ALSO Codable + + @inlinable + func setValue(forKey key: String, to value: T) + where T: RawRepresentable & Codable, + T.RawValue: Codable & CoreDataPrimitiveValue + { + setValue(forKey: key, to: value.rawValue) + } + + @inlinable + func getValue(forKey key: String) -> T + where T: RawRepresentable & Codable, + T.RawValue: Codable & CoreDataPrimitiveValue + { + let rawValue : T.RawValue = getValue(forKey: key) + guard let wrapped = T.init(rawValue: rawValue) else { + fatalError("Could not wrap raw value \(rawValue) for \(key)") + } + return wrapped + } } // MARK: - Codable diff --git a/Tests/ManagedModelTests/CodablePropertiesTest.swift b/Tests/ManagedModelTests/CodablePropertiesTests.swift similarity index 99% rename from Tests/ManagedModelTests/CodablePropertiesTest.swift rename to Tests/ManagedModelTests/CodablePropertiesTests.swift index 7639fef..4be42f7 100644 --- a/Tests/ManagedModelTests/CodablePropertiesTest.swift +++ b/Tests/ManagedModelTests/CodablePropertiesTests.swift @@ -16,6 +16,7 @@ final class CodablePropertiesTests: XCTestCase { ) func testEntityName() throws { + _ = container let entityType = Fixtures.CodablePropertiesSchema.StoredAccess.self XCTAssertEqual(entityType.entity().name, "StoredAccess") } diff --git a/Tests/ManagedModelTests/CodableRawRepresentableTests.swift b/Tests/ManagedModelTests/CodableRawRepresentableTests.swift new file mode 100644 index 0000000..b3eb989 --- /dev/null +++ b/Tests/ManagedModelTests/CodableRawRepresentableTests.swift @@ -0,0 +1,44 @@ +// +// Created by Helge Heß. +// Copyright © 2024 ZeeZide GmbH. +// + +import XCTest +import Foundation +import CoreData +@testable import ManagedModels + +final class CodableRawRepresentableTests: XCTestCase { + // https://github.com/Data-swift/ManagedModels/issues/29 + + private lazy var container = try? ModelContainer( + for: Fixtures.ToDoListSchema.managedObjectModel, + configurations: ModelConfiguration(isStoredInMemoryOnly: true) + ) + + func testEntityName() throws { + _ = container // required to register the entity type mapping + let entityType = Fixtures.ToDoListSchema.ToDo.self + XCTAssertEqual(entityType.entity().name, "ToDo") + } + + func testPropertySetup() throws { + let valueType = Fixtures.ToDoListSchema.ToDo.Priority.self + let attribute = CoreData.NSAttributeDescription( + name: "priority", + valueType: valueType, + defaultValue: nil + ) + XCTAssertEqual(attribute.name, "priority") + XCTAssertEqual(attribute.attributeType, .integer64AttributeType) + + XCTAssertTrue(attribute.valueType == Int.self) + XCTAssertNil(attribute.valueTransformerName) + } + + func testModel() throws { + _ = container // required to register the entity type mapping + let todo = Fixtures.ToDoListSchema.ToDo() + todo.priority = .high + } +} diff --git a/Tests/ManagedModelTests/Schemas/ToDoListSchema.swift b/Tests/ManagedModelTests/Schemas/ToDoListSchema.swift new file mode 100644 index 0000000..4d4e1de --- /dev/null +++ b/Tests/ManagedModelTests/Schemas/ToDoListSchema.swift @@ -0,0 +1,91 @@ +// +// Created by Helge Heß. +// Copyright © 2024 ZeeZide GmbH. +// + +import ManagedModels + +extension Fixtures { + + enum ToDoListSchema: VersionedSchema { + static var models : [ any PersistentModel.Type ] = [ + ToDo.self, ToDoList.self + ] + + public static let versionIdentifier = Schema.Version(0, 1, 0) + + @Model + final class ToDo: NSManagedObject { + + var title : String + var isDone : Bool + var priority : Priority + var created : Date + var due : Date? + var list : ToDoList + + enum Priority: Int, Comparable, CaseIterable, Codable { + case veryLow = 1 + case low = 2 + case medium = 3 + case high = 4 + case veryHigh = 5 + + static func < (lhs: Self, rhs: Self) -> Bool { + lhs.rawValue < rhs.rawValue + } + } + + convenience init(list : ToDoList, + title : String, + isDone : Bool = false, + priority : Priority = .medium, + created : Date = Date(), + due : Date? = nil) + { + // This is important so that the objects don't end up in different + // contexts. + self.init(context: list.modelContext) + + self.list = list + self.title = title + self.isDone = isDone + self.priority = priority + self.created = created + self.due = due + } + + var isOverDue : Bool { + guard let due else { return false } + return due < Date() + } + } + + @Model + final class ToDoList: NSManagedObject { + + var title = "" + var toDos = [ ToDo ]() + + convenience init(title: String) { + self.init() + self.title = title + } + + var hasOverdueItems : Bool { toDos.contains { $0.isOverDue && !$0.isDone } } + + enum Doneness { case all, none, some } + + var doneness : Doneness { + let hasDone = toDos.contains { $0.isDone } + let hasUndone = toDos.contains { !$0.isDone } + switch ( hasDone, hasUndone ) { + case ( true , true ) : return .some + case ( true , false ) : return .all + case ( false , true ) : return .none + case ( false , false ) : return .all // empty + } + } + } + } +}