Hello, I am trying to fetch from associations. I tried to follow what is said in the GRDB documentation on this topic, but it does not work..
Brief overview, I have a Card, SubCard and WordBox models. WordBox has a polymorphic relationship with Card and SubCard. Card has a hasOne association with WordBox. SubCard has a hasMany association with WordBox
To get going, I just want to fetch both Card and its associated WordBox (which I call header in my code).
I defined a simple CardWithHeader struct
struct CardWithHeader: FetchableRecord, Decodable {
var card: Card
var header: WordBox
}
And then I tried to fetch my CardWithHeader array in this variable:
let fetchedCards: [CardWithHeader] = try appDatabase.reader.read { db in
db.trace {
os_log("%{public}@", log: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SQL"), type: .debug, String(describing: $0))
}
var request = Card
.including(required: Card.header)
.asRequest(of: CardWithHeader.self)
.order(Card.Columns.id.desc)
.limit(limit, offset: offset)
return try request.fetchAll(db)
}
My issue is that my CardWithHeader struct correctly initializes its card member but not its header member
I tried to analyze this through the debugger, and printed the fetched GRDB.row with which the CardWithHeader is initialized and I got this:
(lldb) po row
▿ [id:16866 headerID:232140 type:"noun" shoresh:"טפטף" group:NULL tags:"[]" metadata:"{\"url\":\"/dict/6637-tiftuf/\",\"gender\":\"M\"}"]
unadapted: [id:16866 headerID:232140 type:"noun" shoresh:"טפטף" group:NULL tags:"[]" metadata:"{\"url\":\"/dict/6637-tiftuf/\",\"gender\":\"M\"}" id:232140 wordBoxableID:16866 wordBoxableType:"card" pronouns:"[הוא]" menukad:"טִיפְטוּף" transcription:"tiftuf"]
- wordBox: [id:232140 wordBoxableID:16866 wordBoxableType:"card" pronouns:"[הוא]" menukad:"טִיפְטוּף" transcription:"tiftuf"]
Question 1:
So I managed to make my code work by using the unadapted field from this row and creating a custom init function for my CardWithHeader struct, but i feel this is too artificial.
From my research, I should use the annotated method to flatten my fetched columns, but I do not get how this works..
Edit:
Question 2:
Basically, here is my whole goal.
I want to be able to define a struct named FullCard that would contain all Card related informations which means:
- card columns
- card.header columns (header is an entry of table wordbox: card => hasOne => header)
- card.header.translations (translations are entries of the table translation: wordbox => hasMany => translation)
- card.subcards columns (subcard are entries of table subcard: card => hasMany => subcard)
- card.subcards.wordbox columns (subcard => hasMany => wordbox. wordbox have a polymorphic relationship with card and subcard)
- card.subcards.wordbox.translations columns (wordbox => hasMany => translation)
This is where i would like to end up.
All of these relationships are correctly defined in my models. My issue is really on how to correctly design the structs that will receive the fetched data and how to fetch the data.
Here are my Card and WordBox structs:
struct Card: Codable {
var id: Int64?
var headerID: Int64?
var shoresh: String? = nil
var group: CardGroup? = nil
var tags: [String] = []
var type: CardType
var metadata: [String: String] = [:]
var wrappedShoresh: String {
return shoresh ?? ""
}
var wrappedGroupDescription: String {
return group?.description ?? ""
}
var wrappedGroupHebrew: String {
return group?.hebrew ?? ""
}
}
extension Card: FetchableRecord, MutablePersistableRecord {
static var databaseTableName: String {
return "card"
}
enum Columns: String, ColumnExpression {
case id, headerID, shoresh, group, tags, type, metadata
}
static let subcards = hasMany(SubCard.self)
var subcards: QueryInterfaceRequest<SubCard> {
return request(for: Card.subcards)
}
static let header = hasOne(WordBox.self, using: ForeignKey(["id"], to: ["headerId"]))
var header: QueryInterfaceRequest<WordBox> {
return request(for: Card.header)
.filter(Column("wordBoxableType") == WordBoxableType.card.rawValue)
}
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
struct WordBox: Codable {
var id: Int64?
var wordBoxableID: Int64
var wordBoxableType: WordBoxableType
var pronouns: [Pronoun]
var menukad: String
var transcription: String
var wrappedPronounsHebrew: [String] {
return pronouns.map { $0.description }
}
var wrappedPronounsTranscription: [String] {
return pronouns.map { $0.caseName }
}
}
extension WordBox: FetchableRecord, MutablePersistableRecord {
static var databaseTableName: String {
return "wordBox"
}
enum Columns: String, ColumnExpression {
case id, wordBoxableID, wordBoxableType, pronouns, menukad, transcription
}
static let translations = hasMany(Translation.self, using: ForeignKey(["wordBoxID"], to: ["id"]))
var translations: QueryInterfaceRequest<Translation> {
return request(for: WordBox.translations)
}
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}