Search code examples
iosswiftrealmrealm-migration

Swift Realm migration create reference from old type to new one


Initially I had the following classes:

@objcMembers public class NormalObjectRealm: Object {

    // Shared
    dynamic public var id:                String?
    dynamic public var title:             String?
    dynamic public var subTitle:          String?
    dynamic public var imageInfo:         ImageInfoRealm?
    dynamic public var descriptionString: String?
    public var categories = List<String>()
    public var count    = RealmOptional<Int>()
    public var episodes = List<String>()


    public static let realmPrimaryKey: String = "id"

    public override class func primaryKey() -> String? {
        return NormalObjectRealm.realmPrimaryKey
    }
}

@objcMembers public class ImageInfoRealm: Object {

    dynamic public var id: String?
    dynamic public var url:    String?

    public static let realmPrimaryKey: String = "id"

    public override class func primaryKey() -> String? {
        return ImageInfoRealm.realmPrimaryKey
    }

}

but now NormalObjectRealm is kind of incorporated into a new class like so:

@objcMembers public class MediaObjectRealm: Object {

    // Shared
    dynamic public var id:                String?
    dynamic public var title:             String?
    dynamic public var subTitle:          String?
    dynamic public var imageInfo:         ImageInfoRealm?
    dynamic public var descriptionString: String?
    public var categories = List<String>()
    dynamic public var type: String?

    // NormalObjectRealm
    public var episodeCount    = RealmOptional<Int>()
    public var episodes = List<String>()

    // OtherObjectRealm
    dynamic public var urlOne:    String?
    dynamic public var urlTwo: String?
    dynamic public var urlThree:   String?
    public var isExplicit = RealmOptional<Bool>()

    public static let realmPrimaryKey: String = "id"

    public override class func primaryKey() -> String? {
        return MediaObjectRealm.realmPrimaryKey
    }
}

I'm currently trying to write the migration for the transition here where the idea basically is to transfer most of the fields over from NormalObjectRealm to MediaObjectRealm.

This is what my migration-block currently looks like

Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
            if oldSchemaVersion < temp {
                print("RealmMigration: Applying migration from \(oldSchemaVersion) to \(temp)")

                migration.enumerateObjects(ofType: "NormalObjectRealm") { oldObject, newObject in
                    guard let oldObject = oldObject else {
                        return
                    }
                    guard let id = oldObject["id"] as? String else {
                        return
                    }
                    guard let title = oldObject["title"] as? String else {
                        return
                    }

                    guard let subTitle = oldObject["subTitle"] as? String else {
                        return
                    }

                    guard let imgInfo = oldObject["imageInfo"] else {
                        return
                    }
                    guard let count = oldObject["count"] as? RealmOptional<Int>? else {
                        return
                    }

                    guard let descriptionString = oldObject["descriptionString"] as? String? else {
                        return
                    }

                    let item = migration.create("MediaObjectRealm")
                    item["id"] = id
                    item["title"] = title
                    item["subTitle"] = subTitle
                    item["descriptionString"] = descriptionString
                    item["type"] = "myType"
                    item["episodeCount"] = episodeCount // Doesn't work either...

                    migration.enumerateObjects(ofType: "ImageInfoRealm") { oldImg, newImg in
                        guard let oldImg = oldImg else {
                            return
                        }

                        let inf = oldObject.value(forKey: "imageInfo")
                        print(inf)
                        let t = migration.create("ImageInfoRealm", value: inf)
                        print("doing it")
                        //                      print(t)
                        item.setValue(t, forKey: "imageInfo")

                    }
                }
            }
    })

id, title, subTitle etc. (String? and Date? variables) are set fine and appear inside the newly created MediaObjectRealm DB-Entries. However imageInfo of type ImageInfoRealm does not... setting it directly like so: item.setValue(oldObject.value(forKey: "imageInfo"), forKey: "imageInfo") (or item["imageInfo"] = oldObject.value(forKey: "imageInfo")) results in realm crashing and telling me that this object is from another realm and I have to copy it over.

'Object is already managed by another Realm. Use create instead to copy it into this Realm.'

Creating it like in the code above results in not even having any items of type MediaObjectRealm at all i.e. loosing all the data (as NormalObjectRealm is also not present anymore). Am I missing something? What I basically want is to to take the link/reference from the NormalObjectRealm and copy it to the new MediaObjectRealm.


Solution

  • After long testing and trying different possibilities I managed to migrate the data. Here is what I did to accomplish this.

    I used this as a base:

    class RealmMigrationObject {
        let migration: () -> ()
    
        init(migration: @escaping () -> ()) {
            self.migration = migration
        }
    }
    

    and derived classes from that. Something like:

    class MigrationObjectToThree: RealmMigrationObject {
    
        init() {
            super.init(migration: MigrationObjectToThree.migration)
        }
    
        private static func migration() {
            print("Migration to three | migration")
    
            var imageInfos:   [ImageInfo]      = []
    
            let config = Realm.Configuration(schemaVersion: 3, migrationBlock: { migration, oldSchemaVersion in
    
                print("Migration to three | migrationBlock")
                print("RealmMigration: Applying migration from \(oldSchemaVersion) to 3")
    
                migration.deleteData(forType: "ExploreSectionObjectRealm")
    
                migration.enumerateObjects(ofType: "ImageInfoRealm") { oldInfo, newObject in
                    guard let oldInfo = oldInfo else {
                        return
                    }
    
                    guard let id = oldInfo["id"] as? String,
                          let url = oldInfo["url"] as? String,
                          let url500 = oldInfo["url500"] as? String,
                          let url400 = oldInfo["url400"] as? String,
                          let url300 = oldInfo["url300"] as? String,
                          let url200 = oldInfo["url200"] as? String,
                          let url100 = oldInfo["url100"] as? String,
                          let colorString = oldInfo["color"] as? String,
                          let color = UIColor(hexString: colorString) else {
                        return
                    }
                    imageInfos.append(ImageInfo(id: id,
                                                url: url,
                                                url500: url500,
                                                url400: url400,
                                                url300: url300,
                                                url200: url200,
                                                url100: url100,
                                                color: color))
                }
    
            })
    
    
            Realm.Configuration.defaultConfiguration = config
            do {
                let realm = try Realm(configuration: config)
            print("Realm is located at: \(realm.configuration.fileURL?.description ?? "")")
                print(realm.configuration.fileURL?.description ?? "") // Printing here on purpose  as it's easier to copy
            } catch {
            print("Realm Error: \(error), trying to rebuild realm from scratch")
    
                let deleteMigrationConfig = Realm.Configuration(schemaVersion: RealmHelper.schemaVersion,
                                                                deleteRealmIfMigrationNeeded: true)
                do {
                    _ = try Realm(configuration: deleteMigrationConfig)
                } catch {
                    print("Failed to instantiate: \(error.localizedDescription)")
                }
            }
    
            RealmHelper.removeRealmFiles()
            Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 3)
    
            imageInfos.forEach({ $0.save() })
        }
    }
    

    From that I just created all migration for the difference between the current schema version and target schema version on looped over all migrations simply executing the migration function of that given object.