I would like write a PFUser object by the currentUser, i've added the ACL based on the Parse developer guide, but i still get an error:
'User cannot be saved unless they have been authenticated via logIn or signUp'
_ My code:
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:self.bBo];
PFObject *friendData = [query getFirstObject];
PFUser *user = (PFUser *)friendData;
PFACL *userACL = [PFACL ACL];
user.ACL = userACL;
[userACL setWriteAccess:YES forUser:[PFUser currentUser]];
PFRelation *friendRelation = [user relationforKey:#"array"];
[friendRelation addObject:[PFUser currentUser]];
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error){
NSLog(#"Error %# %#", error, [error userInfo]);
}
}];
I think i did everything correct, so i can't figure out what can be the problem. So if you made earlier something like this or know where is the problem, i would really appreciate any suggestions.
For security reasons, Parse won't allow you to save any changes to a user that is not currently logged in.
If you want to be able to make and save changes to user, you need to use Cloud Code and the Master Key to get around this roadblock.
I have had multiple problems like this before, and every time I've been forced to use a workaround via Cloud Code.
Here's an example of a workaround I did for creating a friends relationship between two users:
[PFCloud callFunction:#"editUser" withParameters:#{#"userId": user.objectId}];
The above code statement is in xcode, and executes the function I have added to my Cloud Code file.
Then, here's what my Cloud Code file looks like:
Parse.Cloud.define('editUser', function(request, response) {
var userId = request.params.userId;
var User = Parse.Object.extend('_User'),
user = new User({ objectId: userId });
var currentUser = request.user;
var relation = user.relation("friendsRelation");
relation.add(currentUser);
Parse.Cloud.useMasterKey();
user.save().then(function(user) {
response.success(user);
}, function(error) {
response.error(error)
});
});
The above code uses the Master Key to save changes to both the currently logged in user, and to the user who's objectId was passed into this function.
Code details:
var relation
This is just a variable I'm creating to hold what the following fucntion returns:
user.relation("friendsRelation");
In the above function, "friendsRelation" is the name of my PFRelation key in Parse.
Now that we have a valid relation object contain in our variable called relation, we execute this function with an argument of our currentUser object.
After that, all that's left is saving everything. I don't program with javascript, but I was still able to come up with the above solution by looking at the Parse Cloud Code docs, and searching around on their support forums.
If you take my template from above, and make some small changes, then you should be able to easily accomplish what you need. You just have to jump through these extra hoops because the Parse SDK doesn't want a situation where someone can login and somehow make changes to another user's account, whether by their own fault or a developer's mistake.
EDIT:
Here is the code to add the relationship for the current user:
PFRelation *friendsRelation = [[PFUser currentUser]relationforKey:#"friendsRelation"];
PFUser *user = [self.parseUsers objectAtIndex:indexPath.row];
[friendsRelation addObject:user];
[currentUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error) {
NSLog(#"%# %#", error, [error userInfo]);
}
}];
And then you can call the Cloud Code method right after:
[PFCloud callFunction:#"editUser" withParameters:#{
#"userId": user.objectId
}];
Related
I am facing an issue when trying to update data in Parse.
Here is my code that i am using.
PFQuery *query = [PFQuery queryWithClassName:#"GameScore"];
[query whereKey:#"name" equalTo:#"Username"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *item, NSError *error)
{
if (!error)
{
[item setObject:#500 forKey:"Score"];
}
[item saveInBackgroundWithBlock:^(BOOL succeeded, NSError *itemError)
{
if (!error)
{
NSLog(#"Score is updated");
}
else
{
NSLog(#"Error in updating: %#",error.description);
}
}];
}
else
{
NSLog(#"Error in getting Score: %#",error.description);
}
}];
This code works only when i create a new PFObject and then try to update it.
But,when i exit my app and then try to update the score again,i am unable to update the data.It throws this error..
Error in getting Score: Error Domain=Parse Code=101 "No results matched the query." UserInfo={error=No results matched the query., NSLocalizedDescription=No results matched the query., code=101}
It works again,if i create a new PFObject.
Please help,i am new to Parse and am still try to understand it.
Thank you.
You need to use findObjectsInBackgroundWithBlock instead of getFirstObjectInBackgroundWithBlock as the latter can only be used if there's at least 1 object.
Reference - Parse Questions
You could also use the PFQuery's count method. If the count is >= 1, use getFirstObjectInBackgroundWithBlock, otherwise display a message / handle that case however you'd like.
Other options include storing the objectId of the GameScore object associated with a player on their user object, creating an object without data using the objectId, then fetching it. Or simply use a pointer, but that can do weird things when saving / querying / fetching.
I am using Parse as my backend for my app. Once the user logs into their account I am trying to get the next view to say "Welcome, (First Name)" at the top. However, I cannot seem to figure out how to accomplish this even using Parse's online documents. Their site directed me here for further assistance. I have tried using their query feature, but could not figure it out. In other words, I am trying to pull the current logged in user's first name, from the database and display it once logged in.
Current code:
PFQuery *query = [PFUser query];
[query whereKey:#"firstName" equalTo:currentUser]; // find user's first name
NSArray *firstName = [query findObjects];
Previous code:
PFUser *currentUser = [PFUser currentUser];
if (currentUser) {
// do stuff with the user
Welcome.text = [NSString stringWithFormat:#"Welcome,", currentUser];
Your overcomplicating things.
You don't have to execute a query every time the view loads, instead you should put this in a plist or NSUserDefaults as not to use an API request simply to display the current users name.
However, you can do the following to the current users username :
if ([PFUser currentUser]) {
Welcome.text = [NSString stringWithFormat:#"Welcome, %#", [PFUser currentUser].username];
}
First of all you should check if you actually sign-up and/or logged into Parse with this kind of function:
[PFUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
}];
[PFUser logInWithUsernameInBackground:#"My username" password:#"My password" block:^(PFUser *validUser, NSError *error) {
}];
Check this link: https://parse.com/docs/ios_guide#users-signup/iOS
After you did this, whenever you you want to retrieve your user information,
[PFUser currentUser] is the right way to call some information:
Say for example you want to retrieve the objectId you can get it like this:
NSString *str = [PFUser currentUser].objectId;
Or say you want to set a custom value like this:
NSString *str = #"My custom object";
[PFUser setObject:str forKey:#"MyCustomObject"];
[PFUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
}];
Than you retrieve it like this:
NSString *str = [PFUser objectForKey:#"MyCustomObject"];
You should really check the documentation and example by Parse that are really well explained ! ;)
(Here are some tutorials/Examples by Parse: https://parse.com/tutorials)
How is the transition from your login to your main view set up? Are they both two different controllers?
If so, you should look into NSNotificationCenter...
In your MainViewController, implement
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loadObjects)
name:#"loginFinished"
object:nil];
And loadObjects will be
- (void)loadObjects
{
Welcome.text = [NSString stringWithFormat:#"Welcome %#", [[PFUser currentUser] objectForKey:"userNameField"]];
}
* You need to parse the PFUser object to access its fields. Its just a dictionary so supply it a key 'username' or whatever, and you receive a value 'myusername'. *
Then in your LoginViewController, within your [PFUser logInWithUsernameInBackground:password:block
Implement this
[PFUser logInWithUsernameInBackground:#"My username" password:#"My password" block:^(PFUser *validUser, NSError *error) {
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"loginFinished" object:nil];
}
}];
But this is basically how you should setup your login->main flow. Learn NSNotifications, Delegation, and maybe KVO... Learning these will make you a understand how data can be passed around in the iOS/Mac environment.
Heres an analogy for all of them:
NSNotification: A teacher(NSNotification poster) announces a test to all his students(NSNotification observer), or at least the one's who are currently in class, students missing class aren't observing.
Delegate: A student finishes a test and informs the professor(delegate).
KVO: A student(KVO poster) completes a question and raises their hand where the teacher or even other students could be KVOs (key-value observers) and act on their action.
I am trying to update a chat object I have saved with Parse, and although it works sometimes, it's not consistent. If I clear the object out of data on the browser side, it will work a few times, but then I get the error:
Error: object not found for update (Code: 101, Version: 1.3.0)
Here is the code I am using, although I've tried many ways. This code is nearly identical to the Parse documentation.
PFObject *currentChatroom = _currentChatroom;
NSString *objID = currentChatroom.objectId;
PFQuery *query = [PFQuery queryWithClassName:#"Chats"];
// Retrieve the object by id
[query getObjectInBackgroundWithId:objID block:^(PFObject *fetchedChat, NSError *error) {
// Now let's update it with some new data. In this case, only cheatMode and score
// will get sent to the cloud. playerName hasn't changed.
fetchedChat[#"lastTextSent"] = lastTextWithUser;
fetchedChat[#"lastTextSentDate"] = date;
[fetchedChat saveInBackground];
}];
For good measure, Here is the Parse recommendation:
PFQuery *query = [PFQuery queryWithClassName:#"GameScore"];
// Retrieve the object by id
[query getObjectInBackgroundWithId:#"xWMyZ4YEGZ" block:^(PFObject *gameScore, NSError *error) {
// Now let's update it with some new data. In this case, only cheatMode and score
// will get sent to the cloud. playerName hasn't changed.
gameScore[#"cheatMode"] = #YES;
gameScore[#"score"] = #1338;
[gameScore saveInBackground];
}];
The Code works sometimes, so I know that's not the issue. I'm just not sure what is.
The code I used to fix this problem was allowing each user of the object (chatroom in this case) to have ACL Permissions to edit (writeAccess) the PFObject when it was first created. In order to do this, I used the code:
PFObject *newChatroom = [PFObject objectWithClassName:#"Chats"];
// Create ACL to allow both users to edit/update the chatroom
PFACL *multipleUserRights = [PFACL ACL];
// _currentFriend is one user in the chatroom
[multipleUserRights setReadAccess:YES forUser:_currentFriend];
[multipleUserRights setWriteAccess:YES forUser:_currentFriend];
// Give the current user permission as well
[multipleUserRights setReadAccess:YES forUser:[PFUser currentUser]];
[multipleUserRights setWriteAccess:YES forUser:[PFUser currentUser]];
newChatroom.ACL = multipleUserRights;
I found similar questions to this, and some had similar solutions, but not with the error 1.3.0, so I wouldn't consider it a duplicate.
I'm trying to delete the image connected to the current user from the imageOne column in Parse.com. From the user class.
PFQuery *query = [PFUser query];
[query selectKeys:#[#"imageOne"]];
[query getObjectInBackgroundWithId:[[PFUser currentUser] objectId] block:^(PFObject *object, NSError *error) {
if (!error) {
[object deleteInBackground];
}
}];
My code doesn't work and the console logs this error "User cannot be deleted unless they have been authenticated via logIn or signUp".
How can I fix this?
Seems like the problem comes from the fact that object (image) comes from the user class, am I right?
Why are you doing a query for all users and then doing the delete for just the current user, that's the worst possible way to structure the query (and most likely to fail).
If the current user isn't in the first 100 returned your above code would never find a match.
This sort of query should instead be done using getObjectInBackgroundWithId:block:, but in the case of the current user you already have the object, just do this:
[[PFUser currentUser] deleteInBackground];
If instead you just want to delete information in a column, use the following:
PFUser *currentUser = [PFUser currentUser];
[currentUser removeObjectForKey:#"imageOne"];
[currentUser saveInBackground];
I have several Classes one of which one is User and another one is TestObject. If I query User (which I learned by trial & error that it should queried as _User) I get the correct record count, but if I query TestObject I get 0. This happens for some Classes but not for all. Why is that?
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"Error: %# %#", error, [error userInfo]);
} else {
NSLog(#"Successfully retrieved %d scores.",objects.count);
}
}];
This returns:
2012-10-19 13:55:03.239
TableViewParseDotCom[5497:10103]
Successfully retrieved 24 scores.
But if I change the line, to:
PFQuery *query = [PFQuery
queryWithClassName:#"TestObject"];
I get 0 count, but I know I have 45 records. Why?
The most common reason for this is you've queried for objects which you don't have access to. Double check that if these objects have an ACL you are logged in as the same user.
Along with checking the ACL's for the objects you're querying, you should also take a look at the Settings in the Parse dashboard. If you don't want to force users to log in ensure that "Allow Anonymous Users" is set on On.
Also, in the data browser for the TestObject object, click on the "More" button and then select "Permissions" from the dropdown. Ensure your settings are correct for "Find" and "Get" - set to public with no roles/users to start with to help your debugging. That should ensure that you can query your TestObject objects.
Don't forget to set the read permissions. These can be programatically set as below:
PFACL * defaultACL = [PFACL ACL];
[defaultACL setPublicReadAccess:YES];
[PFACL setDefaultACL:defaultACL withAccessForCurrentUser:YES];
I just ran two queries, one on my users and one on another class (imageClass) and they both returned fine.
User query:
// Remember for users we can run a user query instead of needing to specify the class
PFQuery * userQuery = [PFUser query];
[userQuery whereKey:#"username" equalTo:currentUser.username];
[userQuery findObjectsInBackgroundWithBlock:^(NSArray * objects, NSError *error) {
}];
Other class query:
PFQuery * imageQuery = [PFQuery queryWithClassName:#"bImageClass"];
[imageQuery findObjectsInBackgroundWithBlock:^(NSArray * objects, NSError *error) {
}];
Here is a picture of my classes in Parse.
So I would make sure you have the public access set correctly (in my project this is set just before a query runs) as the code you are using for the queries looks fine.