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}}]
})
Related
I currently have a schema like this:
const postSchema = mongoose.Schema({
title: String,
message: String,
name: String,
creator: String,
tags: [String],
selectedFile: String,
likes: { type: [String], default: [] },
createdAt: {
type: Date,
default: new Date(),
},
})
One of the problem that I anticipate is that as the number of users grow, searching the likes array will become inefficient. Is there a way to store the likes array instead as an Object (key would be userId and value could be true) so that finding someone in the Object would become more efficient.
I am also open to hearing any other ideas that you might have.
Thanks!
I want to suggest populate() for this. From that, you can manage a large no. of user information without a problem. You can create a new schema as likes and add the id of the likes document as an id with the populate. Check the below example.
const likeSchema = mongoose.Schema({
type: [String],
default: [] },
});
const Like = mongoose.model("Like", likeSchema);
Then create the postschema like below.
const postSchema = mongoose.Schema({
title: String,
message: String,
name: String,
creator: String,
tags: [String],
selectedFile: String,
likes: {
type: mongoose.Schema.Types.String,
ref: 'Like',
},
createdAt: {
type: Date,
default: new Date(),
},
})
const Post = mongoose.model("Post", postSchema);
You can easily get all the data inside a likes document by populating when running a query like below.
const posts = await Post.findById({creator_id}).populate("likes");
//Below code will print the data of the first element of the type array of relevant likes document.
console.log(posts.likes.type[0]);
Check the populate and population sections of the mongoose documentation to learn more.
I have the following schemas and models:
const shelfSchema = new mongoose.Schema({
name: {
type: String,
required: true,
default: 'New Shelf'
},
books: {
type: [bookSchema],
required: false
}
}, {autoCreate: false})
const shelfModel = mongoose.model('Shelf', shelfSchema)
const librarySchema= new mongoose.Schema({
shelves: {
type: [shelfSchema],
required: false,
}
})
const libraryModel = mongoose.model('library', librarySchema)
const userSchema = new mongoose.Schema({
username: {
type: String,
required:true,
unique:true
}
library: {
type: mongoose.Schema.Types.ObjectId,
ref: 'library',
required: true
}
})
const userModel = mongoose.model('User', userSchema );
Every User has a unique username and a Library reference, and every library has one or more Shelves, each one with one or more Book.
When I add a book, I pass also the information of the shelf name I want to insert the book into, and if the shelf with that name is missing, it should be created.
Since I come from a sql mentality I'm having a bit of difficulties in understanding if I can manage an upsert the same way.
I thought that I could insert the book using at most two queries: one to create the self if it's missing and one to insert the book in the shelf.
My approach was then to use
UserModel.findOneAndUpdate({username: user.username, "library.shelves.name": shelfName},{}, {upsert: true})
but since it's a query in the UserModel, if it doesn't find a user with a shelf with that name it tries to create a new user, duplicating the username.
Am I right to assume that I have to split this first query in two parts, "Find a user with a shelf with that name in the library" and in case it's not found "Create that shelf in the library"?
Or is it possible to unite the queries in some way?
What you are doing right now is trying to update a UserModel that matches the user name, has the shelve name, that is why upsert: true creates a new user entry.
What you should do is to find out the library _id and then $push the book to the shelf you are searching for.
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
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.
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);