Skip to content

Commit

Permalink
Adding Queryable (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
leogdion authored Oct 16, 2024
1 parent d450301 commit d5a3068
Show file tree
Hide file tree
Showing 44 changed files with 922 additions and 104 deletions.
28 changes: 28 additions & 0 deletions Example/Sources/ChildViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// ChildViewModel.swift
// DataThespian
//
// Created by Leo Dion on 10/16/24.
//

import DataThespian
import Foundation
import SwiftData

internal struct ChildViewModel: Sendable, Identifiable {
internal let model: Model<ItemChild>
internal let timestamp: Date

internal var id: PersistentIdentifier {
model.persistentIdentifier
}

private init(model: Model<ItemChild>, timestamp: Date) {
self.model = model
self.timestamp = timestamp
}

internal init(child: ItemChild) {
self.init(model: .init(child), timestamp: child.timestamp)
}
}
62 changes: 39 additions & 23 deletions Example/Sources/ContentObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ internal class ContentObject {
private var databaseChangeCancellable: AnyCancellable?
private var databaseChangeSubscription: AnyCancellable?
private var database: (any Database)?
internal private(set) var items = [ItemModel]()
internal var selectedItemsID: Set<ItemModel.ID> = []
internal private(set) var items = [ItemViewModel]()
internal var selectedItemsID: Set<ItemViewModel.ID> = []
private var newItem: AnyCancellable?
internal var error: (any Error)?

internal var selectedItems: [ItemModel] {
internal var selectedItems: [ItemViewModel] {
let selectedItemsID = self.selectedItemsID
let items: [ItemModel]
let items: [ItemViewModel]
do {
items = try self.items.filter(
#Predicate<ItemModel> {
#Predicate<ItemViewModel> {
selectedItemsID.contains($0.id)
}
)
Expand All @@ -49,17 +49,7 @@ internal class ContentObject {
private static func deleteModels(_ models: [Model<Item>], from database: (any Database))
async throws
{
try await database.withModelContext { modelContext in
let items: [Item] = models.compactMap {
modelContext.model(for: $0.persistentIdentifier) as? Item
}
dump(items.first?.persistentModelID)
assert(items.count == models.count)
for item in items {
modelContext.delete(item)
}
try modelContext.save()
}
try await database.deleteModels(models)
}

private func beginUpdateItems() {
Expand All @@ -76,10 +66,10 @@ internal class ContentObject {
guard let database else {
return
}
self.items = try await database.withModelContext({ modelContext in
self.items = try await database.withModelContext { modelContext in
let items = try modelContext.fetch(FetchDescriptor<Item>())
return items.map(ItemModel.init)
})
return items.map(ItemViewModel.init)
}
}

internal func initialize(
Expand Down Expand Up @@ -114,20 +104,46 @@ internal class ContentObject {
}
Task {
try await Self.deleteModels(models, from: database)
try await database.save()
}
}

internal func addItem(withDate date: Date = .init()) {
internal func addChild(to item: ItemViewModel) {
guard let database else {
return
}
Task {
let timestamp = Date()
let childModel = await database.insert {
ItemChild(timestamp: timestamp)
}

try await database.withModelContext { modelContext in
let newItem = Item(timestamp: date)
modelContext.insert(newItem)
dump(newItem.persistentModelID)
let item = try modelContext.get(item.model)
let child = try modelContext.get(childModel)
assert(child != nil && item != nil)
child?.parent = item
try modelContext.save()
}
}
}

internal func addItem(withDate date: Date = .init()) {
guard let database else {
return
}
Task {
let insertedModel = await database.insert { Item(timestamp: date) }
print("inserted:", insertedModel.isTemporary)
try await database.save()
let savedModel = try await database.get(
for: .predicate(
#Predicate<Item> {
$0.timestamp == date
}
)
)
print("saved:", savedModel.isTemporary)
}
}
}
2 changes: 1 addition & 1 deletion Example/Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal struct ContentView: View {
if selectedItems.count > 1 {
Text("Multiple Selected")
} else if let item = selectedItems.first {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
ItemChildView(object: object, item: item)
} else {
Text("Select an item")
}
Expand Down
10 changes: 9 additions & 1 deletion Example/Sources/Item.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@
// Created by Leo Dion on 10/10/24.
//

import DataThespian
import Foundation
import SwiftData

@Model
internal final class Item {
internal final class Item: Unique {
internal enum Keys: UniqueKeys {
internal typealias Model = Item
internal static let primary = timestamp
internal static let timestamp = keyPath(\.timestamp)
}

internal private(set) var timestamp: Date
internal private(set) var children: [ItemChild]?

internal init(timestamp: Date) {
self.timestamp = timestamp
Expand Down
19 changes: 19 additions & 0 deletions Example/Sources/ItemChild.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// ItemChild.swift
// DataThespian
//
// Created by Leo Dion on 10/16/24.
//
import Foundation
import SwiftData

@Model
internal final class ItemChild {
internal var parent: Item?
internal private(set) var timestamp: Date

internal init(parent: Item? = nil, timestamp: Date) {
self.parent = parent
self.timestamp = timestamp
}
}
31 changes: 31 additions & 0 deletions Example/Sources/ItemChildView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// ItemChildView.swift
// DataThespianExample
//
// Created by Leo Dion on 10/16/24.
//

import SwiftUI

internal struct ItemChildView: View {
internal var object: ContentObject
internal let item: ItemViewModel
internal var body: some View {
VStack {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
Divider()
Button("Add Child") {
object.addChild(to: item)
}
ForEach(item.children) { child in
Text(
"Child at \(child.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))"
)
}
}
}
}
//
// #Preview {
// ItemChildView()
// }
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,26 @@ import DataThespian
import Foundation
import SwiftData

internal struct ItemModel: Identifiable {
internal struct ItemViewModel: Sendable, Identifiable {
internal let model: Model<Item>
internal let timestamp: Date
internal let children: [ChildViewModel]

internal var id: PersistentIdentifier {
model.persistentIdentifier
}

private init(model: Model<Item>, timestamp: Date) {
private init(model: Model<Item>, timestamp: Date, children: [ChildViewModel]?) {
self.model = model
self.timestamp = timestamp
self.children = children ?? []
}

internal init(item: Item) {
self.init(model: .init(item), timestamp: item.timestamp)
self.init(
model: .init(item),
timestamp: item.timestamp,
children: item.children?.map(ChildViewModel.init)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,7 @@
public import SwiftData

extension Database {
public func insert<PersistentModelType: PersistentModel>(
_ closuer: @Sendable @escaping () -> PersistentModelType
) async -> Model<PersistentModelType> {
let id: PersistentIdentifier = await self.insert(closuer)
return .init(persistentIdentifier: id)
}

@available(*, deprecated)
public func with<PersistentModelType: PersistentModel, U: Sendable>(
_ id: Model<PersistentModelType>,
_ closure: @escaping @Sendable (PersistentModelType) throws -> U
Expand All @@ -53,10 +47,13 @@
}
}

public func first<T: PersistentModel>(_ selectPredicate: Predicate<T>) async throws -> Model<T>? {
@available(*, deprecated)
public func first<T: PersistentModel>(_ selectPredicate: Predicate<T>) async throws -> Model<T>?
{
try await self.first(selectPredicate, with: Model.ifMap)
}

@available(*, deprecated)
public func first<T: PersistentModel, U: Sendable>(
_ selectPredicate: Predicate<T>, with closure: @escaping @Sendable (T?) throws -> U
) async throws -> U {
Expand All @@ -67,38 +64,23 @@
}
}

public func first<T: PersistentModel, U: Sendable>(
fetchWith selectPredicate: Predicate<T>,
otherwiseInsertBy insert: @Sendable @escaping () -> T,
with closure: @escaping @Sendable (T) throws -> U
) async throws -> U {
let value = try await self.fetch {
.init(predicate: selectPredicate, fetchLimit: 1)
} with: { models in
try models.first.map(closure)
}

if let value {
return value
}

let inserted: Model = await self.insert(insert)

return try await self.with(inserted, closure)
}

@available(*, deprecated)
public func delete<T: PersistentModel>(model _: T.Type, where predicate: Predicate<T>? = nil)
async throws
{ try await self.delete(where: predicate) }

@available(*, deprecated)
public func delete<T: PersistentModel>(_ model: Model<T>) async {
await self.delete(T.self, withID: model.persistentIdentifier)
}

@available(*, deprecated)
public func deleteAll(of types: [any PersistentModel.Type]) async throws {
try await self.transaction { context in for type in types { try context.delete(model: type) } }
try await self.transaction { context in for type in types { try context.delete(model: type) }
}
}

@available(*, deprecated)
public func fetch<T: PersistentModel, U: Sendable>(
_: T.Type, with closure: @escaping @Sendable ([T]) throws -> U
) async throws -> U {
Expand All @@ -109,15 +91,19 @@
}
}

@available(*, deprecated)
public func fetch<T: PersistentModel>(_: T.Type) async throws -> [Model<T>] {
try await self.fetch(T.self) { models in models.map(Model.init) }
}

@available(*, deprecated)
public func fetch<T: PersistentModel>(
_: T.Type, _ selectDescriptor: @escaping @Sendable () -> FetchDescriptor<T>
) async throws -> [Model<T>] {
await self.fetch(selectDescriptor) { models in models.map(Model.init) }
}

@available(*, deprecated)
public func fetch<T, U: Sendable>(
of _: T.Type,
for objectIDs: [PersistentIdentifier],
Expand All @@ -134,6 +120,7 @@
}
}

@available(*, deprecated)
public func get<T, U: Sendable>(
of _: T.Type,
for objectID: PersistentIdentifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,25 @@
public import SwiftData

extension Database {
public func save() async throws { try await self.withModelContext { try $0.save() } }

@available(*, deprecated)
@discardableResult public func delete<T: PersistentModel>(
_ modelType: T.Type, withID id: PersistentIdentifier
) async -> Bool { await self.withModelContext { $0.delete(modelType, withID: id) } }

@available(*, deprecated)
public func delete<T: PersistentModel>(where predicate: Predicate<T>?) async throws {
try await self.withModelContext { try $0.delete(where: predicate) }
}

public func insert(_ closuer: @Sendable @escaping () -> some PersistentModel) async
-> PersistentIdentifier
{ await self.withModelContext { $0.insert(closuer) } }

@available(*, deprecated)
public func fetch<T, U: Sendable>(
_ selectDescriptor: @escaping @Sendable () -> FetchDescriptor<T>,
with closure: @escaping @Sendable ([T]) throws -> U
) async rethrows -> U {
try await self.withModelContext { try $0.fetch(selectDescriptor, with: closure) }
}

@available(*, deprecated)
public func fetch<T: PersistentModel, U: PersistentModel, V: Sendable>(
_ selectDescriptorA: @escaping @Sendable () -> FetchDescriptor<T>,
_ selectDescriptorB: @escaping @Sendable () -> FetchDescriptor<U>,
Expand All @@ -65,12 +63,14 @@
}
}

@available(*, deprecated)
public func get<T, U: Sendable>(
for objectID: PersistentIdentifier, with closure: @escaping @Sendable (T?) throws -> U
) async rethrows -> U where T: PersistentModel {
try await self.withModelContext { try $0.get(for: objectID, with: closure) }
}

@available(*, deprecated)
public func transaction(_ block: @Sendable @escaping (ModelContext) throws -> Void) async throws
{ try await self.withModelContext { try $0.transaction(block: block) } }
}
Expand Down
Loading

0 comments on commit d5a3068

Please sign in to comment.