Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

best way to rename a class in Realm swift

I use a "common" library in my iOS project. This library creates a Realm database. So far, I've been using this library on only iOS projects. I want to now use that same library with a macOS project. It's Foundation based, and doesn't use UIKit, so why not?

Here's the problem: I have a Realm class named Collection

Collection is also the name of a standard Swift protocol.

While I've been able to get away with this name collision on my iOS project, for some reason, I can't do the same on my MacOS project -- it creates a name-collection.

I read about this notation that can be used like this:

@objc(SpecialCollection)
class Collection: Realm.Object {
   let items: List<ItemObject>
   let name: String
   let url: String
  ....
}

So, this solves the name-collision problem. In ObjC, the name will be something different, but in Swift, I don't need to change anything.

This is all well and good except for my local Realm database. I have a lot of Collection objects that should be renamed to SpecialCollection (since Realm uses ObjC underneath Swift). I'd like to perform a migration to do this, but apparently there isn't a supported way to do this yet? I noticed tickets on github about this issue being "watched", but unfortunately, there still exists no published solution to fix this problem.

All of my Collection objects contain List objects (hence the name). So, I tried to run an enumeration on all of the Collection objects in a migration... I would just take the older object, and create a new object with the new name, like this:

 migration.enumerateObjects(ofType: "Collection", { (oldObject, _) in
    migration.create("SpecialCollection", value: oldObject)
 }

But since oldObject has a list of other objects, Realm's migration will try and create all the items in any List objects... which can't be done, because it creates objects with the same primaryKey value (causing a crash).

So, I can't keep the old name (Collection), and I can't convert to the new name, and I can't just trash the user's data. So, I'm truly at an impasse.

Blockquote

I tried to modify oldObject before creating the new object, but you can't change oldObject in a migration.

The only rule is that the old data has to be preserved, I can't just destroy the user's realm here.

Thanks for any help in this. It is greatly appreciated.

like image 902
Dan Morrow Avatar asked Oct 18 '22 01:10

Dan Morrow


1 Answers

I had a very similar problem last night. I had a couple Realm classes I wanted to rename, and where one of them had a List property referring to the second class. So the only difference compared to your problem is I was renaming ItemObject class as well.

So here's how I did it:

  1. Migrate your Collection class first, creating SpecialCollection.
  2. While migrating, walk the Collection's list and create new SpecialItemObject for each ItemObject and append it to the new list.
  3. Delete each ItemObject.
  4. Now enumerate all ItemObject remaining in the realm and create a new SpecialItemObject and map its values over. The reason is there may be other ItemObject floating around in your realm, not tied to the list.
  5. Delete all remaining ItemObject.

migration.enumerateObjects(ofType: "Collection")
{ (oldObject, newObject) in
    let specialCollection = migration.create(SpecialCollection.className())
    specialCollection["name"] = oldObject!["name"]
    specialCollection["url"] = oldObject!["url"]

    if let oldItems = oldObject!["items"] as? List<MigrationObject>,
       let newItems = specialCollection["items"] as? List<MigrationObject>
    {
        for oldItem in oldItems
        {
            let newItem = migration.create(SpecialItemObject.className())
            newItem["name"] = oldItem["name"]  // You didn't specify what was in your ItemObject so this example just assumes a name property.
            newItems.append(newItem)
            migration.delete(oldItem)
        }
    }
}
migration.deleteData(forType: "Collection")

// Now migrate any remaining ItemObject objects that were not part of a Collection.
migration.enumerateObjects(ofType: "ItemObject")
{ (oldObject, newObject) in
    let newItem = migration.create(SpecialItemObject.className())
    newItem["name"] = oldItem["name"]
}

// Now let's be sure we'll have no further ItemObject in our entire Realm.
migration.deleteData(forType: "ItemObject") 

So this is how I solved it for myself last night, after finding next to nothing about most of this in cocoa-realm in GitHub or on SO or elsewhere. The above example only differs from what you asked in that you weren't asking to rename your ItemObject class. You could try just creating new ItemObject objects and mapping the properties across in the same way I show in my example. I don't see why it wouldn't work. I've provided my example exactly how I solved my own problem, since I tested some migrations last night to prove it was solid.

Since your question is almost 5 months old, I'm really just posting this answer for posterity. Hope this helps someone!

Tested with Realm 3.3.2 on iOS 11.3 sim / Xcode 9.3 / Swift 4.1

like image 63
Smartcat Avatar answered Oct 21 '22 03:10

Smartcat