Firebase - Create subcollection only if the parent doc exists - javascript

In Firestore, when you are deleting a doc, you can specify a condition like { exists: true }, in order to avoid deleting the document if it doesn't exist.
In a concurrent scenario, it is possible that we would only like to add a doc to a subcollection if the parent document exists.
For example, imagine the following NoSql structure:
-comment (doc)
/likes (subcollection created when the first like is given)
-like1
-like2
...
Is it possible to do something like
likeRef.create(data, { parent_exists: true });
??
Is the only way to handle this situation with Transactions (reading the parent doc, and throwing an error when .exists() is false)?
I am afraid of this type of situation because if the sub-collection is created at the same time as the container document is deleted, there could be orphan "entities" in the database that may even break the UI if it is not prepared for these extreme cases.

This is not possible as a single operation in Firestore.
The only way to do this is to start a transaction, then get() the parent document inside the transaction, and add the documents to the subcollection if the parent exists.

Related

Using a batch to create a collection and a subcollection at once

I'm trying to create a new collection, and then create a sub collection all at once.
The code is being run when a user creates a new team and adds a players to the team. (both team and player are created/added at the same time)
This is what I have attempted so far:
const batch = db.batch()
const collectionRef = db.collection('users').doc(user.uid).collection('teams').doc(id)
batch.set(
collectionRef.set({
teamId: id,
teamName: teamName
})
)
batch.set(
collectionRef.collection('players').doc(player.id).set(playerObject)
)
batch.commit().then(()=> {}).catch(err=> console.log(err))
The player object is defined like so:
{
name: 'John Doe',
id: '123',
imgs: ['img.png']
...
}
But whenever this runs, only the first write is executed, the sub collection is not created. I think this is because the collection does not exist, so might cause an issue.
Using firestore Batch is a better approach to do this. Create a batch and add the main document followed by the subcollection document(s).A collection is created once a document is written to it, and removed automatically once the last document is removed from it.
If you want to write the collection [parent] and the subcollection [child] in a single batch write, you can do it by generating a UniqueID for the parent document, and then creating the sub collection documents under that same path for example as below:
Take a document document under the collection_name collection at the
path collection_name/document/
And another one sub-collection_name document under the
subCollection_name (sub-)collection
collection_name/document/subCollection_name/sub-collection dcoument
Also as mentioned above in the comments from Alex,use reference for the collection object while calling the object in the code to create the path for collection and sub-collection reference path, if the reference object and path is not defined the code will not work.
You may check the below examples for similar implementation:
How to add sub collection to firestore
Create document and sub-collection in one shot
Add collection and subcollection as batch writes

Remove all fields associated linked to a document that I removed on another collection

For the context I'm using firebase/nodeJS on my application.
I'm deleting an user of a collection called workers. To do so I update the field deleted : true on the worker document.
I'm not deleting the document, because I want to keep their infos if needed.
But when I delete a worker, I must remove him from all the properties is working on (they have a field called workers, which is an array of all the workers associated.)
Hence, I must browse all the properties of the collection associated, check if the worker is on it and remove him.
Is there a way to do so ?
I saw this page : https://firebase.google.com/docs/firestore/manage-data/add-data#node.js_11
But don't know how to use theses ressources to do so.
So I finally did it alone.
First, I had to fetch all the properties where the work is working on, to do this I used array-contains.
let docsProperties = await db.collection('user').doc(id).collection("properties")
.where("workers", "array-contains", workerId).get();
Then I wanted to browse all theses properties and remove the worker. Thought about doing a ForEach first. But the problem is I wanted to use an await .update() with arrayRemove and async methods aren't usable inside ForEach.
So I used promises.
let promises = [];
docsProperties.forEach(doc => {
promises.push(doc.ref.update({
workers: admin.firestore.FieldValue.arrayRemove(workerId)
})); });
return Promise.all(promises);
arrayRemove is a method from the firebase documentation to remove all the occurences of an element on a following array field.
So I browsed all properties, added all promises and executed them!
worked perfectly, if anyone got questions don't hesitate.

Using firebase .update within a .where (instead of .doc)

I am new to firebase and am struggling a little bit.
Currently, I am trying to update an array within a user, within a document. However, I cannot match the user to current user using the unique ID, as each users unique ID is their username, and it may have changed since creation.
I figured the best way to match the documents user to the current user would be to use a .where().get() and then use an "update()" to update the array.
Now, this is where I am getting stuck. In the firebase documents, their example of using .update is attached to a .doc
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")
});
However, as I am using a .where, I assume I have to use references and snapshots. But, I am not quite sure how references work in this scenario and, with that, how to update properly.
Here is the code I have after a while of messing round, but no matter my variations i cannot figure it out. (essentially, I want to add a new project (in this case called "new project" to the users array of postedProjects.)
db.collection('users').where('user_id', '==', this.userInfo.user_id)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.data().update({
postedProjects: firebase.firestore.FieldValue.arrayUnion("new project")
})
})
})
This gives me an error of ".update() is not a function".
Is anyone able to help me with my solution to show me how references should properly be used in this scenario?
You're almost there. You can't update the data of DocumentSnapshot though, since that is the in-memory representation of the document data. Instead you need to get the DocumentReference and call update on that.
doc.ref.update({
postedProjects: firebase.firestore.FieldValue.arrayUnion("new project")
})
You need a DocumentReference in order to update() a document. Nothing else will work.
In your code, doc is a QueryDocumentSnapshot type object. If you want the DocumentReference object that refers to the document from that snapshot, use its ref property.
doc.ref.update({
postedProjects: firebase.firestore.FieldValue.arrayUnion("new project")
})

Nodejs + mongodb : How to query $ref fields?

I'am using MongoDB with a nodejs REST service which exposes my data stored inside. I have a question about how to interrogate my data which uses $ref.
Here is a sample of an Object which contains a reference to another object (detail) in anther collection :
{
"_id" : ObjectId("5962c7b53b6a02100a000085"),
"Title" : "test",
"detail" : {
"$ref" : "ObjDetail",
"$id" : ObjectId("5270c7b11f6a02100a000001")
},
"foo" : bar
}
Actually, using Node.js and mongodb module, I do the following :
db.collection("Obj").findOne({"_id" : new ObjectID("5962c7b53b6a02100a000085"},
function(err, item) {
db.collection(item.$ref).findOne({"_id" : item.$id}, function(err,subItem){
...
});
});
In fact I make 2 queries, and get 2 objects. It's a kind of "lazy loading" (not exactly but almost)
My question is simple : is it possible to retrieve the whole object graph in one query ?
Thank you
No, you can't.
To resolve DBRefs, your application must perform additional queries to return the referenced documents. Many drivers have helper methods that form the query for the DBRef automatically. The drivers do not automatically resolve DBRefs into documents.
From the MongoDB docs http://docs.mongodb.org/manual/reference/database-references/.
Is it possible to fetch parent object along with it's $ref using single MongoDB query?
No, it's not possible.
Mongo have no inner support for refs, so it up to your application to populate them (see Brett's answer).
But is it possible to fetch parent object with all its ref's with a single node.js command?
Yes, it's possible. You can do it with Mongoose. It has build-in ref's population support. You'll need to change your data model a little bit to make it work, but it's pretty much what you're looking for. Of course, to do so Mongoose will make the same two MongoDB queries that you did.
Answer of Vladimir is not still valid as the db.dereference method was deleted from MongoDB Nodejs API:
https://www.mongodb.com/blog/post/introducing-nodejs-mongodb-20-driver
The db instance object has been simplified. We've removed the following methods:
db.dereference due to db references being deprecated in the server
No, very few drivers for MongoDb include special support for a DBRef. There are two reasons:
MongoDb doesn't have any special commands to make retrieval of referenced documents possible. So, drivers that do add support are artificially populating the resulting objects.
The more, "bare metal" the API, the less it makes sense. In fact, as. MongoDb collections are schema-less, if the NodeJs driver brought back the primary document with all references realized, if the code then saved the document without breaking the references, it would result in an embedded subdocument. Of course, that would be a mess.
Unless your field values vary, I wouldn't bother with a DBRef type and would instead just store the ObjectId directly. As you can see, a DBRef really offers no benefit except to require lots of duplicate disk space for each reference, as a richer object must stored along with its type information. Either way, you should consider the potentially unnecessary overhead of storing a string containing the referenced collection's documents.
Many developers and MongoDb, Inc. have added an object document mapping layer on top of the existing base drivers. One popular option for MongoDb and Nodejs is Mongoose. As the MongoDb server has no real awareness of referenced documents, the responsibility of the references moves to the client. As it's more common to consistently reference a particular collection from a given document, Mongoose makes it possible to define the reference as a Schema. Mongoose is not schema-less.
If you accept having and using a Schema is useful, then Mongoose is definitely worth looking at. It can efficiently fetch a batch of related documents (from a single collection) from a set of documents. It always is using the native driver, but it generally does operations extremely efficiently and takes some of the drudgery out of more complex application architectures.
I would strongly suggest you have a look at the populate method (here) to see what it's capable of doing.
Demo /* Demo would be a Mongoose Model that you've defined */
.findById(theObjectId)
.populate('detail')
.exec(function (err, doc) {
if (err) return handleError(err);
// do something with the single doc that was returned
})
If instead of findById, which always returns a single document, find were used, with populate, all returned documents' details property will be populated automatically. It's smart too that it would request the same referenced documents multiple times.
If you don't use Mongoose, I'd suggest you consider a caching layer to avoid doing client side reference joins when possible and use the $in query operator to batch as much as possible.
I reach the desired result with next example:
collection.find({}, function (err, cursor) {
cursor.toArray(function (err, docs) {
var count = docs.length - 1;
for (i in docs) {
(function (docs, i) {
db.dereference(docs[i].ref, function(err, doc) {
docs[i].ref = doc;
if (i == count) {
(function (docs) {
console.log(docs);
})(docs);
}
});
})(docs, i)
}
});
});
Not sure that it solution is best of the best, but It is simplest solution that i found.

Accessing a hasMany Association in a Sencha Touch 2 list tap handler programmatically

I am new to ST2, and I am having difficulty with trying to do something I think should be simple.
I have a model that is a user with a hasMany association called emails. I have a list that successfully loads the top level user items, and when I tap on an item in the list, I am able to access the users properties using the record passed into the tap event via
record.get('displayName');
But any way I have tried to access the collection of emails from the record object has not worked.
I've tried :
record.get('emails'); // doesn't work
record.getEmails(); // doesn't work
record.emails().each(...);
record.emails() exists, but no idea what it is. each() yields nothing, though it evaluates ok.
Can anyone explain to me how to get at the association data elements from this record object?
Here is an example of the JSON I am generating :
[
{
"id":0,
"displayName":"Display Name 0",
"emails":[
{
"value":"email#thehost.us",
"type":"home",
"pref":"true"
}
]
}
]
record.emails() is the correct way to do it. That will return an instance of Ext.data.Store which you can then load (if there is no data) the records, or loop through them.
More documentation on the hasMany association is available here: http://docs.sencha.com/touch/2-0/#!/api/Ext.data.association.HasMany

Categories

Resources