I have the following schema:
const Person= new Schema({
father: {
type: Schema.Types.ObjectId,
ref: "Person"
},
childs: [{
type: Schema.Types.ObjectId,
ref: "Person"
}]
})
module.exports = mongoose.model("Person", Person)
The next step is popular. I do this with a pre find
.
Person.pre('find', function(next){
this.populate('father')
this.populate('childs')
return next()
}
The problem comes when I have a child pointing to a father, and a parent pointing to a child. This causes a circular dependency. I have tried to exclude some fields in child in this way this.populate('childs', '-father -childs')
but it still doesn't work.
How can I prevent this deep population?
UPDATE
Currently I solve the problem from the user interface. This is with the following procedure:
- The user requests a document.
- I make my request to the express server.
- The server responds to me with a document not populated for these fields.
- From the client I check the conflicting fields and make a new request with them to the express server.
- The server responds with one or more documents not populated in the conflicting fields.
- With the new documents received, I replace the corresponding conflicting fields and simulate the population.
- I show the information to the user.
Any attempt to replicate this function but on the server
express
ends in an infinite loop.
After doing some research, I think the solution can be found at the stage
$graphLookup
ofaggregate
searching recursively through a collection.In your case, the query would have to be done as follows:
By being able to indicate the depth/recursion number you will not reach the infinite loop that is happening to you.
I'll give you a couple of links that have helped me understand
$graphLookup
that I knew it existed but I've never had the chance to use it and I think it's a good case for it.MongoDB API
A good example to understand $graphLookup
Even so, if this has not been able to help you, I hope it gives you some idea to solve it.
All the best
Although you already have an answer that is valid, I will try to use a different approach to solve the problem.
ISSUE
The problem you have is when you try to make a
middleware
MongooseJS process known aspre hook
.One of the things that we must take into account when carrying out operations of this type is to understand what happens in the process.
Each time the method is called
populate
in this pre-query part, a modification is made to the objectthis
, which in this case represents an object of typeQuery
. In this modification, the fields to be populated are included.This is all very well, however the problem arises because the fields to be populated are from the same collection.
For each field to be populated, a call is made to the method
find
to search for the data of said field, and since we have established apre hook
for said method in our schema, our method will be executed again,middleware
which will seek to populate the fields that we have requested to said collection.If we have
n
generations related by a single field,n + 1
calls will be made tomiddleware pre
.Suppose we have 2 documents:
We have only 1 generation related to 'Papa de Angel', since it only has one field
childs
and does not have a fieldfather
. The fieldchilds
has a value that points to 'Angel'. The 'Angel' document has a fieldchilds
with aArray
blank, and a fieldfather
pointing to 'Papa de Angel'.If we call
populate('childs')
for the document 'Papa de Angel' using apre hook
, a search for the documents in the field will be performedchilds
. This search will be done using thefind
. And since we have set apre hook
for the methodfind
, the documents found (those whose reference appears in thechilds
'Papa de Angel' field), will also have the method appliedpopulate
to the fieldchilds
. Since 'Angel' has no children, the search stops there. Giving as a result that our will have been executed 2 timespre hook
, for 1 generation related to the document 'Papa de Angel' in the fieldchilds
.This gives us an idea of how the method works
populate
on an instancepre hook
.In your case, you're not only calling the method
populate
for the fieldchilds
but you 're calling the method for the field as wellfather
, and that's what causes the infinite loop.Take the same case as above,
populate
for the fieldsfather
andchilds
the document 'Papa de Angel'father
, the search on this field ends there.childs
, for this the methodfind
on the elements of the field is calledchilds
.middleware
to populate the fieldschilds
andfather
of the 'Angel' document.childs
'Angel' field ends there ('Angel' has no children).father
, which is 'Papa de Angel'.middleware
since the search is done using thefind
.populate
about the fieldsfather
andchilds
the 'Papa de Angel' document. We are back where we started (point 1).This is why the infinite loop occurs.
SOLUTION
The solution seems silly, however you are right.
Don't use a
pre hook
to dopopulate
of documents whose reference points to the same collection. I have just explained the reason. You would be using resources unnecessarily, by making repeated calls to the database to obtain only 1 necessaryn
data and unnecessary data. We have already seen that if you haven
documents linked to the same collection,n+1
requests will be made to the database to populate the necessary fields.As you are only interested in 1 level of search (search only the data of the document or documents whose reference appears in the field
father
and in the fieldchilds
), the processpopulate
must be done on the Query Object returned by your queryfind
.For example:
If we run the above code on the 'Papa de Angel' document, the result obtained would be:
Since 'Angel's Dad' does not have a field
father
, it is omitted from the console output, but the fieldchilds
has been populated with the data from the 'Angel' document.Now, you will say, but it is that first it was done
populate
to the fieldfather
and not to the fieldchilds
. Well, it turns out that the order does not affect the result.Therefore, the following lines are all equivalent:
What changes with respect to doing the
populate
in apre hook
?Well, first of all, the method
find
returns an ObjectQuery
whose documents have a fieldfather
and a fieldchilds
that have not been populated , the process of is applied to these fieldspopulate
, which will return only the values of the documents (from the same collection in this case ) that match their respective references, and the process ends there. That is, although these populated documents contain fieldsfather
andchilds
, it is not on them that a process ofpopulate
. Also, their respective fields would be nested fields within the original fieldfather
.childs
Lastly, if you find it cumbersome to write the method calls
populate
directly after the method callfind
, or if for some reason you want to centralize this process, you can write your own function. For example:You could then use it in your code like this:
I hope this clears up your doubt as to why you were falling into an infinite loop by using a
pre hook
to populate your fields with reference to the same collection.