{"action":"created","issue":{"url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/issues/903","labels_url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/issues/903/labels{/name}","comments_url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/issues/903/comments","events_url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/issues/903/events","html_url":"https://github.com/couchbase/couchbase-lite-ios/issues/903","id":104910301,"number":903,"title":"Problem with relationship one-to-many when documents are created simultaneously on multiple devices","user":{"login":"mastohhh","id":1760064,"avatar_url":"https://avatars.githubusercontent.com/u/1760064?v=3","gravatar_id":"","url":"https://api.github.com/users/mastohhh","html_url":"https://github.com/mastohhh","followers_url":"https://api.github.com/users/mastohhh/followers","following_url":"https://api.github.com/users/mastohhh/following{/other_user}","gists_url":"https://api.github.com/users/mastohhh/gists{/gist_id}","starred_url":"https://api.github.com/users/mastohhh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mastohhh/subscriptions","organizations_url":"https://api.github.com/users/mastohhh/orgs","repos_url":"https://api.github.com/users/mastohhh/repos","events_url":"https://api.github.com/users/mastohhh/events{/privacy}","received_events_url":"https://api.github.com/users/mastohhh/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/labels/bug","name":"bug","color":"e11d21"},{"url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/labels/core-data","name":"core-data","color":"006b75"},{"url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/labels/needsmoreinfo","name":"needsmoreinfo","color":"0052cc"},{"url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/labels/ready","name":"ready","color":"ededed"},{"url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/labels/size-medium","name":"size-medium","color":"fad8c7"}],"state":"open","locked":false,"assignee":{"login":"pasin","id":801454,"avatar_url":"https://avatars.githubusercontent.com/u/801454?v=3","gravatar_id":"","url":"https://api.github.com/users/pasin","html_url":"https://github.com/pasin","followers_url":"https://api.github.com/users/pasin/followers","following_url":"https://api.github.com/users/pasin/following{/other_user}","gists_url":"https://api.github.com/users/pasin/gists{/gist_id}","starred_url":"https://api.github.com/users/pasin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pasin/subscriptions","organizations_url":"https://api.github.com/users/pasin/orgs","repos_url":"https://api.github.com/users/pasin/repos","events_url":"https://api.github.com/users/pasin/events{/privacy}","received_events_url":"https://api.github.com/users/pasin/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/milestones/13","html_url":"https://github.com/couchbase/couchbase-lite-ios/milestones/1.2","labels_url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/milestones/13/labels","id":873603,"number":13,"title":"1.2","description":"Feature release, incorporating ForestDB storage engine","creator":{"login":"snej","id":43241,"avatar_url":"https://avatars.githubusercontent.com/u/43241?v=3","gravatar_id":"","url":"https://api.github.com/users/snej","html_url":"https://github.com/snej","followers_url":"https://api.github.com/users/snej/followers","following_url":"https://api.github.com/users/snej/following{/other_user}","gists_url":"https://api.github.com/users/snej/gists{/gist_id}","starred_url":"https://api.github.com/users/snej/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/snej/subscriptions","organizations_url":"https://api.github.com/users/snej/orgs","repos_url":"https://api.github.com/users/snej/repos","events_url":"https://api.github.com/users/snej/events{/privacy}","received_events_url":"https://api.github.com/users/snej/received_events","type":"User","site_admin":false},"open_issues":16,"closed_issues":61,"state":"open","created_at":"2014-11-20T01:04:30Z","updated_at":"2015-09-12T00:09:41Z","due_on":null,"closed_at":null},"comments":6,"created_at":"2015-09-04T15:16:24Z","updated_at":"2015-09-18T17:06:07Z","closed_at":null,"body":"I don't know how UnitTest this, so I'll try to explain my problem here.\r\n\r\nI have an `Entry` document that has many `Subentries`.\r\n\r\nTo display all subentries of an entry, I use a `NSFecthedResultsController` on `Subentry` entity with this predicate : \r\n\r\n```\r\n[NSPredicate predicateWithFormat:@\"entry == %@\", entry];\r\n```\r\n\r\nWhen the controller triggers delegate with : \r\n\r\n```\r\n- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller\r\n```\r\n\r\nI can loop on `controller.fetchedObjects` or `entry.subentries`. This works fine while subentries are created one after an other on each devices.\r\n\r\nBut when I create them simultaneously,  `controller.fetchedObjects` contains good subentries, but `entry.subentries` is not updated. One subentry is missing on each device.\r\n\r\nI'm using `master` branch of CouchbaseLite and NSIncrementalStore.\r\n\r\nI would like to UnitTest it for you but I didn't find a similar one that simulates 2 devices."},"comment":{"url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/issues/comments/141508732","html_url":"https://github.com/couchbase/couchbase-lite-ios/issues/903#issuecomment-141508732","issue_url":"https://api.github.com/repos/couchbase/couchbase-lite-ios/issues/903","id":141508732,"user":{"login":"mastohhh","id":1760064,"avatar_url":"https://avatars.githubusercontent.com/u/1760064?v=3","gravatar_id":"","url":"https://api.github.com/users/mastohhh","html_url":"https://github.com/mastohhh","followers_url":"https://api.github.com/users/mastohhh/followers","following_url":"https://api.github.com/users/mastohhh/following{/other_user}","gists_url":"https://api.github.com/users/mastohhh/gists{/gist_id}","starred_url":"https://api.github.com/users/mastohhh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mastohhh/subscriptions","organizations_url":"https://api.github.com/users/mastohhh/orgs","repos_url":"https://api.github.com/users/mastohhh/repos","events_url":"https://api.github.com/users/mastohhh/events{/privacy}","received_events_url":"https://api.github.com/users/mastohhh/received_events","type":"User","site_admin":false},"created_at":"2015-09-18T17:06:07Z","updated_at":"2015-09-18T17:06:07Z","body":"Ok I think I found it.\r\n\r\nReplication gives batches of documents to the context. And sometimes Subentries comes in a separate batch of documents than their Entries.\r\n\r\nSo, when a Subentry is inserted, his Entry has already been updated and refreshed in the previous batch and at that time, the refresh didn't see his subentries.\r\n\r\nWhat I do now, when an object is inserted, in addition to refresh it, I refresh all his toOne relationships. On every new subentries, I refresh their entry.\r\n\r\n``` objc\r\n- (void) informManagedObjectContext: (NSManagedObjectContext*)context updatedIDs: (NSArray*)updatedIDs deletedIDs: (NSArray*)deletedIDs {\r\n    [context performBlock:^{\r\n        NSMutableDictionary* userInfo = [NSMutableDictionary dictionaryWithCapacity: 3];\r\n        NSMutableSet* updatedEntities = [NSMutableSet set];\r\n\r\n        if (updatedIDs.count > 0) {\r\n            NSMutableArray* updated = [NSMutableArray arrayWithCapacity: updatedIDs.count];\r\n            NSMutableArray* inserted = [NSMutableArray arrayWithCapacity: updatedIDs.count];\r\n\r\n            for (NSManagedObjectID* mocid in updatedIDs) {\r\n                NSManagedObject* moc = [context objectRegisteredForID: mocid];\r\n                if (!moc) {\r\n                    moc = [context objectWithID: mocid];\r\n                    [inserted addObject: moc];\r\n\r\n                    // Ensure that a fault has been fired:\r\n                    [moc willAccessValueForKey: nil];\r\n                    [context refreshObject: moc mergeChanges: YES];\r\n\r\n                    /*\r\n                     REFRESH TO-ONE OBJECTS\r\n                     */\r\n                    for (NSString *relationshipName in moc.entity.relationshipsByName) {\r\n                        NSRelationshipDescription *relationshipDescription = moc.entity.relationshipsByName[relationshipName];\r\n\r\n                        if (NO == relationshipDescription.toMany) {\r\n                            NSManagedObject *destinationObject = [moc valueForKey:relationshipName];\r\n                            if (nil != destinationObject) {\r\n                                // Ensure that a fault has been fired:\r\n                                [destinationObject willAccessValueForKey: nil];\r\n                                [context refreshObject: destinationObject mergeChanges: YES];\r\n                            }\r\n                        }\r\n                    }\r\n                    /*\r\n                     END\r\n                     */\r\n                } else {\r\n                    [updated addObject: moc];\r\n\r\n                    // Ensure that a fault has been fired:\r\n                    [moc willAccessValueForKey: nil];\r\n                    [context refreshObject: moc mergeChanges: YES];\r\n                }\r\n\r\n                [updatedEntities addObject: moc.entity.name];\r\n            }\r\n            [userInfo setObject: updated forKey: NSUpdatedObjectsKey];\r\n            if (inserted.count > 0) {\r\n                [userInfo setObject: inserted forKey: NSInsertedObjectsKey];\r\n            }\r\n        }\r\n\r\n        if (deletedIDs.count > 0) {\r\n            NSMutableArray* deleted = [NSMutableArray arrayWithCapacity: deletedIDs.count];\r\n            for (NSManagedObjectID* mocid in deletedIDs) {\r\n                NSManagedObject* moc = [context objectWithID: mocid];\r\n                [context deleteObject: moc];\r\n                // load object again to get a fault\r\n                [deleted addObject: [context objectWithID: mocid]];\r\n\r\n                [updatedEntities addObject: moc.entity.name];\r\n            }\r\n            [userInfo setObject: deleted forKey: NSDeletedObjectsKey];\r\n        }\r\n\r\n        // Clear cache:\r\n        for (NSString* entity in updatedEntities) {\r\n            [self purgeCachedObjectsForEntityName: entity];\r\n        }\r\n\r\n        NSNotification* didUpdateNote = [NSNotification notificationWithName: NSManagedObjectContextObjectsDidChangeNotification\r\n                                                                      object: context\r\n                                                                    userInfo: userInfo];\r\n        [context mergeChangesFromContextDidSaveNotification: didUpdateNote];\r\n\r\n        [[NSNotificationCenter defaultCenter] postNotificationName: kCBLISObjectHasBeenChangedInStoreNotification\r\n                                                            object: self\r\n                                                          userInfo: @{\r\n                                                                      NSDeletedObjectsKey: deletedIDs,\r\n                                                                      NSUpdatedObjectsKey: updatedIDs\r\n                                                                      }];\r\n    }];\r\n}\r\n```\r\n\r\nI didn't reproduce the bug anymore. I think I solved it. I just need you to tell me if this is good. Do you want a pull request ?\r\n\r\nI don't know how to reproduce it with a UnitTest. It would be great to have one for that."}}