I few questions regarding RestKit and foreign key relationship mapping.
Before starting with my questions here is what is in place and working properly.
Core Data model
Favorite has the following attributes :
placeIds: transformable type that hold the place ids that are define as user's favorite places.
RestKit
a. User data mapping
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:[self entityName]
inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[entityMapping addAttributeMappingsFromDictionary:#{#"id":#"userId"}];
entityMapping.identificationAttributes = #[ #"userId" ];
[entityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"favorites" toKeyPath:#"favorites" withMapping:[AOFavorite mapping]]];
[entityMapping setAssignsDefaultValueForMissingAttributes:YES];
[entityMapping setAssignsNilForMissingRelationships:YES];
b. Favorite Data mapping
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:[self entityName]
inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[entityMapping addAttributeMappingsFromDictionary:#{#"placeIds":#"placeIds"}];
entityMapping.identificationAttributes = #[ #"userId" ];
[entityMapping addConnection:[AOFavorite placesConnection]];
[entityMapping setAssignsDefaultValueForMissingAttributes:YES];
[entityMapping setAssignsNilForMissingRelationships:YES];
+ (RKConnectionDescription *)placesConnection
{
NSEntityDescription *entity = [NSEntityDescription entityForName:[self entityName]
inManagedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext];
NSRelationshipDescription *places = [entity relationshipsByName][#"places"];
return [[RKConnectionDescription alloc] initWithRelationship:places attributes:#{ #"placeIds": #"placeId" }];
}
c. Place Data mapping
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:[self entityName]
inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[entityMapping addAttributeMappingsFromDictionary:#{#"id":#"placeId"}];
entityMapping.identificationAttributes = #[ #"placeId" ];
[entityMapping setAssignsDefaultValueForMissingAttributes:YES];
[entityMapping setAssignsNilForMissingRelationships:YES];
User entity has the logged in user object correctly fit into persistent store.
Since the API provide User's favorite places with an array of ids, they are stored into User > Favorite > placeIds
--
I'm using the following method to request the API's object for the Favorite's place relationship with the Route defined as is:
[[[[RKObjectManager sharedManager] router] routeSet] addRoute:[RKRoute routeWithRelationshipName:#"favorite/places"
objectClass:[AOFavorite class]
pathPattern:#"user/favorite/places"
method:RKRequestMethodGET]];
[[RKObjectManager sharedManager] getObjectsAtPathForRelationship:#"favorite/places"
ofObject:[[AOUser currentUser] favorites]
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
Problem and question
a. getObjectsAtPathForRelationship has the places correctly created into the Place entity but the Favorite > Place relationship isn't linked.
b. what is the purpose of having the placeIds into Favorite entities. I have seen SO question/response regarding Foreign key mapping stating that this is the way to do it. Can someone explained why ?
Related
I added some custom fields in my User class and I would like to hide some of them to the users like the password and verifiedEmail ones.
Is there a way to do this?
When querying an object, you can specify which fields should be returned by using the selectKeys method.
Here is an example for iOS:
PFQuery *query = [PFQuery queryWithClassName:#"GameScore"];
[query selectKeys:#[#"playerName", #"score"]];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
// objects in results will only contain the playerName and score fields
}];
And one for Android:
ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore");
query.selectKeys(Arrays.asList("playerName", "score"));;
List<ParseObject> results = query.find();
If you're developing on another platform, consult the relevant documentation for the one you use (https://www.parse.com/docs/).
I have a user , activty, photo class. Where user likes other users Attached screenshot of Activity Class https://dl.dropboxusercontent.com/u/33860877/Activity.png
My problem is that when a user likes more than 1000, the limitation on the "filter likes" query will cause below method to get users that the user has already liked because number of activity (likes) more than 1000. What should be done here to avoid this?
-(void) loaddata {
PFQuery *filterUsers = [PFUser query];
[filterUsers whereKey:kMUParseUserAccountStatus equalTo:#"Active"];
filterUsers.limit =1000;
PFQuery *filterLikes = [[PFQuery alloc] initWithClassName:kMUActivityClassKey];
[filterLikes whereKey:kMUActivityUserFromKey equalTo:[PFUser currentUser]];
filterLikes.limit =1000;
PFQuery *query = [[PFQuery alloc] initWithClassName:kMUPhotoClassKey];
[query whereKey:kMUPhotoUserKey notEqualTo:[PFUser currentUser]];
[query whereKey:kMUPhotoPicturePriorityKey equalTo:#(0)];
[query whereKey:kMUPhotoUserKey matchesQuery:filterUsers];
[query whereKey:kMUPhotoUserKey doesNotMatchKey:kMUActivityUserToKey inQuery:filterLikes];
[query includeKey:kMUPhotoUserKey];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error)
{
if(objects.count>0)
{
[self updateInformation];
}
else
{
if(objects.count==0)
{
self.hasMorePhotos = false;
}
}
}
}];
}
You need to work around the 1000 record limit.
If I understand you correctly, you want to show photos that the current user HAS NOT YET LIKED. Your solution is not efficient, as you compare a key in a query of 1000 objects (or, if the limit was not 1000, your solution would perhaps need to compare thousands of objects).
If you need to filter out liked photos, you could instead store an array of objectIDs on the user. When the user likes a photo, you could add this objectId to an array on the User object, or another object tied to the user. Then, in your query, you could compare with this array rather than another query:
[query whereKey:#"objectId" notContainedIn:arrayOfLikedObjectIds];
Currently I have several instances where I need to send a group of objects to the server:
{
"things": [
{
//object stuff
},
{
//object stuff
},
...
]
}
So what I've been doing is defining an intermediate object MyThingPayload
#interface MyThingPayload
#property (nonatomic, strong) NSArray *things;
#end
And then when mapping
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:NSClassFromString(#"MyThingPayload")];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"things"
toKeyPath:#"things"
withMapping:[self entityMappingForManagedThingObject]]];
Seems like unnecessary overhead. Is there a way to do this without the intermediate object that holds an array?
You need an intermediate object to provide the structure to be used during serialisation. It doesn't need to be a custom class though, it can just be an NSDictionary containing the correct key and NSArray value.
I currently have a cardType attribute on my entity, which in the old model could be "Math", "Image" or "Text". In the new model, I'll be using just "Math" and "Text" and also have a hasImage attribute, which I want to set to true if the old cardType was Image (which I want to change to "Text").
Lastly, I have a set of another entity, "card", of which a set can be associated with a deck, and in each of those, I'll also have hasImage which I want to set to true if the deck was of "Image" type before.
Is this all possible using the Value Expression in the Mapping Model I've created between the two versions, or will I have to do something else?
I can't find any document telling me exactly what is possible in the Value Expression (Apple's doc - http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreDataVersioning/Articles/vmMappingOverview.html%23//apple_ref/doc/uid/TP40004735-SW3 - only has a very simple transformation). If I have to do something else, what would that be? This seems simple enough that an expression should be able to do it.
One thing you can do is create a custom migration policy class that has a function mapping your attribute from the original value to a new value. For example I had a case where I needed to map an entity called MyItems that had a direct relationship to a set of values entities called "Items" to instead store an itemID so I could split the model across multiple stores.
The old model looked like this:
The new model looks like this:
To do this, I wrote a mapping class with a function called itemIDForItemName and it was defined as such:
#interface Migration_Policy_v1tov2 : NSEntityMigrationPolicy {
NSMutableDictionary *namesToIDs;
}
- (NSNumber *) itemIDForItemName:(NSString *)name;
#end
#import "Migration_Policy_v1tov2.h"
#implementation Migration_Policy_v1tov2
- (BOOL)beginEntityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error {
namesToIDs = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:1],#"Apples",
[NSNumber numberWithInt:2],#"Bananas",
[NSNumber numberWithInt:3],#"Peaches",
[NSNumber numberWithInt:4],#"Pears",
[NSNumber numberWithInt:5],#"Beef",
[NSNumber numberWithInt:6],#"Chicken",
[NSNumber numberWithInt:7],#"Fish",
[NSNumber numberWithInt:8],#"Asparagus",
[NSNumber numberWithInt:9],#"Potato",
[NSNumber numberWithInt:10],#"Carrot",nil];
return YES;
}
- (NSNumber *) itemIDForItemName:(NSString *)name {
NSNumber *iD = [namesToIDs objectForKey:name];
NSAssert(iD != nil,#"Error finding ID for item name:%#",name);
return iD;
}
#end
Then for the related Mapping Name for the attribute in your mapping model you specify the Value Expression as the result of your function call as such:
FUNCTION($entityPolicy,"itemIDForItemName:",$source.name) **
You also have to set the Custom Policy Field of your Mapping Name for that attribute to your mapping class name (in this case Migration_Policy_v1tov2).
**note this should match the selector signature of the method
Imagine an CoreData entity (e.g. named searchEngine).
NSManagedObjectContext manages some "instances" of this entity.
The end-user is going to be able to select his "standard searchEngine" with a NSPopupButton.
The selected object of NSPopupButton should be binded to the NSUserDefaults.
The problem:
1) #try{save}
a) If you try to save the selected "instance" directly to NSUserDefaults there comes something like this:-[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value ' (entity: searchEngine; id: 0x156f60 ; data: {
url = "http://google.de/";
someAttribute = 1;
name = "google";
})' of class 'searchEngine'.
b) If you try to convert the "instance" to NSData comes this:-[searchEngine encodeWithCoder:]: unrecognized selector sent to instance 0x1a25b0
So any idea how to get this entities in a plist-compatible data?
2) #try{registerDefaults}
Usually the registerDefaults: method is implemented in + (void)initialize. The problem here is that this method is called before CoreData loads the saved entities from his database. So I can't set a default to a no-existing object, right?
I know, long questions... but: try{[me provide:details]} ;D
If you need to store a reference to a specific managed object, use the URI representation of its managed object ID:
NSURL *moIDURL = [[myManagedObject objectID] URIRepresentation];
You can then save the URL to user defaults.
To retrieve the managed object, you use:
NSManagedObjectID *moID = [myPersistentStoreCoordinator managedObjectIDForURIRepresentation:moIDURL];
NSManagedObject *myManagedObject = [myContext objectWithID:moID];
The only caveat is that you must ensure that the original managed object ID is permanent -- this is not a problem if you've already saved the object, alternatively you can use obtainPermanentIDsForObjects:error:.
Here's the cleanest and shortest way to currently do this using the setURL and getURL methods added in 4.0 to avoid extra calls to NSKeyedUnarchiver and NSKeyedArchiver:
Setter:
+ (void)storeSomeObjectId:(NSManagedObjectID *)objectId
{
[[NSUserDefaults standardUserDefaults] setURL:[objectId URIRepresentation]
forKey:#"someObjectIdKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Getter:
+ (SomeManagedObject *)getObjectByStoredId
{
NSURL *uri = [[NSUserDefaults standardUserDefaults] URLForKey:#"someObjectIdKey"];
NSManagedObjectID *objectId = [self.persistentStoreCoordinator managedObjectIDForURIRepresentation:uri];
SomeManagedObject *object = [self.managedObjectContext objectWithID:objectId];
}
You wouldn't want to try and archive a core data entity and store it. Instead, you would store the key or some other known attribute and use it to fetch the entity when the application starts up.
Some example code (slightly modified from the example posted in the Core Data Programming Guide):
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"SearchEngine" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"engineName LIKE[c] '%#'", selectedEngineName];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil)
{
// Deal with error...
}
This way you save the name in the user defaults and fetch the entity when necessary.
My model saves the record's UUID to the UserDefaults in order to launch the last opened record in the next app launch.
public class Patient: NSManagedObject {
override public func awakeFromInsert() {
super.awakeFromInsert()
uuid = UUID()
}
extension Patient {
#NSManaged public var uuid: UUID
#NSManaged ...
}
It is a good practice to identify each record with an unique ID (UUID). You can save the UUID as string simply calling uuid.uuidString.