I have the following model where I'm using SubDocuments:
const SubCategory = new Schema({
label: { type: String}
});
const Category = new Schema({
label: { type: String},
subcategories: [SubCategory]
});
Also I have a model where I'd like to save subcategory
const Article = new Schema({
title: { type: String }
subcategory: { type: Schema.Types.ObjectId, ref: 'SubCategory'}
},
Could you help me how I should populate subcategory for Article model?
Short answer: To populate a document the referenced document needs to be in its own collection.
Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). -- http://mongoosejs.com/docs/populate.html
So in the example above, SubCategory would need to be a model/collection on its own that is referenced from both the Category collection and the Article collection.
Related
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 I could query with mongoose all food by City _id which is referenced in Place model, and Place model referenced in Food model.
EDIT: (example) If I select New York (City), I want to get all food from New York.
const FoodSchema = new Schema({
place: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Place',
required: true
}})
const PlaceSchema = new Schema({
city: {
type: mongoose.Schema.Types.ObjectId,
ref: 'City',
required: true
}})
const CitySchema = new Schema({
name: {
type: String}})
You can use populate() multiple times. This code from the official documentation gives an example where they query a user, populates its friends, and then the friends of the friends:
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});
In your case this would be
Food.find().populate({path: 'place', populate: {path: 'city'}})
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
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.
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);