I am trying to migrate our Realm Swift schema from using a String wrapper class to the primitive String for collections. I am running into issues extracting the string values from the wrapper during migration.
Here is the wrapper class:
class StringDB: Object {
@objc var stringVal: String = ""
convenience init(string: String) {
self.init()
stringVal = string
}
}
Then I have an example class with a List<StringDB>
property:
class MyObjDB: Object {
var emails: List<StringDB> = List<StringDB>()
@objc dynamic var id: String = UUID().uuidString
convenience init(_ emails: [StringDB]) {
self.init()
for email in emails {
self.emails.append(objectsIn: emails)
}
}
override static func primaryKey() -> String? {
return "id"
}
}
that I want to convert to a List<String>
.
Here is my migration code.
let config = Realm.Configuration(schemaVersion: latestSchemaVersion, migrationBlock: {
migration, version in
if version < 2 {
migration.enumerateObjects(ofType: MyObjDB.className(), { old, new in
guard let old = old, let new = new else { return }
let oldEmails = old["emails"] as! List<StringDB>
let newEmails = List<String>()
for email in oldEmails {
newEmails.append(email.stringVal)
}
new["emails"] = newEmails
})
}
})
However, the let oldEmails = old["emails"] as! List<StringDB>
cast fails. I've tried also casting the collection to List<MigrationObject>
and then casting the individual objects to StringDB
but that cast fails as well.
I've found a workaround that may be satisfactory (I haven't confirmed yet), of converting the MigrationObject
directly to string using coercion like "\(email)"
and then running a regEx that will extract a desired substring from the garbage (StringDB {\n\tstringVal = [email protected];\n}
), but I have no idea yet whether that will hold up in production, and I would prefer to work with something resembling a recommended way for doing this migration.
CodePudding user response:
The Realm version is not shown in the question and there are a few typo's in the code. For older Realm versions, Lists
should be defined thusly:
let emails = RealmSwift.List<StringDB>() //need RealmSwift. to differentiate it from Swift List
then newer versions should be this:
@Persisted var emails = RealmSwift.List<StringDB>()
Then this is a problem as it iterates over the emails array count times and appends the entire emails list over and over for every email in that list.
for email in emails {
self.emails.append(objectsIn: emails)
}
Also, when using older Realm versons with @Objc property types, they need to include dynamic
. So on the StringDB
object this
@objc var stringVal: String = ""
should be this
@objc dynamic var stringVal: String = ""
Lastly, the MyObjDB
needs to have somewhere to put the new email list. You can't overwrite the old one as it's not the correct type. So add a property
class MyObjDB: Object {
var emails: List<StringDB> = List<StringDB>()
let updatedEmailList = List<String>()
Then to the question: See comments in code for the flow
How about this:
migration.enumerateObjects(ofType: MyObjDB.className()) { oldItem, newItem in
//instantiate a List of the old email objects as migration objects
let oldEmailList = oldItem!["emails"] as! List<MigrationObject>
//we're going to populate a new List with the email strings
var newEmailList = List<String>()
//iterate over the old list, extracting the string from the old object as
// a string an inject it into a new List of Strings
for oldEmailObject in oldEmailList {
let oldEmail = oldEmailObject["stringVal"] as! String
newEmailList.append(oldEmail)
}
//assign the new List of strings to a new emails property
newItem!["updatedEmailList"] = newEmailList
}