Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Queryable #10

Merged
merged 9 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.existingModel(for: item.model)
let child = try modelContext.existingModel(for: 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,6 @@
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)
}

public func with<PersistentModelType: PersistentModel, U: Sendable>(
_ id: Model<PersistentModelType>,
_ closure: @escaping @Sendable (PersistentModelType) throws -> U
Expand All @@ -53,7 +46,8 @@
}
}

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

Expand All @@ -67,25 +61,25 @@
}
}

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)
}
// 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)
// }
leogdion marked this conversation as resolved.
Show resolved Hide resolved

public func delete<T: PersistentModel>(model _: T.Type, where predicate: Predicate<T>? = nil)
async throws
Expand All @@ -96,7 +90,8 @@
}

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) }
}
}

public func fetch<T: PersistentModel, U: Sendable>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
public import SwiftData

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

@discardableResult public func delete<T: PersistentModel>(
_ modelType: T.Type, withID id: PersistentIdentifier
) async -> Bool { await self.withModelContext { $0.delete(modelType, withID: id) } }
Expand All @@ -44,10 +42,6 @@
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) } }

public func fetch<T, U: Sendable>(
_ selectDescriptor: @escaping @Sendable () -> FetchDescriptor<T>,
with closure: @escaping @Sendable ([T]) throws -> U
Expand Down
Loading