-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Properly update a model after creation #984
Comments
The way you are using |
I am able to save to the object I created with What I'm seeing is that after a record is created, the object I have in memory doesn't update the record when it changes. If I try to save it manually after changes have been made it creates a whole new record instead of updating it. I tried the synchronous way but I get another problem. After the new record is made, the object I have in memory has all of its fields turned to nil as if the object has been flushed. The workflow I need is as follows:
This all happens very quickly. In the time between saving the record locally and the API coming back with new data I still hold the Please forgive my ignorance in this regarding Core Data. I've been doing a lot of reading and I have yet found the answer. Please advise! |
I took it a step further and generate a UUID on This seems so strange to me. It seems like I will have to use this method but delete the found record when updating it since a new one will be replacing it. |
When updating an existing record from the API you would need to find the existing record and either update it or delete it and create a new one. I often use this pattern: [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// Assuming dict is a dictionary of parsed data from the API
Order *order = [Order findFirstByAttribute:@"uuid" withValue:dict[@"uuid"] inContext:localContext];
if (!order)
order = [Order MR_createEntityInContext:localContext];
// Set data on order object
}]; If you have an object already in memory and want to save changes in a block: // Somewhere above you got this
// Order *myOrder = […]
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Order *orderInContext = [myOrder MR_inContext:localContext];
orderInContext.someValue = @"newValue";
} completion:^(BOOL success, NSError *error){
// Do any work you need to do post-saving here
}]; Did you turn on concurrency debugging? Any concurrency issues will make you go mad by giving you all sorts of weird problems. There is more info about it here: https://github.com/magicalpanda/MagicalRecord/blob/develop/Docs/Threads.md |
1.) Have you turned on concurrency debugging yet? Order *o = [order MR_inContext:localContext]; |
@thomassnielsen just beat me to it! |
When I enable concurrency debugging my app stops on startup breaking on
The break states
- (NSString *) MR_workingName;
{
NSString *workingName = [[self userInfo] objectForKey:kMagicalRecordNSManagedObjectContextWorkingName];
if (nil == workingName)
{
workingName = @"UNNAMED";
}
return workingName;
} I assume this is from
I have
Beyond that, without the concurrency debugging turned on, when I use the method above mentioned I have [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSLog(@"Saving order locally");
Order* localOrder = [Order MR_createInContext:localContext];
localOrder.transactionType = self.transactionType;
localOrder.total = self.total;
} completion:^(BOOL success, NSError *error) {
if(success)
{
NSLog(@"Order local save succeeded");
[self createRemoteOrder];
[delegate createLocalSucceeded:self];
}
else
{
NSLog(@"Order local save failed\n%@", error);
[delegate createLocalFailed:error];
}
}]; This succeeds and only after this thread is complete does it start the next step. Then in the API POST's success block it calls [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSLog(@"Saving order locally");
Order* localOrder = [self MR_inContext:localContext];
localOrder.orderId = [dictionary valueForKey:@"id"];
localOrder.confirmationCode = [NSString stringWithFormat:@"%@", [dictionary valueForKey:@"confirmationCode"]];
localOrder.status = [dictionary valueForKey:@"status"];
localOrder.createdAt = [dictionary valueForKey:@"createdAt"];
} completion:^(BOOL success, NSError *error) {
if(success)
{
NSLog(@"Order local save succeeded");
[delegate updateLocalSucceeded:self];
}
else
{
NSLog(@"Order local save failed\n%@", error);
[delegate updateLocalFailed:error];
}
}]; Doing it this way results in the log output
Stepping through starting on Since these all occur in each success block I don't see it being a concurrency issue. Please forgive me if I'm being thick as I am new to core data. |
What version of MagicalRecord are you using? You should try version 2.3beta5. That should resolve the concurrency exception on the working name. Then you can turn concurrency debugging back on. Next, you say that you call - (id) MR_inContext:(NSManagedObjectContext *)otherContext
{
NSError *error = nil;
NSManagedObject *inContext = [otherContext existingObjectWithID:[self objectID] error:&error];
[MagicalRecord handleErrors:error];
return inContext;
} Versus what is now in v2.3beta5: - (id) MR_inContext:(NSManagedObjectContext *)otherContext
{
NSError *error = nil;
if ([[self objectID] isTemporaryID])
{
BOOL success = [[self managedObjectContext] obtainPermanentIDsForObjects:@[self] error:&error];
if (!success)
{
[MagicalRecord handleErrors:error];
return nil;
}
}
error = nil;
NSManagedObject *inContext = [otherContext existingObjectWithID:[self objectID] error:&error];
[MagicalRecord handleErrors:error];
return inContext;
}
You'll notice in v2.3beta5 it attempts to resolve temporary ID problems for you. I suspect if you put a break on line 225 in |
Changing from - (void)createLocal
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DDLogInfo(@"Saving order locally");
Order* localOrder = [Order MR_createInContext:localContext];
localOrder.transactionType = self.transactionType;
localOrder.total = self.total;
} completion:^(BOOL success, NSError *error) {
if(success)
{
DDLogInfo(@"Order local save succeeded");
[self createRemote];
[delegate createLocalSucceeded:self];
}
else
{
DDLogInfo(@"Order local save failed\n%@", error);
[delegate createLocalFailed:error];
}
}];
} to - (void)createLocal
{
[self.managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if(success)
{
DDLogInfo(@"Order local save succeeded");
[delegate createLocalSucceeded:self];
}
else
{
DDLogInfo(@"Order local save failed\n%@", error);
[delegate createLocalFailed:error];
}
}];
} also saves the record but I notice that all the properties in the order, self, are now nil after the save completes. Is saving a model supposed to flush its properties? Is there a way to preserve them? |
Are you sure that the order does have its properties set when you call save? I've never seen Core Data saving flush properties an object. Can you try this and post the results? NSLog(@"Before: %@", self.someProperty);
[self.managedObjectContext MR_saveToPersistentStoreAndWait];
NSLog(@"After: %@", self.someProperty); |
- (void)createLocal
{
/*[self.managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if(success)
{
DDLogInfo(@"Order local save succeeded");
[self createRemote];
[delegate createLocalSucceeded:self];
}
else
{
DDLogInfo(@"Order local save failed\n%@", error);
[delegate createLocalFailed:error];
}
}];*/
NSLog(@"Before: %@", self.transactionType);
[self.managedObjectContext MR_saveToPersistentStoreAndWait];
NSLog(@"After: %@", self.transactionType);
}
I should mention I'm using version 2.2 |
You should use v2.3beta5, as I previously mentioned. |
So I was doing other things with MagicalRecord / Core Data with other models and having to mimic the API's backend database structure to cache its responses and I thought it was going to get really hairy real quick, but it didn't. Everything worked pretty much on the first attempt. I noticed one major difference between my - (void)createLocal
{
[self.managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if(success)
{
DDLogInfo(@"Order local save succeeded");
[self createRemote];
[delegate createLocalSucceeded:self];
}
else
{
DDLogInfo(@"Order local save failed\n%@", error);
[delegate createLocalFailed:error];
}
}];
}
- (void)updateLocal
{
[self.managedObjectContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if(success)
{
DDLogInfo(@"Order local save succeeded");
[delegate updateLocalSucceeded:self];
}
else
{
DDLogInfo(@"Order local save failed\n%@", error);
[delegate updateLocalFailed:error];
}
}];
} and |
I have a model called
Order
that extendsNSManagedObject
. In it are methods to save locally and remotely to an api. I want to be able to save the order locally and POST to the api independent of each other. However, when the POST is successful I want to update the order I just made locally.I create an
Order
in memory:Order* o = [Order MR_createEntity];
I save the
Order
locally:I POST to the api using AFNetworking and in the POST success block I have:
With the code as it is it creates an Order locally as desired with some database fields missing (will come from the api). Then after the api comes back with more data I try to save the updated fields but instead of updating, a new record is created with all fields populated.
So my question is, how do I update my Order without duplicating it in the local database? I suspect this has to do with contexts and using them correctly.
Here is an example of my sqlite table after one
Order
creation with update attempt:The text was updated successfully, but these errors were encountered: