How to prevent Parse from saving PFObject children's?
Asked Answered
G

3

7

I'm facing a very common issue with Parse and iOS.

I have a class POST with the following structure:

  • text(String)
  • Image (PFFile)
  • LikesUsers (Array of String)
  • LikesCount (Int)
  • From (Pointer to User who posted it)

and if the user (already logged in) likes a post. I'm just incrementing the likes and the add Objectid of the user to the array

For example : User-2 likes User-1's Post.

PostObject.incrementKey("Likes")
PostObject.addObject((PFUser.currentUser()?.objectId)!, forKey: "LikesUsers")
PostObject.saveEventually()

The issue is here. I can't save a the PostObject as long as it has a pointer to another user (than the logged in) I'm getting the error :

User cannot be saved unless they have been authenticated via logIn or signUp

So how to prevent from saving the ParseObject Children ("From")

I don't want to use a CloudCode, I want to keep it simple and to Use SaveEventually for a better user experience.

Gordan answered 10/3, 2016 at 15:43 Comment(4)
If I'm not wrong it's some kind of permission problem you're facing. Since you mentioned iOS I assume you're doing the update client side. As the post does not belong to the current user, he would not be able to save it. What you can do is create a cloudcode function and call that method passing along the current user.Natterjack
I can't do a Save Eventually from Parse CloudGordan
Can you elaborate more on why you can't? What's the error?Natterjack
What ACLs have you set on the _User class and its objects? If have have restricted read on a class level then it could mean that the logged-in user is not able to see other users in the _User class and thus tries to create another user.Probity
H
2

From the parse.com forums:

When you save an object with a pointer, Parse looks at the entirety of the object and saves the fields that are "dirty" (changed since last save). It also inspects all the objects being pointed to by pointers. If your object has a pointer to PFUser and the instance of the PFUser is "dirty", then Parse will save the object and then attempt to save the instance of the PFUser as well. My hypothesis is that somewhere in your code you are setting a value on a user and then not saving the user. This, I suspect, is the source of your error. Go through your code and look for anywhere you set a PFUser instance and make sure there's a corresponding save. If you can't find it at first glance, be sure to check all blocks and ensure that you are not asynchronously dirtying a user and subsequently trying to save it.

Are you trying to make any changes to the user that the post was made by?

Hibben answered 15/3, 2016 at 23:54 Comment(1)
I went through my code, and I'm not trying to change the PFUser or trying to make any changesGordan
C
2

Short answer:

Try to set from field as:

let author = PostObject["from"] as! PFObject
PostObject["from"] = PFObject(withoutDataWithClassName: "_User", 
    objectId: author.objectId)

This is not tested.

But I suggest you to change the architecture

I was facing the same problem and finally decided to move likes field to User class instead and make it Relation<Post> type. Parse documentation says that Relation type is better for keeping many objects. Since LikesUsers is an array of objects, the performance may drop significantly if a post will get many likes: the app will download all the users liked this post. See this for more info: https://parse.com/docs/ios/guide#objects-relational-data

Here is how my class structure looks like (simplified):

User:

  • postLikes (Relation)

Post:

  • author (PFObject)
  • likesCount (Int)

I also moved like/dislike logic to CloudCode function:

/// The user liked or disliked the post.
Parse.Cloud.define("likeDislikePost", function(request, response) {
    var userProfileId = request.params.userProfileId
    var postId = request.params.postId
    // Shows whether the user liked or disliked the post. Bool value
    var like = request.params.like
    var query = new Parse.Query("_User");
    query.equalTo("objectId", userProfileId);
    query.first({
      success: function(userProfile) {
            if (userProfile) {
              var postQuery = new Parse.Query("Post");
              postQuery.equalTo("objectId", postId);
              postQuery.first({
                  success: function(post) {
                        if (post) {
                            var relation = userProfile.relation("postLikes");
                            if (like) {
                              relation.add(post);
                              post.increment("likesCount");
                            }
                            else {
                              relation.remove(post)
                              post.increment("likesCount", -1);
                            }
                            post.save();
                            userProfile.save();

                            console.log("The user with user profile id " + userProfileId + " " + (like ? "liked" : "disliked") + " the post with id " + postId);
                            response.success();
                        }
                        else {
                            response.error("Unable to find a post with such ID");
                        }         
                  },
                  error: function() {
                    response.error("Unable to like the post");
                  }
              });
            }
            else {
                response.error("Unable to find a user profile with such ID");
            }         
      },
      error: function() {
        response.error("Unable to like the post");
      }
  });
});

Works just fine.

Caines answered 20/3, 2016 at 7:28 Comment(1)
Thank you for the cloud function provided, But I would like to use saveEventually with an offline optionGordan
S
0

I created some project from scratch for you. I think you miss some thing when you are creating your PFClass. I don't know. Whatever look my codes,

This is the ViewController and it contains like Button Action;

    import UIKit

    class ViewController: UIViewController {

    private let test = Test() // PFObject SubClass
    private let post = Post() // PFObject SubClass

    @IBAction func likeButton() {

        self.test.fromUser = PFUser.currentUser() // Current User
        self.test.likedUsers.append(Post().user  // In there your post should be a Subclass of PFObject too like your this Test class and it should be contains Owner property. So you can grab the Post owner PFUser or User Object ID.)
        self.test.saveEventually() {
            (let success, error) in
            if error == nil {
                // Success
            } else {
                print(error?.localizedDescription)
            }
        }
    }
}

In your AppDelegate init your ParseSDK and Register your new Parse subclasses.

   func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    Parse.setApplicationId("YOUR_API_KEY", clientKey: "YOUR_CLIENT_KEY")
    Test.registerSubclass()
    Post.registerSubclass()

    return true
}

Test Subclass;

import Foundation
import Parse

class Test: PFObject, PFSubclassing {

    // Your Properties
    @NSManaged var likedUsers: [PFUser]
    @NSManaged var fromUser: PFUser

    static func parseClassName() -> String {
        return "Test" // Your Class Name
    }
}

Post Subclass;

    class Post: PFObject, PFSubclassing {

    // Your Properties
    @NSManaged var owner: PFUser
    @NSManaged var likeCount: NSNumber

    static func parseClassName() -> String {
        return "Post" // Your Class Name
    }
   }

In my case it works fine. If you get an error please inform me.

Have a nice coding.

Sherlynsherm answered 2/4, 2016 at 20:39 Comment(1)
No this doesn't solve the problem ! I think you misunderstand the questionGordan

© 2022 - 2024 — McMap. All rights reserved.