How to populate a document inside an array inside another document? - node.js

Sorry if title looks complicated... I couldn't think of a better way to describing it.
My real case situation matches the following Schemes:
Collection1:
const mongoose = require('mongoose');
const itemSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
quantity: { type: Number, required: [true, 'Quantity is required.'] },
collection2: { type: mongoose.Schema.Types.ObjectId, ref: 'Collection2' }
}, { _id : false });
const collection1Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
imagePath: { type: String, required: [true, 'Image is required.'] },
items: [itemSchema]
});
module.exports = mongoose.model('Collection1', collection1Schema);
Note: itemsSchema is inside the collection1 file (and having no declared _id's) because they only exist for the Collection1 model (considering "quantity" and other fields I removed for simplification). This itemsScheme is not needed elsewhere as another collection.
Collection2:
const mongoose = require('mongoose');
const collection2Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'], unique: true }
});
module.exports = mongoose.model('Collection2', collection2Schema );
Note: Other properties (such as 'imagePath') were removed for simplification.
Now, this is the query I am trying to run:
Collection1.find()
.populate({
path: 'items',
populate: {
path: 'collection2', model: 'Collection2'
}
})
.then(...)
.catch(...);
And this is the error message I am getting when I run it:
Error fetching collection1: Cast to ObjectId failed for value "{
name: 'an item name',
quantity: 750
}" at path "_id" for model "Collection1"
The exact same error happens if I just run:
Collection1.find()
.populate('items')
.then(...)
.catch(...);
Maybe I cannot run .populate('items') because it has no declared model. If this is the case, how can I populate collection2 while querying collection1? Again, I cannot consider storing items in a separated collection.
But if I run:
Collection1.find()
.populate('collection2')
.then(...)
.catch(...);
I get the items, no errors, but it doesn't populate collection2. Well, it makes sense for the items because they're just an array of a block of properties inside collection1. But what about populating collection2?
Collection2 already has a few documents added, all with their _ids and other fields well filled. In the controller, I set _id: new mongoose.Types.ObjectId(), while creating a new document for both cases, Collection1 and Collection2.
In the front-end, I create a new document for Collection1, I add items, each item with a document from Collection2, and I save everything with no errors. I also confirmed everything is been properly saved (collection1 has list of items and each item an _id reference to collection2). The only problem is populating collection2 inside this array.
I have already tried restructuring everything with _ids (including itemScheme) and dropping all collections to test it again but no success.
I have been stuck with this problem for about three days now.
Is there any special property I should be setting for populate to make it work for this specific structure?
Thanks in advance...

populate('items')
This will not work as item is not a model.
What you want is following:
Collection1.find()
.populate('items.collection2')
.then(...)
.catch(...);
This will populate collection2 in all the array elements

Related

Dynamically create a collection field based on the user response with mongoose in mongo dB

I've a database collection where two types of users are there.
1.Customer: They will have all the basic functionalities.
2.Vendor: They will also have the basic functionalities available in addition they can create, delete, update and get/view the vehicles.
suppose a vendor created a vehicle so a vehicle id will get added to the vendor's collection likewise:
{
. /*other fields*/
.
.
listings: [
"uniqueId",
"uniqueId2"
]
}
I did some searching and found out that to add vehicle Id's to listings, the field needs to be created first in mongoose otherwise my data will not get inserted in mongodb through mongoose.
This rises a problem where all the users have listings field in them.
So, here's my user model I have created:
const userSchema = new mongoose.Schema({
user_type: {
type: String,
required: [true, "user type is required!"],
enum: ["customer", "vendor", "Customer", "Vendor"],
default: "customer"
},
listings: {
type: Array,
},//TODO: only create the listings array if the user type is vendor
});
So, my question is can I create this listing field only if the user_type is vendor?
As defined, in the mongoose documentation, Arrays have a default value of [].
So you'll need to override it. Try this:
const userSchema = new mongoose.Schema({
user_type: {
type: String,
required: [true, "user type is required!"],
enum: ["customer", "vendor", "Customer", "Vendor"],
default: "customer"
},
listings: {
type: [String], // can also be ObjectId
default: undefined
},
});

How can I populate an array field without ref in MongoDB (mongoose)?

I have a Schema that contains an array field. In this array field I'll insert objects that contains the author's _id and an author's comment.
I want populate this field, but my Schema don't have ref.
that's my Schemas
const Book = new Schema({
name: {
type:String,
required: true
}
authors: [{
type: Array
}]
})
const Author = new Schema({
name: {
type:String,
required: true
}
(... author's data)
})
I will insert objects in authors field of Book collection:
insertAuthor = {
_id:id,
comment: 'comment'
}
It's working fine, but I can't populate the authors field.
Can anybody help me with this?
Thank you!!
You can specify the model in the populate if the ref is not defined in the Schema. So from what I understand you need to query the Book by populating Author.
const books = await Book.find().populate({path: 'authors._id', model: 'Author'}).exec();
Also in your Book schema if you are inserting a JSON object as mentioned in your question then don't need to define type: Array inside the JSON. You can update it as below.
const Book = new Schema({
name: {
type:String,
required: true
}
authors: [{}] //or [{_id: {type: Schema.Types.ObjectId}, comment: {type: String}}]
})

How to create reference in Model Schema in mongoose

If I have model Products:
var ProductSchema = new Schema({
title: {
type: String,
maxlength: 20,
required: true
},
description: {
type: String,
maxlength: 300
},
price: Number,
active: Boolean,
category: {
}
});
And I must create category, type ObjectID with reference to category model
I create right now this:
var CategorySchema = new Schema({
name: {
type: String,
maxlength: 300
},
description: {
type: String,
maxlength: 300
}
});
Can someone what to do now? Because i do not know how.
The category object in the schema should be as following:
category: {
type: Schema.Types.ObjectId,
ref: 'Category' //category model name
}
For more information about references you should read the following paragraph:
DBRefs are references from one document to another using the value of the first document’s _id field, collection name, and, optionally, its database name. By including these names, DBRefs allow documents located in multiple collections to be more easily linked with documents from a single collection.
To resolve DBRefs, your application must perform additional queries to return the referenced documents like population queries.

Mongoose populate ObjectID from multiple possible collections

I have a mongoose model that looks something like this
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: 'article',
index:true,
},
});
But 'item' could be referenced from multiple collections. Is it possible to do something like this?
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: ['article','image'],
index:true,
},
});
The idea being that 'item' could be a document from the 'article' collection OR the 'image' collection.
Is this possible or do i need to manually populate?
Question is old, but maybe someone else still looks for similar issues :)
I found in Mongoose Github issues this:
mongoose 4.x supports using refPath instead of ref:
var schema = new Schema({
name:String,
others: [{ value: {type:mongoose.Types.ObjectId, refPath: 'others.kind' } }, kind: String }]
})
In #CadeEmbery case it would be:
var logSchema = new Schema({
item: {type: mongoose.Types.ObjectId, refPath: 'kind' } },
kind: String
})
But I did't try it yet...
First of all some basics
The ref option says mongoose which collection to get data for when you use populate().
The ref option is not mandatory, when you do not set it up, populate() require you to give dynamically a ref to him using the model option.
#example
populate({ path: 'conversation', model: Conversation }).
Here you say to mongoose that the collection behind the ObjectId is Conversation.
It is not possible to gives populate or Schema an array of refs.
Some others Stackoverflow people asked about it.
Soluce 1: Populate both (Manual)
Try to populate one, if you have no data, populate the second.
Soluce 2: Change your schema
Create two link, and set one of them.
var LogSchema = new Schema({
itemLink1: {
type: ObjectId,
ref: 'image',
index: true,
},
itemLink2: {
type: ObjectId,
ref: 'article',
index: true,
},
});
LogSchema.find({})
.populate('itemLink1')
.populate('itemLink2')
.exec()
Dynamic References via refPath
Mongoose can also populate from multiple collections based on the value of a property in the document. Let's say you're building a schema for storing comments. A user may comment on either a blog post or a product.
body: { type: String, required: true },
on: {
type: Schema.Types.ObjectId,
required: true,
// Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
// will look at the `onModel` property to find the right model.
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['BlogPost', 'Product']
}
});
const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);

Can't update Mongoose subdocument property

Using the following line of code I can update a given Transaction (document), but when I try to update properties of its subdocument, the given value is not persisted.
Transaction.findById('55cf89abe148323e5368dcd5').populate('cryptocurrencies')
.then(function(transaction){
transaction.status = 'completed'; // this updates the transaction status correctly
transaction['cryptocurrencies'][0].status = 'ordered'; // this update is not persisted
return transaction.save()
.then(function(transaction){
console.log(transaction['cryptocurrencies'][0].status); // this shows the status as updated, but it's not persisted
})
})
I've also used the line transaction['cryptocurrencies'][0].markModified('status'); after I update the property to no avail. Can't find anything in the docs: What am I missing?
Update: I've tested this further and found that I have to use the .save() method on both the document and its subdocument. Is there any I can run a method that will save the document with its subdocument properties changed, or do I have to run two operations to save one document each time?
Update:
Here is my model code:
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TransactionSchema = new Schema({
userId: { type: String, required: true },
status: { type: String, enum: ['unpaid', 'failed', 'paid', 'ordered', 'received', 'withdrawn', 'completed'] },
invoice: String,
saltStatus: String,
saltTransactionId : Number,
saltBank: String,
saltConfirmation: String,
saltAmount : Number,
saltDate : String,
saltResponseCode: String,
cryptocurrencies: [{ type: mongoose.Schema.Types.ObjectId, ref: 'CryptoCurrency' }]
});
var CryptoCurrencySchema = new Schema({
currencyName: String,
price: Number,
amount: Number,
total : Number,
walletAddress: String,
dollarsPaid : Number,
exchangeTransactionId: Number,
coinTransactionId : String,
status: { type: String, enum: ['ordered', 'received', 'withdrawn'] }
});
module.exports.Transaction = mongoose.model('Transaction', TransactionSchema);
module.exports.CryptoCurrency = mongoose.model('CryptoCurrency', CryptoCurrencySchema);
From your code example it seems that you are saving the sub-documents as references which means that once you update the sub-document, you only need to call .save() for it and not the parent document as well.
If you are saving your documents as sub-schemas, once updating the sub-document you can call .save() only for the parent document and it will persist the child document as well.
From the docs:
Sub-documents enjoy all the same features as normal documents. The
only difference is that they are not saved individually, they are
saved whenever their top-level parent document is saved.
http://mongoosejs.com/docs/subdocs.html

Resources