I'm new with Swift, coming from web, I want to make a GRDB model that would auto update on edits and can be loaded from json. I ended up writing a model like this. The issue is that I need to repeat each field 8 times, that's a lot of boilerplate. Is there any way to write this shorter with less repetition and more elegant?
import Foundation
import GRDB
class Routine: Identifiable, Codable, FetchableRecord, PersistableRecord, ObservableObject {
var id: UUID = UUID()
@Published var name: String {
didSet { save() }
}
// Define the table name for GRDB
static let databaseTableName = "routines"
// Define columns as enum for type-safe column access
enum Columns: String, ColumnExpression {
case id, name
}
// MARK: - Initializers
required init(row: Row) {
id = row[Columns.id]
name = row[Columns.name]
}
init(id: UUID, name: String) {
self.id = id
self.name = name
}
// MARK: - Codable
enum CodingKeys: String, CodingKey {
case id, name
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
}
// MARK: - PersistableRecord
func encode(to container: inout PersistenceContainer) {
container[Columns.id] = id
container[Columns.name] = name
}
// MARK: - Auto Save
func save() {
do {
try DatabaseManager.shared.dbQueue.write { db in
try self.save(db)
}
} catch {
print("Error saving routine:", error)
}
}
}
The immediate problem is that the use of @Published
prevents the automatic synthesis of Codable
conformance, and you have to implement Codable
on your own. But...
Your database records should not be an ObservableObject
. If you are working with SwiftUI, Making Routine
a struct would be the most convenient. You can store instances of these structs in an ObservableObject
if you like.
struct Routine: Identifiable, Hashable, Codable, PersistableRecord, FetchableRecord {
let id: UUID
var name: String {
didSet {
save()
}
}
static let databaseTableName = "routines"
enum Columns {
static let id = Column(CodingKeys.id)
static let name = Column(CodingKeys.name)
}
init(id: UUID = UUID(), name: String) {
self.id = id
self.name = name
}
func save() {
// ...
}
}
If you absolutely need Routine
to be a class (UIKit interop is the only situation I can think of where this is necessary), see the answers to this question. For example, if you made Published
conform to Codable
, then you can just write
class Routine: Identifiable, Codable, FetchableRecord, PersistableRecord, ObservableObject {
let id: UUID
@Published var name: String {
didSet { save() }
}
static let databaseTableName = "routines"
init(id: UUID = UUID(), name: String) {
self.id = id
self.name = name
}
enum Columns {
static let id = Column(CodingKeys.id)
static let name = Column(CodingKeys.name)
}
func save() {
// ...
}
}
You do not need to implement PersistableRecord.encode(to:)
.