I'm having some problems with realm and relationships.
Here's my models:
class CreditCard: Object {
dynamic var owner: String?
var purchases: List<Purchase>()
...
}
And the second class which looks like that:
class Purchase: Object {
dynamic var id: Int = 0
dynamic var name: String?
dynamic var data: String?
let creditCard = LinkingObjects(fromType: CreditCard.self, property: "purchases")
...
}
All information comes from a web server. When the app opens, the first view controller makes a network request and download some credit card information. I can save all CreditCard objects just fine:
try! realm.write {
for creditCard in creditCards { // creditCards = all dictionary from my json object
let newCreditCard = CreditCard()
newCreditCard.id = creditCard["Id"].intValue
...
realm.add(newCreditCard, update: true)
}
}
On a second view controller, I have another request that downloads all purchases made by a credit card. After the request this is how I try to write to realm:
let creditCard = realm.objects(CreditCard.self).first // there's some code to get this.
try! realm.write {
for purchase in purchases { // purchases = objects from a son
let newPurchase = Purchase()
newPurchase.id = purchase["Id"].intValue
newPurchase.name = purchase["name"].stringValue
newPurchase.date = purchase["date"].dateValue
realm.add(newPurchase, update: true)
creditCard.purchases.append(newPurchase)
}
}
Realm Browser shows that all credit card and purchases are saved. The problem happens when I open the app again, and try to create/update all credit cards again (inside the first view controller). For some reason, this is breaking the relationship between the credit card and its purchases.
The purchases are saved on my realm objects, but the relationship between CreditCard and Purchase is lost. So, the return from the code below is always empty when I re-open the app:
let purchases = creditCard.purchases
Does realm.add(newCreditCard, update: true) overwrite all purchases? What am I doing wrong?
Realm version: 2.0.3
Xcode version: 8.1
iOS/OSX version: 10
Does realm.add(newCreditCard, update: true) overwrite all purchases? What am I doing wrong?
When update: true is passed to Realm.add(_:update:), Realm looks for an existing object with the given primary key. If it finds one, it updates all of the object's properties to the values specified in the passed-in object. This means that the purchases property of the credit card object will be updated to contain only the objects in newCreditCard.purchases. You don't appear to populate this field in the code snippet you've provided, so it's quite possible that you're unintentionally clearing the field by doing this.
Thanks for the clarification. You're right. I don't populate newCreditCard.purchases when my request finishes. I misunderstood how update:true works. I was thinking that it would update all fields that changed, but keep the existing ones as before. Realm would only update the limit, for example, but keep all purchases, name and so on.
What would be the best approach to keep all credit card purchases when updating it? Should I write a query to verify if the credit card already exists and populate the newCreditCard.purchases with the result? Is there a better way to do this?
My suggestion would be to use Realm.object(ofType:forPrimaryKey:) to retrieve the existing object based on its primary key, then set its properties as you do now. If object(ofType:forPrimaryKey:) returns nil, instantiate a new object and set its properties instead. It'd look something like:
try! realm.write {
for creditCard in creditCards { // creditCards = all dictionary from my json object
let id = creditCard["Id"].intValue
let creditCard = realm.object(ofType: CreditCard.self, forPrimaryKey: id) ?? CreditCard(value: ["id": id])
...
realm.add(newCreditCard)
}
}
This relies on the fact that Realm.add(_:) is a no-op if the object to be added already exists in the Realm.
Perfectly. Worked like a charm. Thank you for your time and attention.
Most helpful comment
My suggestion would be to use
Realm.object(ofType:forPrimaryKey:)to retrieve the existing object based on its primary key, then set its properties as you do now. Ifobject(ofType:forPrimaryKey:)returns nil, instantiate a new object and set its properties instead. It'd look something like:This relies on the fact that
Realm.add(_:)is a no-op if the object to be added already exists in the Realm.