I'm using a category on NSManagedObject, called NSManagedObject+Serialization.h located here https://gist.github.com/nuthatch/5607405.
Everything pretty much works great, but I need to implement this but not sure how? I want to skip some objects.
- (NSDictionary*) toDictionary {
// Check to see there are any objects that should be skipped in the traversal.
// This method can be optionally implemented by NSManagedObject subclasses.
NSMutableSet *traversedObjects = nil;
if ([self respondsToSelector:#selector(serializationObjectsToSkip)]) {
traversedObjects = [self performSelector:#selector(serializationObjectsToSkip)];
}
return [self toDictionaryWithTraversalHistory:traversedObjects];
}
How do I add object relationships to be skipped?
Many Thanks
In your managed object subclass you must implement the serializationObjectsToSkip:
- (NSMutableSet*) serializationObjectsToSkip
{
NSMutableSet* objectsToSkip = [NSMutableSet new];
//Here you select objects that relate to this object and you don't want to serialise.
//Insert them into `objectsToSkip`
return objectsToSkip;
}
However, the implementation of the serialisation looks buggy (lines 80 and 93) ... (if you don't supply all objects to skip in advance)
The toDictionary of the relatedObject is skipped and so objects that the related objects may want to skip will not be added to the traversal history set ...
A quick fix might be to replace these lines with the full implementation of toDictionary and merging the traversal history set and the objectsToSkip sets returned ...
A better solution will be to change the signature of the toDictionary method to accept the traversal history and do the set merging there and replace the above lines with toDictionary of the related object.
Related
How do I sort my fetched results by a value that is returned by a method in a category of the entity I'm fetching?
In my category, I sum up several values from the entity's to-many relationship, then divide by the number of objects in the relationship, effectively creating an average that I return in my category method as a float value.
Here is my code:
In the Category.h
- (float)smallPenaltyAvg;
In the Category.m
- (float)smallPenaltyAvg{
float smallPenaltyAvg = 0;
for (Match *mtch in self.matches) {
smallPenaltyAvg += [mtch.penaltySmall floatValue];
}
if ([self.matches count] > 0) {
smallPenaltyAvg = (float)smallPenaltyAvg/(float)[self.matches count];
}
return smallPenaltyAvg;
}
And when I call it in the Core Data Table View Controller class that I created...
NSFetchRequest *poolRequest = [[NSFetchRequest alloc] initWithEntityName:#"Team"];
poolRequest.predicate = [NSPredicate predicateWithFormat:#"regionalIn.name = %#", _regionalToDisplay];
poolRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"smallPenaltyAvg" ascending:YES]];
And I have the Category.h file imported on every file previously mentioned outside of the Category.h file itself.
It gives me the error of:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath smallPenaltyAvg not found in entity <NSSQLEntity Team id=5>
Am I not allowed to do this?
If I am, what am I doing wrong?
I do not think this has anything to do with the kind of persistent store.
The trick is to create an appropriate attribute in the managed object model, and mark it as Transient. Then override the getter of this attribute to do your calculations.
Now your fetch request should work as expected (although there are some caveats with fetched results controllers).
As for the SQLite problem, when you add the SQLite store with
- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType
configuration:(NSString *)configuration
URL:(NSURL *)storeURL
options:(NSDictionary *)options
error:(NSError **)error
just pass NSSQLiteStoreType as the storeType. The other options are binary and in-memory, so in this sense this is indeed the "default".
This is not possible when using a backing SQLite store.
My suggestion is you persist the average property, and maintain it yourself by overriding the Match setCategory: property and making the calculation there for every match added.
What I did to solve my problem was create a new attribute for every average or sum that I needed in the Team object from all of its Match objects' attributes and then created a method in the TeamCategory file that populated those averages and that method was called every time a Match object was inserted into the Team object. It took a while to do, but it works now. If there is a better solution, I'm still open for suggestions.
I have two entities:
Ticket
TicketResolved
Both entities have the same attributes. What would be the the most efficient way to copy a Ticket NSManagedObject to TicketResolved NSManagedObject?
I'm thinking using a Category: Ticket+Copy be the least expensive way? If so, I would have to #import both Ticket and TicketResolved in the Category file.
Here is what I came up with, can someone please advise if this is the right way of going about it. I'm using NSManagedObjectSubclass for each entity.
Method in Ticket+Copy:
-(TicketResolved *)copyObjects:(Ticket *)ticket
{
TicketResolved *ticketResolved = [NSEntityDescription insertNewObjectForEntityForName:#"TicketResolved" inManagedObjectContext:self.managedObjectContext];
ticketResolved.attribute = ticket.attribute;
// Assign rest of the ticketResolved attributes values this way
return ticketResolved;
}
Now calling the Method
#import Ticket;
#import Ticket+Copy;
#implementation
....
Ticket *ticket = [NSEntityDescription insertNewObjectForEntityForName:#"Ticket" inManagedObjectContext:self.managedObjectContext];
TicketResolved *newTicketResolved = [ticket copyObjects:ticket];
// 'newTicketResolved' now has all the keys/values that 'ticket' had.
Is this a right approach or is there a simpler way to do this?
If Ticket and TicketResolved actually have the same attributes, the most efficient option is to:
Get rid of TicketResolved
Add a boolean flag on Ticket named resolved that you can set to YES when the ticket is resolved.
Use this attribute it fetch requests to get either resolved or non-resolved tickets, whichever you need.
Then you don't actually need to copy any data, and not doing work is always more efficient than doing it.
If for some reason you really want two separate entities with the same attributes, basically you have it, you need to create a TicketResolved instance and have your code copy over every attribute value. The only major problem with your code is lines like this:
Ticket *ticket = [Ticket alloc]init];
You can't create managed objects like that, because you're not calling the designated initializer. You need to either use -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:] or else use +[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:].
Thanks to #Tom, I did correct my error in the question.
Below is the solution that worked for me:
TicketResolved *ticketResolved = [NSEntityDescription insertNewObjectForEntityForName:#"TicketResolved" inManagedObjectContext:self.managedObjectContext];
NSArray *keys = [[[ticket entity] attributesByName] allKeys];
NSDictionary *dict= [ticket dictionaryWithValuesForKeys:keys];
[ticketResolved setValuesForKeysWithDictionary:dict];
The code above does not copy the Relationships Objects. For that I had to use the code below:
ticketResolved.relationshipObject = ticket.relationshipObject;
in the context of some tests I'm writing I've found the following issue. I make use of RestKit 0.20.2, among the other natural purposes, also to manage my coredata-related aspects.
In one of those tests, I create a CoreData entity A, containing a Date as one of its fields, by assigning it a Nil value (it's an optional field). My saving function performs the following code:
NSError* myError;
[[RKTestFactory managedObjectStore].mainQueueManagedObjectContext saveToPersistentStore:&myError];
NSLog(#"Save done on main queue with myError: %#", myError);
NSAssert1(myError == Nil, #"Coredata saving error: %#", myError);
After this save, an algorithm is run which operates on A and update that date field. This algorithm works in a private managed object context:
// Get a Local Managed Object Context
NSManagedObjectContext* myLocalMOC = [[DDRestKitManager sharedInstance].objectManager.managedObjectStore newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType trackChanges:YES];
(DDRestKitManager is a singleton managing about every RestKit-related aspect of my project). This algorithm first retrieves A with a FetchRequest within its private managed object context, then operates on it and finally updates its date field. It then saves every CoreData related aspect it dealt with (including the updated A-status) with a save on its private MOC.
When, in the previous test body, I need to invoke the very same algorithm again on the very same entity A after having updated some of its fields in order to test the new algorithm outcome, I need to put A's date field back to Nil before invoking the algorithm. This is what I do:
A.date_field = Nil;
[[TestCoreDataManager sharedInstance] save];
// <invoke_algorithm>
(TestCoreDataManager is a further singleton providing objects and saving them by means of the previously reported function). The problem is that when the algorithm retrieves again the object, the date_field is not Nil but still contains the previously assigned value.
It seems like the instance of A retrieved by the algorithm in its private context is not up-to-date wrt the underlying persistent store. Is there anyone who might tell me what I'm doing wrong?
Some more details
I've just moved the default semantics of this date field in order to be always not nil and to have 0 as default value. I rewrote the algorithm in order for the test condition to be [A.date_field timeIntervalSince1970] == 0 in place of A.date_field == Nil.
In my code, in order for that test to be met, I use the following code
A.date_field = [NSDate dateWithTimeIntervalSince1970:0];
[TestCoreDataManager save]; // [mainManagedObjectContext saveOnPersistentStore:&myError];
// Get a Local Managed Object Context
NSManagedObjectContext* myLocalMOC =
[[DDRestKitManager sharedInstance].objectManager.managedObjectStore
newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType
tracksChanges:YES];
<query_for_A_entities_in_the_store>;
At this point A.date_field contains the value it had before resetting to 0 seconds from 1970.
The even more strange part follows. If I replace
A.date_field = [NSDate dateWithTimeIntervalSince1970:0];
with
A.date_field = [NSDate dateWithTimeIntervalSince1970:1];
or
A.date_field = [NSDate dateWithTimeIntervalSince1970:-1];
and leave the rest of the code untouched, then the child managed object context fetch an A object with the date_field now up-to-date to the correct date and time (1970, 1:00:01 a.m. or 0:59:59 a.m. respectively).
This is driving me crazy.
I have two entities, Item and Category. Each item has one category, and a category can have 0-many items.
I have a special category, the misc category. I denote this with a boolean property, isMisc, so category.isMisc=YES.
When I delete a category, I want to reassign any of its items to the "misc" category. So I wrote the following custom setter for item:
- (void)setCategory:(Category *)category
{
[self willChangeValueForKey:#"category"];
if (category == nil) {
category = [Database theMiscCategory];
}
[self setPrimitiveValue:category forKey:#"category"];
[self didChangeValueForKey:#"category"];
}
The problem is, [Database theMiscCategory] performs a fetch, which I believe is discouraged. Is there another way to do this?
I have looked at just letting item.category = nil, but this introduces enough complications in other areas of the code that I'd much rather have a "misc" category.
You could create or fetch the "misc" category object once in your program, e.g. after creating the managed object context. Then your custom setter method can always use this instance.
If you work with several managed object contexts, then you would have to create one object for each context.
Is there any way I can validate a value updated in a Core Data entity's property against values of the property in other entities in the collection?
At the moment I create an entity with some default values, add it to arrangedObjects, then get the user to modify the various property values. However, I would like to check a particular property and make sure there're no other entities in the array with the same value for that property. What's the best way to do this?
Many thanks,
Dany.
Manually checking is only a few lines of code with a fast enumeration loop:
BOOL unique = YES;
for (NSManagedObject *obj in collection) {
if (obj.property == value) {
unique = NO;
break;
}
}