{"action":"created","issue":{"url":"https://api.github.com/repos/drewmccormack/ensembles/issues/235","repository_url":"https://api.github.com/repos/drewmccormack/ensembles","labels_url":"https://api.github.com/repos/drewmccormack/ensembles/issues/235/labels{/name}","comments_url":"https://api.github.com/repos/drewmccormack/ensembles/issues/235/comments","events_url":"https://api.github.com/repos/drewmccormack/ensembles/issues/235/events","html_url":"https://github.com/drewmccormack/ensembles/issues/235","id":163859308,"number":235,"title":"Deleting objects in reparationContext","user":{"login":"pronebird","id":704044,"avatar_url":"https://avatars.githubusercontent.com/u/704044?v=3","gravatar_id":"","url":"https://api.github.com/users/pronebird","html_url":"https://github.com/pronebird","followers_url":"https://api.github.com/users/pronebird/followers","following_url":"https://api.github.com/users/pronebird/following{/other_user}","gists_url":"https://api.github.com/users/pronebird/gists{/gist_id}","starred_url":"https://api.github.com/users/pronebird/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pronebird/subscriptions","organizations_url":"https://api.github.com/users/pronebird/orgs","repos_url":"https://api.github.com/users/pronebird/repos","events_url":"https://api.github.com/users/pronebird/events{/privacy}","received_events_url":"https://api.github.com/users/pronebird/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":9,"created_at":"2016-07-05T14:06:49Z","updated_at":"2016-07-05T21:20:28Z","closed_at":null,"body":"Hi,\r\n\r\nI am using \r\n```objc\r\n- (BOOL)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble shouldSaveMergedChangesInManagedObjectContext:(NSManagedObjectContext *)savingContext reparationManagedObjectContext:(NSManagedObjectContext *)reparationContext\r\n```\r\n\r\n to de-duplicate and merge objects before they make it to Core Data store.\r\n\r\nMerge looks as following (sourceObject is a duplicate object, and mergedObject is the object that I will retain in database after de-duplication):\r\n\r\nI find all objects in database matching certain value, e.g. Category with name Groceries. Then I choose the object that I would like to keep in database and start merging other objects with it, after merge completed, I delete all duplicates.\r\n\r\n```objc\r\nNSManagedObject *retainedObject = [self retainedObjectFromDuplicateObjects:similarObjects];\r\n\r\nNSParameterAssert(retainedObject);\r\n\r\n// remove retained object from duplicates\r\nNSMutableArray *duplicateObjects = [similarObjects mutableCopy];\r\n\r\n[duplicateObjects removeObject:retainedObject];\r\n\r\n[duplicateObjects enumerateObjectsUsingBlock:^(NSManagedObject *duplicateObject, __unused NSUInteger idx, __unused BOOL *stop) {\r\n    [mergeResolver mergeObject:duplicateObject intoObject:retainedObject];\r\n}];\r\n\r\n[duplicateObjects MR_deleteEntitiesInContext:reparationContext];\r\n```\r\n\r\n```objc\r\n[entityDescription.attributesByName enumerateKeysAndObjectsUsingBlock:^(NSString *key, __unused NSAttributeDescription *attr, __unused BOOL *stop) {\r\n    id oldValue = [mergedObject valueForKey:key];\r\n    id newValue = [sourceObject valueForKey:key];\r\n\r\n    // because CoreData produces change even when we set the same value\r\n    // it's a good practice to at least check equality with isEqual:\r\n    // before using setter\r\n    if(![oldValue isEqual:newValue]) {\r\n        [mergedObject setValue:newValue forKey:key];\r\n    }\r\n}];\r\n\r\n[entityDescription.relationshipsByName enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSRelationshipDescription *relation, __unused BOOL *stop) {        \r\n    if(relation.isToMany) { // to-many\r\n        if(relation.isOrdered) {\r\n            // ... not used\r\n        } else {\r\n            NSSet *oldRelatedObjects = [mergedObject valueForKey:key];\r\n            NSSet *newRelatedObjects = [sourceObject valueForKey:key];\r\n            NSMutableSet *combinedRelatedObjects = [[NSMutableSet alloc] init];\r\n\r\n            [combinedRelatedObjects unionSet:oldRelatedObjects];\r\n            [combinedRelatedObjects unionSet:newRelatedObjects];\r\n\r\n            // reset relationship to avoid any delete rules to trigger\r\n            [sourceObject setValue:[NSSet set] forKey:key];\r\n\r\n            if(![oldRelatedObjects isEqualToSet:combinedRelatedObjects]) {\r\n                [mergedObject setValue:[combinedRelatedObjects copy] forKey:key];\r\n            }\r\n        }\r\n    } else { // to-one\r\n        NSManagedObject *oldRelatedObject = [mergedObject valueForKey:key];\r\n        NSManagedObject *newRelatedObject = [sourceObject valueForKey:key];\r\n\r\n        // reset relationship to avoid any delete rules to trigger\r\n        [sourceObject setValue:nil forKey:key];\r\n\r\n        if(oldRelatedObject != newRelatedObject) {\r\n            [mergedObject setValue:newRelatedObject forKey:key];\r\n        }\r\n    }\r\n}];\r\n```\r\n\r\nAll of it happens in reparation context using performBlockAndWait.\r\n\r\nHowever what happens is that all relationships in duplicated objects are being deleted too (as a part CASCADE deletion rule I suppose), even though I copy all of relationships over to the single object (mergedObject) that I would like to keep in database after de-duplication and nullify the relationship on duplicates.\r\n\r\nIf I do not remove duplicates then everything is fine and all relationships are properly reassigned to the single object, however I end up with a bunch of duplicates dangling around... \r\n\r\nI have a feeling that Ensembles picks up deletions but does not save changes and so instead all of objects go through revision as being deleted."},"comment":{"url":"https://api.github.com/repos/drewmccormack/ensembles/issues/comments/230606659","html_url":"https://github.com/drewmccormack/ensembles/issues/235#issuecomment-230606659","issue_url":"https://api.github.com/repos/drewmccormack/ensembles/issues/235","id":230606659,"user":{"login":"pronebird","id":704044,"avatar_url":"https://avatars.githubusercontent.com/u/704044?v=3","gravatar_id":"","url":"https://api.github.com/users/pronebird","html_url":"https://github.com/pronebird","followers_url":"https://api.github.com/users/pronebird/followers","following_url":"https://api.github.com/users/pronebird/following{/other_user}","gists_url":"https://api.github.com/users/pronebird/gists{/gist_id}","starred_url":"https://api.github.com/users/pronebird/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pronebird/subscriptions","organizations_url":"https://api.github.com/users/pronebird/orgs","repos_url":"https://api.github.com/users/pronebird/repos","events_url":"https://api.github.com/users/pronebird/events{/privacy}","received_events_url":"https://api.github.com/users/pronebird/received_events","type":"User","site_admin":false},"created_at":"2016-07-05T21:20:28Z","updated_at":"2016-07-05T21:20:28Z","body":"Thanks for trying to help me.\r\n\r\nMy last guess is that I observe NSManagedObjectContextDidSaveNotification/WillSaveNotification on root context quite a lot. I may be wrong but it looks to me that I merge changes while Ensembles is repairing stuff and then I end up with unsaved root context, which when saved, fires deletion of objects that are kind of stuck in some state when they are not really valid for save and have been changed in persistent store but never refreshed because of wrong merge. \r\n\r\nThat's why when I end up with empty category on my model and if I restart the app, everything is fine. I am not sure what alternative I can use to catch up with ensembles. The book mentions `CDEMonitoredManagedObjectContextXXXSaveNotification`s but they don't contain information regarding changes which I need to do the proper update.\r\n\r\nI pulled out all the pieces that were processing objects upon didSave and things are back to normal. However, I still do merge things in didSave therefore I have to get rid of those too and replace them with equivalent CDE notifications... "}}
{"actor":{"display_login":"pronebird"}}