Hélène Martin
Hélène Martin

Reputation: 1441

Going from one-to-one relation to one-to-many in Realm Swift

I am building a Swift iOS app using Swift 2.1.1 and RealmSwift 0.97.1. My initial requirements involved a one-to-one relation between classes A and B so I wrote the following:

class A: Object {
    dynamic var b: B? // A has one B
}

class B: Object {
    var a: A {
        return linkingObjects(A.self, forProperty: "b")[0]
    }
}

This worked well but now my requirements have changed and I need a one-to-many relation:

class A: Object {
    var bs: [B] {
        return linkingObjects(B.self, forProperty: "a")
    }
}

class B: Object {
    dynamic var a: A? // B belongs to A
}

I have users with real data so I would really like to push out this change gracefully using migrations.

From what I have tried, it's not possible to change a relation from within a migration (I get Can not add objects from a different Realm). Is this actually the case?

As a workaround, I create a dictionary that maps A primary keys to B primary keys. This also means I had to add a primary key to B which didn't previously have one. Then, outside the migration, I change the order of the relation:

for (aKey, bKey) in keyMap {
    let a = realm.objectForPrimaryKey(A.self, key: aKey)
    let b = realm.objectForPrimaryKey(B.self, key: bKey)

    b?.a = a
    a?.b = nil
}

This works but I can't seem to get rid of A's b property. If I try to remove it, the part of the migration that builds the dictionary fails: there is no link between As and Bs if I use newObject and there is no primary key for B if I use oldObject. This all would work fine if I could sequence the updates (first add the primary key, then build the dictionary, then delete the b property) but I don't think that's possible.

Is there a better way to approach making this change? If not, is there a way I can remove the b property?

Upvotes: 1

Views: 1590

Answers (1)

Thomas Goyne
Thomas Goyne

Reputation: 8138

If all of your B objects are linked to by exactly one A object, you can do the following in your migration:

migration.deleteData("B")
migration.enumerate("A") { oldObject, newObject in
    let b = migration.create("B", value: oldObject!["b"]!)
    (newObject!["bs"] as! List<MigrationObject>).append(b)
}

This works around the fact that you can't get a reference to the linked object which is compatible with newObject without keeping the property b by instead deleting all of the existing B objects and instead linking to new copies of them.

Upvotes: 2

Related Questions