I want to add an Object to an existing Array in firestore.
I already have one Object in the Array, now I want to add the second Object.
So I have created this code below to store data to firestore.
After I triggered the function the data in firestore won't add a new Object to the array
Code:
let docId = `${this.currentUser.uid}`
fb.usersCollection.doc(docId).update({
userId: this.currentUser.uid,
posts: [
{
createdOn: this.postDetails.createdOn,
content: this.postDetails.content,
image: this.postDetails.image,
comments: this.postDetails.comments,
likes: this.postDetails.likes,
tags: this.model,
userData: [
{ userName: this.userProfile.name,
userId: this.currentUser.uid,
userImage: this.userProfile.userImage
}
]
}
]
})
Update doesn't automatically add a new object to array, the way you are doing it, you are simply overwriting the posts Array with a new object.
To add new object to existing array, you either read old array and then manipulate it your self, then write it back in using the transaction method or use the new array method as mentioned by #Joseph
Related
I am building an education application and I am trying to add/update a field which is an array of objects with addToSet from a javascript array, and if the object already exists (matched with objectId) I want to update the already existing object's array (addToSet) and change another field of that same object.
My model looks like this (simplified):
const course = new Schema(
{
events: [
{
type: Schema.Types.ObjectId,
ref: 'event'
}
],
students: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user'
},
status: {
type: String,
enum: ['notBooked', 'booked', 'attended', 'completed']
},
events: [
{
type: Schema.Types.ObjectId,
ref: 'event'
}
]
}
],
});
And ideally I would like to use an updateOne query to both addToSet to the course's list of events, while also updating the students list.
Currently I am using this code to accomplish my updates by first finding the course and then using javascript to update it, which works:
const studentsToAdd = this.attendees.map((attendee) => ({
user: attendee._id,
status: 'booked',
events: [this._id]
}));
const studentIds = studentsToAdd.map((student) => student.user);
const course = await courseModel.findById(this.course);
console.log(studentIds);
course.events.push(this._id);
course.students.forEach((student) => {
if (studentIds.some((s) => s.equals(student.user))) {
student.events.push(this._id);
student.status = 'booked';
studentsToAdd.splice(studentsToAdd.indexOf(studentsToAdd.find((s) => s.user.equals(student.user))), 1);
}
});
course.students.push(...studentsToAdd);
course.save();
However I am curious if it is possible to solve this using a single updateOne on the courseModel schema.
This is part of a mongoose middleware function, and "this" references an event object, which has its own _id, attendees of the event, and the course ID (named course).
The goal is to add the student object part of studentsToAdd to the students array of the course IF the student does not exist (signified by user being a reference by objectId), and if the student already exists then I want to add the eventId (this._id) to the events array for that particular student and set status for that student to "booked".
Is this possible? I have tried many iterations using $cond, $elemmatch, "students.$[]" and so forth but I am quite new to mongodb and am unsure how to go about this.
I'm trying to add object inside an object with id as a key in react provider. Following is the use case.
const [memberList, setMemberList] = useState({
homeTeam: [],
awayTeam: [],
homeTeamClone: {},
});
I can successfully add member to an array, however I'm more keen to add that in homeTeamClone object.
example of object = {"id":"3a21b0a-1223-46-5abe-67b653be5704","memberName":"Adam"}
I want final result as
homeTeamClone: {
"3a21b0a-1223-46-5abe-67b653be5704": {"id":"3a21b0a-1223-46-5abe-67b653be5704","memberName":"Adam"},
"3a21b0a-1223-46-5abe-67b653be5705": {"id":"3a21b0a-1223-46-5abe-67b653be5705","memberName":"Chris"},
"3a21b0a-1223-46-5abe-67b653be5706": {"id":"3a21b0a-1223-46-5abe-67b653be5706","memberName":"Martin"},
}
I tried Object.assign(homeTeamClone, member) but did not get the expected result.
Thanks in Advance.
If the question is how to set individual member than you can do this:
const member = { id: '3a21b0a-1223-46-5abe-67b653be5704', memberName: 'Adam' };
setMemberList({
...memberList,
homeTeamClone: {
...memberList.homeTeamClone,
[member.id]: member,
},
});
In this case spread all old values and add new one. (In case user with same ID is added again, object value will be from the new one)
I'm currently trying Firestore, and I'm stuck at something very simple: "updating an array (aka a subdocument)".
My DB structure is super simple. For example:
proprietary: "John Doe",
sharedWith:
[
{who: "first#test.com", when:timestamp},
{who: "another#test.com", when:timestamp},
],
I'm trying (without success) to push new records into shareWith array of objects.
I've tried:
// With SET
firebase.firestore()
.collection('proprietary')
.doc(docID)
.set(
{ sharedWith: [{ who: "third#test.com", when: new Date() }] },
{ merge: true }
)
// With UPDATE
firebase.firestore()
.collection('proprietary')
.doc(docID)
.update({ sharedWith: [{ who: "third#test.com", when: new Date() }] })
None works. These queries overwrite my array.
The answer might be simple, but I could'nt find it...
Firestore now has two functions that allow you to update an array without re-writing the entire thing.
Link: https://firebase.google.com/docs/firestore/manage-data/add-data, specifically https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array
Update elements in an array
If your document contains an array field, you can use arrayUnion() and
arrayRemove() to add and remove elements. arrayUnion() adds elements
to an array but only elements not already present. arrayRemove()
removes all instances of each given element.
Edit 08/13/2018: There is now support for native array operations in Cloud Firestore. See Doug's answer below.
There is currently no way to update a single array element (or add/remove a single element) in Cloud Firestore.
This code here:
firebase.firestore()
.collection('proprietary')
.doc(docID)
.set(
{ sharedWith: [{ who: "third#test.com", when: new Date() }] },
{ merge: true }
)
This says to set the document at proprietary/docID such that sharedWith = [{ who: "third#test.com", when: new Date() } but to not affect any existing document properties. It's very similar to the update() call you provided however the set() call with create the document if it does not exist while the update() call will fail.
So you have two options to achieve what you want.
Option 1 - Set the whole array
Call set() with the entire contents of the array, which will require reading the current data from the DB first. If you're concerned about concurrent updates you can do all of this in a transaction.
Option 2 - Use a subcollection
You could make sharedWith a subcollection of the main document. Then
adding a single item would look like this:
firebase.firestore()
.collection('proprietary')
.doc(docID)
.collection('sharedWith')
.add({ who: "third#test.com", when: new Date() })
Of course this comes with new limitations. You would not be able to query
documents based on who they are shared with, nor would you be able to
get the doc and all of the sharedWith data in a single operation.
Here is the latest example from the Firestore documentation:
firebase.firestore.FieldValue.ArrayUnion
var washingtonRef = db.collection("cities").doc("DC");
// Atomically add a new region to the "regions" array field.
washingtonRef.update({
regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")
});
// Atomically remove a region from the "regions" array field.
washingtonRef.update({
regions: firebase.firestore.FieldValue.arrayRemove("east_coast")
});
You can use a transaction (https://firebase.google.com/docs/firestore/manage-data/transactions) to get the array, push onto it and then update the document:
const booking = { some: "data" };
const userRef = this.db.collection("users").doc(userId);
this.db.runTransaction(transaction => {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(userRef).then(doc => {
if (!doc.data().bookings) {
transaction.set({
bookings: [booking]
});
} else {
const bookings = doc.data().bookings;
bookings.push(booking);
transaction.update(userRef, { bookings: bookings });
}
});
}).then(function () {
console.log("Transaction successfully committed!");
}).catch(function (error) {
console.log("Transaction failed: ", error);
});
Sorry Late to party but Firestore solved it way back in aug 2018 so If you still looking for that here it is all issues solved with regards to arrays.
https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.htmlOfficial blog post
array-contains, arrayRemove, arrayUnion for checking, removing and updating arrays. Hope it helps.
To build on Sam Stern's answer, there is also a 3rd option which made things easier for me and that is using what Google call a Map, which is essentially a dictionary.
I think a dictionary is far better for the use case you're describing. I usually use arrays for stuff that isn't really updated too much, so they are more or less static. But for stuff that gets written a lot, specifically values that need to be updated for fields that are linked to something else in the database, dictionaries prove to be much easier to maintain and work with.
So for your specific case, the DB structure would look like this:
proprietary: "John Doe"
sharedWith:{
whoEmail1: {when: timestamp},
whoEmail2: {when: timestamp}
}
This will allow you to do the following:
var whoEmail = 'first#test.com';
var sharedObject = {};
sharedObject['sharedWith.' + whoEmail + '.when'] = new Date();
sharedObject['merge'] = true;
firebase.firestore()
.collection('proprietary')
.doc(docID)
.update(sharedObject);
The reason for defining the object as a variable is that using 'sharedWith.' + whoEmail + '.when' directly in the set method will result in an error, at least when using it in a Node.js cloud function.
#Edit (add explanation :) )
say you have an array you want to update your existing firestore document field with. You can use set(yourData, {merge: true} ) passing setOptions(second param in set function) with {merge: true} is must in order to merge the changes instead of overwriting. here is what the official documentation says about it
An options object that configures the behavior of set() calls in DocumentReference, WriteBatch, and Transaction. These calls can be configured to perform granular merges instead of overwriting the target documents in their entirety by providing a SetOptions with merge: true.
you can use this
const yourNewArray = [{who: "first#test.com", when:timestamp}
{who: "another#test.com", when:timestamp}]
collectionRef.doc(docId).set(
{
proprietary: "jhon",
sharedWith: firebase.firestore.FieldValue.arrayUnion(...yourNewArray),
},
{ merge: true },
);
hope this helps :)
addToCart(docId: string, prodId: string): Promise<void> {
return this.baseAngularFirestore.collection('carts').doc(docId).update({
products:
firestore.FieldValue.arrayUnion({
productId: prodId,
qty: 1
}),
});
}
i know this is really old, but to help people newbies with the issue
firebase V9 provides a solution using the arrayUnion and arrayRemove
await updateDoc(documentRef, {
proprietary: arrayUnion( { sharedWith: [{ who: "third#test.com", when: new Date() }] }
});
check this out for more explanation
Other than the answers mentioned above. This will do it.
Using Angular 5 and AngularFire2. or use firebase.firestore() instead of this.afs
// say you have have the following object and
// database structure as you mentioned in your post
data = { who: "third#test.com", when: new Date() };
...othercode
addSharedWith(data) {
const postDocRef = this.afs.collection('posts').doc('docID');
postDocRef.subscribe( post => {
// Grab the existing sharedWith Array
// If post.sharedWith doesn`t exsit initiated with empty array
const foo = { 'sharedWith' : post.sharedWith || []};
// Grab the existing sharedWith Array
foo['sharedWith'].push(data);
// pass updated to fireStore
postsDocRef.update(foo);
// using .set() will overwrite everything
// .update will only update existing values,
// so we initiated sharedWith with empty array
});
}
We can use arrayUnion({}) method to achive this.
Try this:
collectionRef.doc(ID).update({
sharedWith: admin.firestore.FieldValue.arrayUnion({
who: "first#test.com",
when: new Date()
})
});
Documentation can find here: https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array
Consider John Doe a document rather than a collection
Give it a collection of things and thingsSharedWithOthers
Then you can map and query John Doe's shared things in that parallel thingsSharedWithOthers collection.
proprietary: "John Doe"(a document)
things(collection of John's things documents)
thingsSharedWithOthers(collection of John's things being shared with others):
[thingId]:
{who: "first#test.com", when:timestamp}
{who: "another#test.com", when:timestamp}
then set thingsSharedWithOthers
firebase.firestore()
.collection('thingsSharedWithOthers')
.set(
{ [thingId]:{ who: "third#test.com", when: new Date() } },
{ merge: true }
)
If You want to Update an array in a firebase document.
You can do this.
var documentRef = db.collection("Your collection name").doc("Your doc name")
documentRef.update({
yourArrayName: firebase.firestore.FieldValue.arrayUnion("The Value you want to enter")});
Although firebase.firestore.FieldValue.arrayUnion() provides the solution for array update in firestore, at the same time it is required to use {merge:true}. If you do not use {merge:true} it will delete all other fields in the document while updating with the new value. Here is the working code for updating array without loosing data in the reference document with .set() method:
const docRef = firebase.firestore().collection("your_collection_name").doc("your_doc_id");
docRef.set({yourArrayField: firebase.firestore.FieldValue.arrayUnion("value_to_add")}, {merge:true});
If anybody is looking for Java firestore sdk solution to add items in array field:
List<String> list = java.util.Arrays.asList("A", "B");
Object[] fieldsToUpdate = list.toArray();
DocumentReference docRef = getCollection().document("docId");
docRef.update(fieldName, FieldValue.arrayUnion(fieldsToUpdate));
To delete items from array user: FieldValue.arrayRemove()
If the document contains a nested object in the form of an array, .dot notation can be used to reference and update nested fields.
Node.js example:
const users = {
name: 'Tom',
surname: 'Smith',
favorites: {
sport: 'tennis',
color: 'red',
subject: 'math'
}
};
const update = await db.collection('users').doc('Tom').update({
'favorites.sport': 'snowboard'
});
or Android sdk example:
db.collection("users").document("Tom")
.update(
'favorites.sport': 'snowboard'
);
There is a simple hack in firestore:
use path with "." as property name:
propertyname.arraysubname.${id}:
db.collection("collection")
.doc("docId")
.update({arrayOfObj: fieldValue.arrayUnion({...item})})
When i update this data, the deslon and deslat part is not inserted in the document.
var locationData = { update_time: new Date() ,
location: [
{curlon: req.payload.loclon , curlat: req.payload.loclat},
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]};
the update
userLocationModel.update({uid: req.params.accesskey}, locationData, { upsert: true }, function (err, numberAffected, raw) {
//DO SOMETHING
});
I cannot understand why this is happining.
Here is the mongo document that gets inserted. The deslon and deslat are missing even if a new document is created.
{
_id: ObjectId("52f876d7dbe6f9ea80344fd4"),
location: [
{
curlon: 160,
curlat: 160,
_id: ObjectId("52f8788578aa340000e51673")
},
{
_id: ObjectId("52f8788578aa340000e51672")
}
],
uid: "testuser6",
update_time: ISODate("2014-02-10T06:58:13.790Z")
}
Also : Should I be using a structure like this if the document is updated frequently.
This is the mongoose model:
var userLocationSchema = mongoose.Schema({
uid: String, //same as the user access key
update_time: Date, //time stamp to validate, insert when updating. created by server.
location:[
{
curlon: Number, //current location in latitude and longitude <INDEX>
curlat: Number
},
{
deslon: Number, //destination in latitude and longitude <INDEX>
deslat: Number
}
]
});
I wish to update both of the elemets. I don't wan't to insert a new one. But even when I update a non existent document(ie- which results in the creation of a new one), the deslon and deslat are missing.
I have a real problem with this structure but, oh well.
Your Schema is wrong for doing this. Hence also the superfluous _id entries. To do what you want you need something like this:
var currentSchema = mongoose.Schema({
curlon: Number,
curlat: Number
});
var destSchema = mongoose.Schema({
destlon: Number,
destlat: Number
});
var userLocationSchema = mongoose.Schema({
uid: String,
update_time: Date,
location: [ ]
});
This is how mongoose expects you to do embedded documents. That will allow the update in your form you are using to work.
Also your logic on upsert is wrong as you have not included the new uid that is not found in the updated document part. You should take a look at $setOnInsert in the MongoDB documentation, or just live with updating it every time.
Actually, I'm just pointing you to how to separate the schema. As your usage in code stands location will accept anything by the above definition. See the mongoose docs on Embedded Documents for a more detailed usage.
This will work with your update statement as stands. However I would strongly urge you to re-think this schema structure, especially if you intend to do Geo-spatial work with the data. That's out of the scope of this question. Happy googling.
You have to tell mongo how to update your data. So add a simple $set to your update data:
var locationData = {
$set: {
update_time: new Date(),
location: [
{curlon: req.payload.loclon , curlat: req.payload.loclat},
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]
};
EDIT:
If you do not want to exchange the location property as a whole, but insert a new item into the array, use:
var locationData = {
$set: {
update_time: new Date()
},
$push: {
location: [
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]
};
What you should consider is, if it is a good idea to put the current location and the destinations in one array, just because they have the same properties (lon/lat). If for example, there is always one current location and zero to many destinations, you could put the current location into a separate property.
To modify a specific location within an array, you can address it via.
var index = 2, // this is an example
arrayElement = 'location.' + n,
locationData = { $set: {} };
locationData.$set[arrayElement] = {deslon: req.payload.deslon , deslat: req.payload.deslat};
userLocationModel.update({uid: req.params.accesskey}, locationData );
could it be that the intial collection was built with an other version of the schema? i.e. one that had only curlon and curlat? you may have to update the documents then to reflect the amended schema with the deslon and deslat properties.
Basically I got my app up an running but I'm stuck with a problem: if I pass an object that contains an empty array to be saved, the array is not saved into the db. I'm not sure this is a problem in js or the mongo driver, but in order to save the empty array I need to pass the array like so: products: [''].
This is the structure of my mongo document:
_id: ObjectId(...),
name: 'String',
subcategories: [
{
subcategory: 'string',
products: [
{
name: 'string'
price: integer
}
]
}
]
So in my front-end I'm grabbing the whole document through an ajax call pushing a new object into the subcategories array. The new object looks like this:
{subcategory:'string', products:['']}
And this works okay until I need to insert a new object inside the array: Because I've grabbed the whole object, pushed the new object to the array, the previous one looks like this:
{subcategory: 'string'}
Having lost the mention to products:[] array in the process.
How can I get around this? I need to be able to have empty arrays in my object.
EDIT
What I did on front end: Got the whole object with $.get which returned:
var obj =
_id: ObjectId(...),
name: 'String',
subcategories: [
{
subcategory: 'Subcategory1',
products: [
{
name: 'string'
price: integer
}
]
}
];
Then on the front end I've pushed the new object category inside the subcategories array:
data.subcategories.push({subcategory: 'Subcategory2', products: ['']})
Where subcat was a string with the category name. On my db I could see that I've successfully added the object:
var obj =
_id: ObjectId(...),
name: 'String',
subcategories: [
{
subcategory: 'Subcategory1',
products: [
{
name: 'string'
price: integer
}
]
},
{
subcategory: 'Subcategory2'
products: []
}
];
The problem was when I wanted to add another subcategory, the previous one return empty:
var obj =
_id: ObjectId(...),
name: 'String',
subcategories: [
{
subcategory: 'Subcategory1',
products: [
{
name: 'string'
price: integer
}
]
},
{
subcategory: 'Subcategory2'
},
{
subcategory: 'Subcategory3'
products: []
},
];
Because at some point the empty array was removed from the object. Like I said, I did fix this in the front end, so the error jade was throwing has been addressed, but I still find odd that the products: [] was being removed from the document.
I'm new to MongoDb and node, not to mention that I'm also new with JS, so it might well be a feature that I'm unaware of.
When passing empty arrays to Mongo they are interpreted as empty documents, {}. Zend Json encoder will interpret them as empty arrays []. I understand that it's not possible to tell which one is correct.
Incase of empty arrays try posting as
Array[null];
instead of Array[];
This will be working fine
When passing empty arrays to Mongo they are interpreted as empty documents, {}. Zend Json encoder will interpret them as empty arrays []. I understand that it's not possible to tell which one is correct.
In my view it's more logical that the actual php array (when empty) is interpreted as an array in MongoDB. Although that will require something else to identify empty documents it's still more logical than the current behaviour.
A possible solution would be to introduce a new object, MongoEmptyObject (or using the stdObj) whenever one want to introduce an empty object.
Meanwhile, a workaround is to detect empty arrays in php, and inject a null value $arr[0] = null;
Then the object will be interpreted as an empty array in mongo.
The workaround works both in PHP and in the mongo console. Question: does json allow for arrays with null values? If so, then the workaround is a sign of another bug.
PHP:
if (is_array($value) && empty($value))
{ $value[0] = null; }
Mongo Console:
var b =
{hej:"da", arr: [null]}
db.test.save(b);
db.test.find();
{"_id" : "4a4b23adde08d50628564b12" , "hej" : "da" , "arr" : []}