realm-swift
                                
                                 realm-swift copied to clipboard
                                
                                    realm-swift copied to clipboard
                            
                            
                            
                        Upserting an object with an embedded object crashes the app
Goals
Upsert an object that contains an embedded object.
Expected Results
Upsert works flawlessly.
Actual Results
Upsert crashes an app.
2021-03-04 19:48:14.412232+0100 Example[37771:2640657] *** Terminating app due to uncaught exception 'RLMException', reason: 'Cannot set a link to an existing managed embedded object'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff20421af6 __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00007fff20177e78 objc_exception_throw + 48
	2   Realm                               0x0000000103fcddc0 _ZN18RLMAccessorContext12createObjectEP11objc_objectN5realm12CreatePolicyEbNS2_6ObjKeyE + 3568
	3   Realm                               0x000000010410859e RLMAddObjectToRealm + 302
	4   RealmSwift                          0x0000000105ad1770 $s10RealmSwift0A0V3add_6updateySo0aB6ObjectC_AC12UpdatePolicyOtF + 1392
…
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Steps for others to Reproduce
- Create an instance of Realm database.
- Create an instance of object that contains an embedded object (MyObject).
- Set the embedded object to non-nil.
- Write the instance to the database.
- Create a copy of the first instance.
- Upsert the second instance into the database.
Code Sample
Models:
final class MyEmbeddedObject: EmbeddedObject {
    @objc dynamic var value = false
}
final class MyObject: Object {
    @objc dynamic var id = ObjectId.generate()
    @objc dynamic var embeddedObject: MyEmbeddedObject?
    
    override static func primaryKey() -> String? {
        return "id"
    }
}
Code:
let realm = try! Realm()
let myObject1 = MyObject()
myObject1.embeddedObject = MyEmbeddedObject()
try! realm.write {
    realm.add(myObject1)
}
let myObject2 = MyObject(value: myObject1)
try! realm.write {
    realm.add(myObject2, update: .modified) // ⚠️ Responsible line
}
Code that works, but I guess is a workaround instead of a solution.
try! realm.write {
    myObject2.embeddedObject = MyEmbeddedObject(value: myObject2.embeddedObject as Any)
    realm.add(myObject2, update: .modified)
}
I understand it relates how Object.init(value:) initializer works (shallow instead of deep copy), but as I call Realm.add(_:update:) with .modified instead of .all, I expected this to work properly.
Version of Realm and Tooling
Realm framework version: 10.7.0
Realm Object Server version: N/A
Xcode version: 12.4
iOS/OSX version: 14.4
Dependency manager + version: CocoaPods 1.10.1
Hi @sochalewski , Yes, this is a question of shallow/deep copy and therefore the workaround above is a solution. UpdatePolicy.modified won't work in this case as in fact you're creating a copy of a link to embedded object instead of embedded object.
@pavel-ship-it Thank you for your answer, it makes sense. Then I'll close the issue.
The code sample you posted is something that's supposed to work and we just have some overly-eager validation.
Fixed in pull request #7301
@pavel-ship-it since this was fixed... in #7301 should we close the issue?