I am trying to get my head around PFRelation in parse. I have a class called "girlBio" that stores information about girls and a class called "stuff" that stores information about items. code below:
PFObject *item = [PFObject objectWithClassName:#"stuff"];
item[#"Name"] = #"PS3";
PFObject *girl = [PFObject objectWithClassName:#"girlBio"];
girl[#"Name"] = #"Jessica";
PFObject *girl2 = [PFObject objectWithClassName:#"girlBio"];
girl2[#"Name"] = #"Cindy";
PFRelation *relation = [item relationForKey:#"owners"];
[relation addObject:girl];
[relation addObject:girl2];
[item saveInBackground];
--------------------------------- update also tried this -------------------------
PFObject *item = [PFObject objectWithClassName:#"stuff"];
item[#"Name"] = #"PS3";
PFObject *girl = [PFObject objectWithClassName:#"girlBio"];
girl[#"Name"] = #"Jessica";
[item saveInBackground];
[girl saveInBackground];
PFRelation *relation = [item relationForKey:#"owners"];
[relation addObject:girl];
[item saveInBackground];
So I want this item to be owned by several girls however when I run the program I get this error:
Error: can't add a non-pointer to a relation (Code: 111, Version: 1.6.0)
Can someone help please?
Thank you
You need to save your objects girl1 and girl2 before saving the relationship. Otherwise, even thought your local copy knows about them, the server does not.
UPDATE
You also need to make sure the saves for girl1 and girl2 and item complete before you save the relation. However, you probably don't want to run these saves on the primary thread, so I'd recommend something like this (which I just ran without issue):
dispatch_async(dispatch_get_main_queue(), ^{
PFObject *item = [PFObject objectWithClassName:#"stuff"];
item[#"Name"] = #"PS3";
PFObject *girl = [PFObject objectWithClassName:#"girlBio"];
girl[#"Name"] = #"Jessica";
[item save];
[girl save];
PFRelation *relation = [item relationForKey:#"owners"];
[relation addObject:girl];
[item saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
//Do something after the last save...
}];
});
Related
I am trying to add a list people who follow a user to the column titled "Followers" under the user, which is retrieved from objectatindex of a sender tag.
Here is the code:
-(void)followButton:(id)sender {
UIButton *senderButton = (UIButton *)sender;
NSLog(#"current Row=%ld",(long)senderButton.tag);
PFObject *object = [self.objects objectAtIndex:senderButton.tag];
if (![[object objectForKey:#"followers"]containsObject:[PFUser currentUser].objectId]) {
[[PFUser currentUser] addUniqueObject:object.objectId forKey:#"following"];
[[PFUser currentUser] saveInBackground];
PFUser *otherUser = [self.objects objectAtIndex:senderButton.tag];
NSLog(#"Followed %#", otherUser);
[otherUser addUniqueObject:[PFUser currentUser].objectId forKey:#"followers"];
[otherUser saveInBackground];
// NSLog(#"Followed %#", object);
} else {
[[PFUser currentUser] removeObject:object.objectId forKey:#"following"];
[[PFUser currentUser] saveInBackground];
PFUser *otherUser = [self.objects objectAtIndex:senderButton.tag];
[otherUser removeObject:[PFUser currentUser].objectId forKey:#"followers"];
[otherUser saveInBackground];
}
[self.tableView reloadData];
}
For some reason, the code above only adds to the array of "Following" of the current user. When re-clicked, the object is to be removed but nothing happens. Additionally, the code
PFUser *otherUser = [self.objects objectAtIndex:senderButton.tag];
[otherUser removeObject:[PFUser currentUser].objectId forKey:#"followers"];
[otherUser saveInBackground];
does absolutely nothing meanwhile it supposed to do the work of add to the "Followers" array of the user at the selected row. What am I doing wrong? The goal's to get a working following/followers action. I'm using PFQueryTableViewController!
You can't modify other user's objects from the client. You can only modify the current user. The way to modify users that are the current user is to use the master key in the cloud code by using Parse.Cloud.useMasterKey();
https://parse.com/questions/how-can-i-allow-a-user-to-edit-another-user
So create a function in the cloud code by passing the username of the "otherUser" and the objectId that you want to remove
Parse.Cloud.define("RemoveFollower", function(request, response){
var username = request.params.username;
var id_to_remove = request.params.objectId_passed;
var query = new Parse.Query(Parse.User);
query.equalTo("username", username);
query.find({
success: function(results){
var u = results[0];
//u modify the user u however you like...
u.save();
response.success("Success");
},
error: function(){
response.error("Error");
}
});
My situation includes having PFObjects that are connected with relations and I want to add to the relation using the local datastore.
So, I have a screen that creates a main object and saves it to the data store and server properly like so:
// Save User
PFObject *parseUser = [PFObject objectWithClassName:#"Users"];
parseUser[#"name"] = name;
parseUser[#"uuid"] = userUuid;
[parseUser saveEventually];
// Save List
PFObject *parseList = [PFObject objectWithClassName:#"Lists"];
parseList[#"name"] = newList.listName;
parseList[#"uuid"] = newList.listUuid;
[parseList saveEventually];
[parseUser pinInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// Create relation
PFRelation *relation = [parseUser relationForKey:#"listAccess"];
[relation addObject:parseList];
[parseUser saveEventually];
}
}];
[parseList pinInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// Create relation
PFRelation *relation = [parseList relationForKey:#"sharedWith"];
[relation addObject:parseUser];
[parseList saveEventually];
}
}];
So this part works. This took me a bit to figure out as well, but it works. Creates the User and List. Sets the relations of both then saves. Easy.
On to the problem area:
// Save List
PFObject *parseList = [PFObject objectWithClassName:#"Lists"];
parseList[#"name"] = newList.listName;
parseList[#"uuid"] = newList.listUuid;
[parseList saveEventually];
PFQuery *query = [PFQuery queryWithClassName:#"Users"];
[query fromLocalDatastore];
[query whereKey:#"uuid" equalTo:[User instance].userUuid];
[[query findObjectsInBackground] continueWithBlock:^id(BFTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
return task;
}
PFObject *user = [task.result objectAtIndex:0];
// Create relation
PFRelation *relation = [user relationForKey:#"listAccess"];
[relation addObject:parseList];
[user saveEventually];
PFQuery *relationQuery = [relation query];
[[relationQuery findObjectsInBackground] continueWithBlock:^id(BFTask *task) {
NSLog(#"resutls: %#", task.result);
return task;
}];
[parseList pinInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// Create relation
PFRelation *relation = [parseList relationForKey:#"sharedWith"];
[relation addObject:user];
[parseList saveEventually];
}
}];
return task;
}];
So what I am thinking I am doing here is grabbing the user from the local data store and adding another relation to it. But in reality, this actually is only saving the parseList to user relation. User doesn't get changed in the datastore or the server.
Other things I've tried:
Querying just the server
Pinning the User object with a name
EDIT: includeKey (This doesn't work as the include isn't a PFObject.)
Thanks for the help in advance.
Edit:
Looking through the query and relation grabs I get some weird data information.
So, first off, the relation is empty when searching through it like so:
estimatedData
0 - name - Bill Nye
1 - listAcces - no summary
value - PFRelation
knownObjects - 0 objects
2 - uuid - long string that is uuid
So this matches the server stuff, all besides the relation. But, when I query the relation and log it, I get this:
resutls: (
" {\n name = Okkkkk;\n sharedWith = \" Users>\";\n uuid = \"D8174D80-4F08-4B25-9B72-F8E00CF19DDB\";\n}"
)
So querying it, it is there, but it doesn't exist to the relation?
I'm trying to add an object to a relation in Parse, although the code gets exectued without any errors the relation does not appear in the backend, therefore the object was not saved.
PFObject *newContact = [PFObject objectWithClassName:#"Contact"];
[newContact saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
PFQuery *query = [PFQuery queryWithClassName:#"Trip"];
PFObject *trip = [query getObjectWithId:self.parseID];
PFRelation *rel = [trip relationForKey:#"contacts"];
[rel addObject:newContact];
contact.parseID = newContact.objectId;
}];
I've also checked if the PFObject trip is correct and I get back the desired object with the corresponding id. Also the key contacts is double-checked and correct.
The problem is that you never save the relation. You create the PFRelation within the block, add an object to it, and do nothing else with it... You never save it.
Instead, try fetching the trip object and creating the PFRelation outside of the save block, ex:
PFQuery *query = [PFQuery queryWithClassName:#"Trip"];
PFObject *trip = [query getObjectWithId:self.parseID];
PFObject *newContact = [PFObject objectWithClassName:#"Contact"];
PFRelation *rel = [trip relationForKey:#"contacts"];
[rel addObject:newContact];
[newContact saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
contact.parseID = newContact.objectId;
}];
// Posts a message to the backend database
- (void)postMessage:(NSString *)message
{
// return right away if message is nil or all whitespace
if (!message || ![[message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]) {
return;
}
// Create new Message object and set relationships
PFObject *postedMessage = [PFObject objectWithClassName:#"Message"];
postedMessage[#"text"] = message;
PFRelation *tagRelation = [postedMessage relationForKey:#"tags"];
NSMutableArray *tags = [self generateTagsFromMessage:message];
NSLog(#"Number of Tags Found: %lu", (unsigned long)[tags count]);
[PFObject saveAllInBackground:tags block:^(BOOL succeeded, NSError *error) {
for (PFObject *tag in tags) {
// [tagRelation addObject:tag];
NSLog(#"%#", tag[#"text"]);
[tag setObject:postedMessage forKey:#"message"];
}
}];
[postedMessage saveInBackground]
}
I am using Parse as the back end for my application, but am having inexplicable difficulty creating a one-to-many relationship.
The idea is to make it so that users can post messages to the database with a tag on them, and the database stores Messages as one class, and Tags as another. The Message class has a "tags" field intended to store all tags in the message, and the Tag class has a "message" field intended to store the parent message of the tag. I thought I followed the tutorial of how to create a One-to-many relationship in Parse very well, but no matter what I cannot see the relationship on the Data Browser in Parse.
I have tried getting the relation to work by both simply setting the postedMessage as the newTag's message field, and also by creating a PFRelation * for the tags field of the message and adding tags to that. None of it works.
Does anybody know what my problem might be?
Thank you!
Edit
Specifically, the problem is that no relation is formed between the objects. By inspecting the data browser I can see that both the "tags" relational field of Message and "message" relational field of Tag remain blank after running this code.
An issue with your above code is that you're creating two new objects at once and triggering background saves on both at once.
For this to work, you need to save one side of the relationship first, then create the new objects, relate them and save them (in the block).
In your case you have a single message from multiple tags, so create the message and save it, then in the save block create the tags, add them to the message and re-save the message (it'll walk the tree looking for changes and save the new tags).
// Create new Message object
PFObject *postedMessage = [PFObject objectWithClassName:#"Message"];
postedMessage[#"text"] = message;
[postedMessage saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// message saved, now create tags with link to message
PFRelation *tagRelation = [postedMessage relationForKey:#"tags"];
NSMutableArray *tags = [self generateTagsFromMessage:message];
NSLog(#"Number of Tags Found: %lu", (unsigned long)[tags count]);
for (PFObject *tag in tags) {
NSLog(#"%#", tag[#"text"]);
[tag setObject:postedMessage forKey:#"message"];
}
// now save the tags
[PFObject saveAllInBackground:tags block:^(BOOL succeeded, NSError *error) {
// now add the relationships
for (PFObject *tag in tags) {
[tagRelation addObject:tag];
}
// and save
[postedMessage saveInBackground];
}];
}];
Your code is almost correct, but you missed a few details. See my working version of your code re-written below. But first, read through the examples from Parse.
Take a look at this example from the Parse iOS documentation where they are setting the pointer relationship between two objects; a One-to-One relationship.
Neither object must have an objectId before creating the relationship.
// Create the post
PFObject *myPost = [PFObject objectWithClassName:#"Post"];
myPost[#"title"] = #"I'm Hungry";
myPost[#"content"] = #"Where should we go for lunch?";
// Create the comment
PFObject *myComment = [PFObject objectWithClassName:#"Comment"];
myComment[#"content"] = #"Let's do Sushirrito.";
// Add a relation between the Post and Comment
myComment[#"parent"] = myPost;
// This will save both myPost and myComment
[myComment saveInBackground];
What you are looking for is either a One-to-Many relationship array of pointers, which is outlined here and copied below for easy reference.
One object must have an objectId before creating the relationship
// Create Post
PFObject *newPost = [PFObject objectWithClassName:#"Post"];
// Set text content
[newPost setObject:[textView text] forKey:#"textContent"];
// Create relationship
[newPost setObject:[PFUser currentUser] forKey:#"author"];
// Save the new post
[newPost saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
// Dismiss the NewPostViewController and show the BlogTableViewController
[self dismissModalViewControllerAnimated:YES];
}
}];
OR you are looking for a One-to-Many relationship using the PFRelation object because; "you don't need to download all the Objects in a relation at once. This allows PFRelation to scale to many more objects than the NSArray of PFObject approach" . If this approach is what you are looking for, your re-written code below will work.
One object must have on objectId before creating the relationship
// Create new Message object and set relationships
PFObject *postedMessage = [PFObject objectWithClassName:#"TestClass"];
postedMessage[#"text"] = #"Hello World";
//example tags into array
PFObject *tag1 = [PFObject objectWithClassName:#"Tag"];
PFObject *tag2 = [PFObject objectWithClassName:#"Tag"];
PFObject *tag3 = [PFObject objectWithClassName:#"Tag"];
PFObject *tag4 = [PFObject objectWithClassName:#"Tag"];
NSArray *tags = [NSArray arrayWithObjects:tag1, tag2, tag3, tag4, nil];
//iterate the tags, add pointer for each
for (PFObject *tag in tags) {
//create a new tag-to-message pointer
[tag setObject:postedMessage forKey:#"message"];
}
//save all the tags and save the postedMessage
[PFObject saveAllInBackground:tags block:^(BOOL succeeded, NSError *error) {
if (!error) {
NSLog(#"created new postedMessage w/o relations :%#", postedMessage);
NSLog(#"saved tags with pointers :%#", tags);
PFRelation *tagRelation = [postedMessage relationForKey:#"tags"];
//iterate the tags, add relation for each
for (PFObject *tag in tags) {
//create a new postedMessage-to-tag relationship
[tagRelation addObject:tag];
}
//update the postedMessage
[postedMessage saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
NSLog(#"updated postedMessage with relations :%#", postedMessage);
}
}];
}
}];
Edit: Adding the code I wrote; which works as you would expect. Once you get your relationships working there is one thing you will encounter using the Browser and that is an "object not found" error when you click on an object relationship from an object that is not directly related. A better way to explain that is to refer you to my example below.
I created a Message-to-Notification-to-User relationship -- think of it in any direction you prefer. When I access this relationship data using the Browser in CoreData and I click on the Users notifications relations column, I am directed to the users notifications. However; when I click on any of the message relations column, I received the, "object not found" error message. This is expected behavior and logically it makes sense. The User object doesn't care what messages its notifications contain, much like how the messages don't care who the user is. When the User object calls delete on a notification, auto-cleanup removes the messages without the User object needing to worry about them. When an object has no reference pointers and auto-cleanup is called, the object will be removed.
- (void)addNotificationWithData:(NSDictionary*)userInfo withCompletionHandler:(void(^)(BOOL isFinished))completionHandler {
PFObject *notification = [PFObject objectWithClassName:#"Notification"];
PFObject *msg = [PFObject objectWithClassName:#"Message"];
[msg saveEventually:^(BOOL succeeded, NSError *error) {
if (!error) {
PFRelation *notification_to_message = [notification relationForKey:#"messages"];
[notification_to_message addObject:msg];
[notification saveEventually:^(BOOL succeeded, NSError *error) {
if (!error) {
PFRelation *user_to_notification = [[PFUser currentUser]relationForKey:#"notifications"];
[user_to_notification addObject:notification];
[[PFUser currentUser]saveEventually:^(BOOL succeeded, NSError *error) {
if (!error) {
//add the notication to the notification(s)
[notifications addObject:notification];
NSLog(#"the current notifications are :%#", notifications);
//call the completion handler
if (completionHandler) {
completionHandler(YES);
}
}
}];
}
}];
}
}];
}
You've commented out the addObject call to add the object to the relation. You'll need to uncomment that line in order to actually add the object to the relation.
i'm trying to post the users location with his display name in the Table View. but all I'm getting in the data browser is the correct post but the display name is blank
I'm trying to query the location Class and also the User Class which has the display name for the user
// Configure the new event with information from the location.
CLLocationCoordinate2D coordinate = [location coordinate];
PFGeoPoint *geoPoint = [PFGeoPoint geoPointWithLatitude:coordinate.latitude longitude:coordinate.longitude];
PFObject *object = [PFObject objectWithClassName:#"Location"];
[object setObject:geoPoint forKey:#"location"];
PFObject *UserObject = [PFObject objectWithClassName:#"_User"];
[[UserObject objectForKey:#"postedUser"] objectForKey:#"displayName"];
// Create relationship
[object setObject:[PFUser currentUser] forKey:#"postedUser"];
[UserObject setObject:[PFUser currentUser] forKey:#"displayName"];
PFACL *locatinACL = [PFACL ACLWithUser:[PFUser currentUser]];
[locatinACL setPublicReadAccess:YES];
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
}
}];
anyone know what I'm doing wrong. the display name is in the user class under displayName and i need it in the new post under displayName
the user save details saved under a form where the set has to fill in extra details after sign up
NSString *saveName = UserNameText.text;
NSString *saveEmail = EmailAddressText.text;
NSString *saveMobile = MobileText.text;
NSString *saveAdd1 = Address1Text.text;
NSString *saveAdd2 = Address2Text.text;
NSString *savePostCode = PostCodeText.text;
NSString *saveCustTax = TaxiOrCust.text;
currentUserSave[#"displayName"] = saveName;
currentUserSave[#"email"] = saveEmail;
currentUserSave[#"mobile"] = saveMobile;
currentUserSave[#"address1"] = saveAdd1;
currentUserSave[#"address2"] = saveAdd2;
currentUserSave[#"postCode"] = savePostCode;
currentUserSave[#"customer"] = saveCustTax;
Forgive the long answer... I think I'm still slightly unclear about your data structure and what you're trying to achieve, so it seems to make the most sense if I clarify what your existing code is actually doing. Hopefully this will help you make any necessary adjustments.
What you're currently doing
PFObject *object = [PFObject objectWithClassName:#"Location"];
[object setObject:geoPoint forKey:#"location"];
Here you're creating a new Location object, and setting the geopoint you've created as its location. If you have an existing Location object you want to update, you'll have to fetch that from Parse (unless you already have a variable / property for it).
PFObject *UserObject = [PFObject objectWithClassName:#"_User"];
Here you're creating a new User object. I suspect you want to use the current user, rather than create a new one, however. There's also a specific Parse object for users: PFUser - which you are using later on.
[[UserObject objectForKey:#"postedUser"] objectForKey:#"displayName"];
This line does nothing. You're fetching UserObject's postedUser property, and then fetching that user's displayName property. I don't think this is what you were intending.
[object setObject:[PFUser currentUser] forKey:#"postedUser"];
[UserObject setObject:[PFUser currentUser] forKey:#"displayName"];
This then updates the two new objects you created (your location and user objects), by setting two of their attributes to point to the current user.
PFACL *locatinACL = [PFACL ACLWithUser:[PFUser currentUser]];
[locatinACL setPublicReadAccess:YES];
These two lines create a new ACL which gives the current user write access, and everybody else read access. However, you're not applying this to your new location object.
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
}
}];
Finally, this will save your new Location object. However, the new User object you created will not be saved, as nothing else has a reference to it, and you're not calling save on it.
What you might be trying to do
I think the code you're writing can probably be cut down to something along these lines:
// Create a new location object
PFObject *parseLocation = [PFObject objectWithClassName:#"Location"];
// Create a new PFGeopoint, and store it as the location's location.
CLLocationCoordinate2D coordinate = [location coordinate];
PFGeoPoint *geoPoint = [PFGeoPoint geoPointWithLatitude:coordinate.latitude longitude:coordinate.longitude];
[parseLocation setObject:geoPoint forKey:#"location"];
// Store the current user and their display name as part of the location object
[parseLocation setObject:[PFUser currentUser] forKey:#"postedUser"];
[parseLocation setObject:[[PFUser currentUser] objectForKey:#"displayName"] forKey:#"displayName"];
// Create ACL and save it to the location object
PFACL *locationACL = [PFACL ACLWithUser:[PFUser currentUser]];
[locationACL setPublicReadAccess:YES];
[parseLocation setACL:locationACL];
// Save the location object
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
}
}];
Is this closer to what you wanted?