-
-
Notifications
You must be signed in to change notification settings - Fork 742
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
Creating a Record Structure with Generic Types #1749
Comments
Hello @Artem-Viveritsa,
With Since the compiler won't have struct AnyBlockData: BlockData {
init(_ base: any BlockData) { ... }
} You'll probably need the extension Block: FetchableRecord {
init(row: Row) throws {
let data: any BlockData
let jsonDecoder = JSONDecoder()
let jsonData: Data = row["data"]
let type: BlockType = row["type"]
switch type {
case .type1:
let data = try jsonDecoder.decode(BlockData1.self, from: jsonData)
case .type2:
let data = try jsonDecoder.decode(BlockData2.self, from: jsonData)
// Other cases
}
if let data = data as? T {
self.init(type: type, data: data)
} else if T.self == AnyBlockData.self {
self.init(type: type, data: AnyBlockData(data))
} else {
// The `type` column does not match our `T` type, we can't decode.
throw /* some error */
}
}
} |
Thank you very much @groue, it seems to be the right direction. I remade the whole project for this structure. protocol BlockData: Codable, Hashable, Sendable, DatabaseValueConvertible {
static var type: BlockType { get }
}
struct AnyBlockData: BlockData {
static let type: BlockType = .empty
init(_ base: any BlockData) { }
} But I can't figure out this point: if let data = data as? T {
print("From row init: \(data)")
self.init(data) // OK
} else if T.self == AnyBlockData.self {
print(data) // Right data type...
self.init(AnyBlockData(data) as! T) // ...but return as AnyBlockData
}
else {
throw NSError()
}
// OK:
struct BlockRequest<T: BlockData>: ValueObservationQueryable {
static var defaultValue: Block<T> { Block(EmptyBlockData() as! T) }
// Not OK:
struct BlocksRequest: ValueObservationQueryable {
static var defaultValue: [Block<AnyBlockData>] { [] } It works only in such a way that the request returns an array (single record) of one type and this is expected. But is it possible to make it so that there are blocks of different types inside? Or should it already be something like a dictionary or set? Preferably in one request to the database, because the type of blocks is unpredictable. And such queries always cause an error because struct FullBlockRequest<T: BlockData>: ValueObservationQueryable {
static var defaultValue: FullBlock<T> { FullBlock(block: Block(EmptyBlockData() as! T)) } // <---
let blockId: UUID
init(_ blockId: UUID) {
self.blockId = blockId
}
func fetch(_ db: Database) throws -> FullBlock<T> {
// try FullBlock.find(db, id: blockId) <--- Is it possible to use find instead of fetchOne?
let request = Block
.filter(id: blockId)
.including(optional: Block.parent.deleteStatus(.notDeleted))
.including(all: Block.children.deleteStatus(.notDeleted))
.asRequest(of: FullBlock<AnyBlockData>.self)
return try FullBlock.fetchOne(db, request)!
}
} |
In the end, I came to this decision, it is much easier in terms of support and allows me to change the type and structure of blocks on the fly: struct AnyBlock: Syncable {
...
private func getData<T: BlockData>(as type: T.Type) -> T? {
if let data = data.data(using: .utf8) {
return try? JSONDecoder().decode(T.self, from: data)
}
return nil
}
@discardableResult
private mutating func setData<T: BlockData>(_ value: T) -> Bool {
if self.type == T.type, let newData = try? value.toJSON() {
self.data = newData
return true
}
return false
}
private mutating func setData<T: BlockData>(_ value: T?) -> Bool {
if let newData = value {
return setData(newData)
}
return false
}
mutating func setParent(_ parent: AnyBlock?) { parentId = parent?.id }
@MainActor
subscript<T: BlockData>(type: T.Type) -> T? {
get { getData(as: type) }
set(newValue) { if setData(newValue) { save() } }
}
} And then in SwiftUI: if let data = block[TagData.self] { ... }
block[TagData.self]?.title = "GRDB❤️" |
Let's say I have this structure:
Where the
data
field is currently represented by a json string. To get this data, you need to call the appropriate methods. But I would like it to have the correct type depending on its content:And if the request for blocks of the same type works well:
Then what about those requests that should return blocks of different types?
Ideally, I want to make it so that the type of the T block is determined based on its
type
field, so that the maximum amount of work is done on the request side and ready-made blocks with the correct types come to the interface. Is this even possible?The text was updated successfully, but these errors were encountered: