Parse Cloud Code - How to add object from another class to user - ios

My application displays data to my users via the Parse.com. I am using their iOS sdk with their cloud code. I am required to use cloud code to add an object to my user. What I'm looking for is something like:
[PFCloud callFuction:#"addObjectToUser" withParameters:params];
And my parameters would be the userID and the the object ID of the object I want associated with that user ID.
How would I achieve this in JavaScript?

In order to change anything on a user object in parse (without being changed by the user themselves) it must be done through cloud code and by using the useMasterKey(). Assuming that you have set up your params like this:
NSDictionary *params = #{userID: #"12345", objectID: #"67890"};
You can save objects to a user with the master key like this:
//Add Object To User's "objectsAddedToUser" array
Parse.Cloud.define("addObjectToUser", function(request, response) {
// (1)
var userID = request.params.userID;
var objectID = request.params.objectID;
// (2)
var query = new Parse.Query(Parse.User);
query.equalTo("userID", userID);
query.first().then(function (user) {
// (3)
Parse.Cloud.useMasterKey();
// (4)
user.add("objectsAddedToUser", objectID);
// (5)
user.save().then(function(user) {
response.success(user);
}, function(error) {
response.error(error)
});
}, function (error) {
response.error(error);
});
});
(1) Get the parameters that you passed in.
(2) Query the database and retrieve the user that needs to be updated.
(3) IMPORTANT Use the master key to unlock privileges.
(4) Add the objectID to the user.
(5) Save to Parse.

Adding cloud function is explain in detail here, there is an example as well which explain how you can write java-script function to manipulate or add data.

Related

Saving to coredata from different APIs when you have relationships

I have two different APIs to get Accounts and Contacts, where in the Core-Data model I have made an inverse relationship with the assumption of one Account could have many Contacts. My question is when I'm in the contacts screen (when I call the GET Contacts API using the Account Id), whats the best way to save those contacts in to that specific account. Entity relationship as follow.
my partially completed code as bellow, but i guess its wrong as im getting zero contacts when I fetch them back.
func saveContactfor(accountID:String,data:[Array],isAddedFromDevice:Bool) {
let request:NSFetchRequest<Account> = Account.fetchRequest()
do {
let searchResults = try managedObjectContext.fetch(request)
for account in searchResults {
if account.account_Id == accountID {
print("ID found in already saved Accounts")
for contact in data {
account.contacts?.adding(contact.dictionary) //contact.dictionary-> i have written a utils method to convert an object array to [[String:String]] which is an array of dictionaries
}
try managedObjectContext.save()
}
}
print("Contacts Updated")
} catch {
print("Error in saving contacts from api to coredata")
}
}
Since you are using relations you should be pretty near to what you need to do. All items need to be updated; account and contacts so you need to add relation one way or another.
The other one would be to use contact.account = account which looks more reasonable.
In the code you provided I am confused about a few things:
How you append your contacts. Should this not be account.contacts = account.contacts?.adding(contact.dictionary)?
Why do you fetch all contacts? Use predicates to extract only those with given ID.
Should possibly old accounts be deleted?
I would expect your (pseudo) code to look something like this:
func saveContacts(_ contacts: [ContactDetailModel], forAccount accountID: String) throws {
// Fetch or create an account
let account: Account = try Account.findWithID(accountID) ?? Account.createNewInstance(accountID: accountID)
// Assign account
contacts.forEach { $0.account = account }
// Save database
try saveContext()
}
I think you should be able to imagine what the 3 methods do for you. Remember you can mark your method to throw by using throws which can be huge convenience in such cases.
Now to also clear old accounts a bit more work may be needed:
func saveContacts(_ contacts: [InputContactData], forAccount accountID: String) throws {
// Fetch or create an account
let account: Account = try Account.findWithID(accountID) ?? try Account.createNewInstance(accountID: accountID)
// Match accounts
// Get all current contacts
var depletingContacts: [ContactDetailModel] = (account.contacts.allObjects as? [ContactDetailModel]) ?? [ContactDetailModel]()
contacts.forEach { newContact in
if let index = depletingContacts.firstIndex { $0.contactID == newContact.id } {
// The contact with this ID already exists. Remove it from array and assign new values
let contact: ContactDetailModel = try depletingContacts.remove(at: index)
contact.fillWithInputData(newContact)
} else {
// This contact is not yet in database. A new item needs to be created
let contact: ContactDetailModel = try ContactDetailModel.newInstance()
contact.fillWithInputData(newContact)
contact.account = account
}
}
// Clean up all old contacts.
// The contact that are still left in depletingContacts were not part of input contacts and need to be deleted
depletingContacts.forEach { try $0.removeFromDatabase() }
// Save database
try saveContext()
}
This is now a step further. I added InputContactData which is the data you receive from whatever service you are using and is not yet in core data.
Then an account is found or created. When iterating through each of new contacts the code tries to first match an existing one with a new item. Either an existing item is updated or a new one is created. Since an existing item is removed (depleted) from array of existing contacts the depleting array is left with contacts that have been deleted. So all of those may be removed from database.
This is a procedure where relation is one-to-many. Many-to-many would have a similar approach but a bit different.
I hope the rest of the code speaks for itself.

Swift & Firebase - Delete hashtags from post

I'm trying to code the function to delete a post in my app. When I delete a post, I would like to delete also all the hashtags related to that post.
I'm using Firebase, not for a long time and I don't really know how to do that :/
This is my DataBase:
I have every "hashtags" and in the child, the posts associated.
When I delete a post, I would like to delete the child associated from hashtags.
This is my unfinished code:
func deleteHashtagsFromPost(withPost id: String) {
Database.database().reference().child("hashtag").observe(.value) { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
for hashtag in dict {
// Here I can access to the hashtag with hashtag.key
// And also to the children associated with hashtag.value
}
}
}
}
This is an insight of "Posts" in my DataBase:
In noSQL database like Firebase is based on, the best practice is to duplicate your objects.
Your tags initially belong to the posts associated, so put them in here. It will be easy to display them in your app as they are in the object you retrieve. You probably also want to search the posts based on tags, so you create a tags branch like you did, that way you can get all posts related without having to search in each posts for the tag.
This may seems redundant, but this is how it works in noSQL, you have to handle multiple places for you datas if you want better results.
You might know that but if your data model has a lot of relationships, it could be better to go for an SQL database. (your model as it is doesn't have much, so it's ok)
Vive la baguette !
The problem here is that inside your post you aren't storing the hashtags. So in order to delete them you'd have to loop over the whole hashtag branch which you don't want to do on the client side. I'd store the hashtags in the post object. Then do this
let hashtags = [String]() // put all the posts hashtags in here
let postKey = post.key // key for the post you just deleted
for i in 0..<hashtags.count {
deleteHashtagsFromPost(postKey: postKey, hashtagUsedInPost: hashtags[i])
}
func deleteHashtagsFromPost(postKey: String, hashtagUsedInPost: String) {
Database.database().reference.child("hashtag").child(hashtagUsedInPost).child(postKey).removeValue()
// you can add a completion block if you wish
}
If you're interested in using a Firebase cloud function it could look something like this:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// so we can write to the database in functions outside of the database triggers
var db = admin.database();
exports.deleteHastags = functions.database.ref('/posts/{postid}').onDelete(event => {
console.log("Post was deleted");
const key = event.data.previous.key;
const post = event.data.previous.val();
const hashtags = event.data.previous.val().hashtags; // requires that the hashtags are stored with the post in FB DB
removeHashtags(hashtags, key);
return;
});
function removeHashtags(hashtags, key) {
for (var hashtag in hashtags) {
db.ref("hashtag").child(hashtag).child(key).remove();
}
}
Firebase Database Triggers

Prevent duplicate objects but allow Update with Cloud-Code and Parse.com as backend

I've an issue with Cloud Code.My problem is explained as:
I've a class "MyClass"
I want only one object saved in my backend referring to a particular property ("myID")
If a user try to save this object and there is not any with the same "myID" save it
If a user try to save this object and THERE IS ALREADY ONE with the same "myID" update the field ("address") for the existing object.
I try to this using this code:
var MyObject = Parse.Object.extend("MyClass");
Parse.Cloud.beforeSave("MyClass", function(request, response) {
if (!request.object.get("myID")) {
response.error('A MyObject must have a unique myID.');
} else {
var query = new Parse.Query(MyClass);
query.equalTo("myID", request.object.get("myID"));
query.first({
success: function(object) {
if (object) {
object.set('address',request.object.get("address"));
object.save().then(function () {
response.error('Updated existing MyObject address');
},function (error) {
response.error("Error: " + error.code + " " + error.message);
});
} else {
response.success();
}
},
error: function(error) {
response.error("Could not validate uniqueness for this MyObject object.");
}
});
}
});
But this doesn't works and in my Parse.com Log says:
Result: Error: 142 Error: 124 Too many recursive calls into Cloud Code
I know can I achieve what I want?
You have a beforeSave method that is triggered when an object of MyClass should be stored. If this method detects that the object searched for already exists, it modifies this object and tries to store it. But this triggers again the beforeSave method, and so on, an infinite loop.
You could do it on the client side: Check, if the object exists. If not, create it, but if so, add an entry to it. But you should be aware that you will have a problem: If more than 1 client executes this code at the same time, they could all find out that the object does not exist yet, and more than 1 object will be created.
I would thus create such an object right from the beginning, and use the atomic operation addUniqueObject:forKey: to add entries to an array.
You don't need to save the object in beforeSave - any changes you've made to request.object will be saved when you call response.success()

Using before or after save in parse cloud code to add a userId to another table

I am making a running app and would like to add the username and userId to another Class along with the User Class as soon as they register. I am having difficulty with the userId not saving to the Runner Class, the username saves just fine. Is this because I am using the code in the beforeSave function? How would I go about fixing this?
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
// Check if the User is newly created
if (request.object.isNew()) {
// Set default values
var RunnerClass = Parse.Object.extend("Runner");
var runner = new RunnerClass;
runner.set("username", request.object.get("username"));
runner.set("userId", request.object.id);
runner.save(null,{
success:function(runner) {
response.success(runner);
},
error:function(error) {
response.error(error);
}
});
}
response.success();
});
If relevant this is for iOS.
I attempted to user the afterSave but nothing gets added into the Runner class only the User class
I used the following code :
Parse.Cloud.afterSave(Parse.User, function(request, response) {
// Check if the User is newly created
if (!request.object.existed()) {
// Set default values
var RunnerClass = Parse.Object.extend("Runner");
var runner = new RunnerClass;
runner.set("username", request.object.get("username"));
runner.set("userId", request.object.id);
runner.save()
}
});
In the beforeSave handler a new object will not have an id yet, so the userId of the RunnerClass will not have a value.
You can use the afterSave handler and create the RunnerClass there. In an afterSave handler, you can use request.object.existed() instead of request.object.isNew(). Immediately after the first time an object is saved, existed() will return false to indicate that the last server operation created the object. If the object is subsequently saved again, the object will have already existed on the server, so existed() will return true from then on.
UPDATE
I have tested your afterSave function on my own Parse Service and it works well. It creates a Runner class with correct userId and username. This is the exact code that I used
Parse.Cloud.afterSave("_User", function(request, response) {
if (!request.object.existed()) {
// Set default values
console.log("User did not yet exist")
var RunnerClass = Parse.Object.extend("Runner");
var runner = new RunnerClass;
runner.set("username", request.object.get("username"));
runner.set("userId", request.object.id);
runner.save()
} else {
console.log("User exists")
}
})

Duplicate Parse.com User objectId to another column

I am trying to write some Cloud Code for Parse that upon creation of a new _User, it will copy that user's objectId, append it, and then put the resulting value in a new column called SubscriptionObject. The code I have tried is:
Parse.Cloud.afterSave(Parse.User, function(request, response) {
Parse.Cloud.useMasterKey(MYMASTERKEY);
var theObject = Parse.User.current().id
var newObject = 'User_' + theObject;
request.object.set("SubscriptionObject", newObject);
response.success();
});
However, the SubscriptionObject key never gets set.
What does console.log('User_' + theObject); log? It may not be a string. I think it may something like "Object object"
Try using concat.
var userString = 'User_';
var newObject = userString.concat(request.user.id);
I used request.user.id because I'm not familiar with Parse.User.current().id
Unrelated, but I've never had to pass my master key into the call. I just call Parse.Cloud.useMasterKey(); Perhaps we have some differences where it is necessary. Just looking out so you don't have to publish your master key as a global variable if it's not necessary.

Resources