Is it possible to populate a mongoose model with a field of a reference model that isn't the _id ... e.g. a username.
so something like
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : { type: String, field: "username", ref: 'Story' }
});
This is supported since Mongoose 4.5, and is called virtuals population.
You have to define your foreign keys relationships after your schemas definitions and before creating models, like this:
// Schema definitions
BookSchema = new mongoose.Schema({
...,
title: String,
authorId: Number,
...
},
// schema options: Don't forget this option
// if you declare foreign keys for this schema afterwards.
{
toObject: {virtuals:true},
// use if your results might be retrieved as JSON
// see http://stackoverflow.com/q/13133911/488666
//toJSON: {virtuals:true}
});
PersonSchema = new mongoose.Schema({id: Number, ...});
// Foreign keys definitions
BookSchema.virtual('author', {
ref: 'Person',
localField: 'authorId',
foreignField: 'id',
justOne: true // for many-to-1 relationships
});
// Models creation
var Book = mongoose.model('Book', BookSchema);
var Person = mongoose.model('Person', PersonSchema);
// Querying
Book.find({...})
// if you use select() be sure to include the foreign key field !
.select({.... authorId ....})
// use the 'virtual population' name
.populate('author')
.exec(function(err, books) {...})
It seems they enforce to use _id, and maybe we can customize it in the future.
Here is the issue on Github https://github.com/LearnBoost/mongoose/issues/2562
This is an example of using the $lookup aggregate to populate a model called Invite with the respective User based on the corresponding email field:
Invite.aggregate(
{ $match: {interview: req.params.interview}},
{ $lookup: {from: 'users', localField: 'email', foreignField: 'email', as: 'user'} }
).exec( function (err, invites) {
if (err) {
next(err);
}
res.json(invites);
}
);
It's probably quite similar to what you're trying to do.
To add to Frosty's answer, if you're looking to refer to an array of documents of another collection you would make changes like so.
BookSchema = new mongoose.Schema(
{
title: String,
authorId: [Number],
},
// schema options: Don't forget this option
// if you declare foreign keys for this schema afterwards.
{
toObject: { virtuals: true },
// use if your results might be retrieved as JSON
// see http://stackoverflow.com/q/13133911/488666
toJSON: {virtuals:true}
});
PersonSchema = new mongoose.Schema({ id: Number, name: String });
BookSchema.virtual("author", {
ref: "Person",
localField: ["authorId"],
foreignField: ["id"],
// justOne: true, // Needs to be commented out in this scenario,
});
You may use the populate() API.
The API is more flexible, you don't have to specify ref and field in the Schema.
http://mongoosejs.com/docs/api.html#document_Document-populate
http://mongoosejs.com/docs/api.html#model_Model.populate
You can mix and match with find().
Related
Get Data from the different collection without any relation in mongoose
Collection Schema
mongoose.Schema({
skillid:{type:Number},
name:{type:String},
});
Skill Collection
mongoose.Schema({
userid: {type: mongoose.Types.ObjectId},
OverView:{type:String},
location:{type:String},
skills:[{Type:Number}]
});
{
_id: 5f48d5d1b98bffee67b49917
skillid: 1
name: 'HTML'
}
{
_id: 5f48d612b98bffee67b49919
skillid: 2
name: 'PHP'
}
User Collection
{
_id: 5f425bdb311b791670d60de6,
userid: 5f41115fbd904134883ae2d8,
OverView: 'sdsdssdsd',
skills: [1,2], // skill id
Education: [ 5f453e7f53895727f0e39d82, 5f453fb963d4ab181c115982 ],
location: 'India',
}
How can u get skill name from Skill Collection - mongoose
i want result like this
{
_id: 5f425bdb311b791670d60de6,
userid: 5f41115fbd904134883ae2d8,
OverView: 'sdsdssdsd',
skills: ['HTML','PHP'], // skill id
Education: [ 5f453e7f53895727f0e39d82, 5f453fb963d4ab181c115982 ],
location: 'India'
} ```
You can use aggregate(),
$lookup join skill collection, match skills in skill collection
$addFields to add skill name in array format using $reduce
db.user.aggregate([
{
$lookup: {
from: "skill",
localField: "skills",
foreignField: "skillid",
as: "skills"
}
},
{
$addFields: {
skills: {
$reduce: {
input: "$skills",
initialValue: [],
in: {
$concatArrays: [["$$this.name"], "$$value"]
}
}
}
}
}
])
Playground
Another option you can use mongoose Virtual and also look at this
// SKILL SCHEMA
const SkillSchema = mongoose.Schema({
skillid:{type:Number},
name:{type:String},
});
// USER SCHEMA
const UserSchema = mongoose.Schema({
userid: {type: mongoose.Types.ObjectId},
OverView:{type:String},
location:{type:String},
skills:[{Type:Number}]
});
// CREATES VIRTUAL CONNECTION WITH SKILL COLLECTION
UserSchema.virtual('skills', {
ref: 'Skill', // The model to use
localField: 'skills', // Find people where `localField`
foreignField: 'skillid', // is equal to `foreignField`
// If `justOne` is true, 'members' will be a single doc as opposed to
// an array. `justOne` is false by default.
justOne: false,
// you can use other options
// options: { sort: { name: -1 }, limit: 5 }
});
// CREATE MODELS
const User = mongoose.model('User', UserSchema);
const Skill = mongoose.model('Skill', SkillSchema);
// QUERY TO FIND USERS AND POPULATE SKILLS
User.find({}).populate('skills').exec(function(error, user) {
console.log(user);
});
Note: please refer documentation, if you are getting any problem, This is not tested code!
I have created this schema with mongoose
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const QuestionSchema = new mongoose.Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
question:{
type:String
},
name:{
type:String,
},
answerd:[
{
user:{
type:mongoose.Schema.Types.ObjectId,
ref:'users'
}
}
]
})
module.exports = Question = mongoose.model('question',QuestionSchema);
In my global state (REDUX) I have the state
const initialState = {
questions:[],
question:null,
loading:true,
error:{}
};
The element questions store question object which contains the name of the user who made the question, the question itself, and people who have answered.
Some where in a .js file I can get the id of users who have answered by simply
question.buzzed.map(user=> <h1> {user._id} </h1>),
but how is possible to get this user name, I also have a schema for user which have attributes such as name, id, ... etc
You should use the populate method. It is like join in SQL for mongoose, because it connects your answer to the user collection. Based on your code, it could look like this :
Question.find({}).populate("answerd")
or something like this:
Question.find().populate({ path: 'answerd', select: 'username' });
For more information please read the populate documentation
U can use $lookup.
var aggregate = [
{
$unwind: "$answerd"
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "user"
}
}
];
Questions.aggregate(aggregate, function(err, users) {
})
I have two schemas, one collection and another category. A collection has many category and a category can have many collection items.
I'm looking to create a filter later on down the line.
category schema
const mongoose = require('mongoose')
let categorySchema = new mongoose.Schema({
name: {
type: String,
required: true,
unique: true
},
collections: [{ type: mongoose.Types.ObjectId, ref: 'Collection' }]
})
module.exports = mongoose.model('category', categorySchema)
collection schema
const mongoose = require('mongoose')
let collectionSchema = new mongoose.Schema({
...
categories: [{
type: mongoose.Schema.Types.ObjectId, ref: 'categories',
required: true
}]
})
module.exports = mongoose.model('collection', collectionSchema)
truncated this to keep it relevant.
I'm not looking to populate the references yet as I'm only doing the backend for now, so I'm only rendering JSON for now.
I can create multiple category
and I can create a collection with a category as a collection must have at least one category
I can edit a collection and add a new category
However, sometimes I seem to get the following error
I'm not sure why maybe the database hasn't updated in the app, I am using nodemon so I'm not really sure what the issue could be here.
const CollectionSchema = new Schema({
name: String
categoryName: String
});
const CategorySchema = new Schema({
name: String
});
CollectionSchema.virtual('category', {
ref: 'Category', // The model to use
localField: 'name', // Find people where `localField`
foreignField: 'categoryName', // is equal to `foreignField`
// If `justOne` is true, 'members' will be a single doc as opposed to
// an array. `justOne` is false by default.
justOne: false,
options: { sort: { name: -1 }, limit: 5 } //you can add options as well
});
const Category = mongoose.model('Category', CategorySchema);
const Collection = mongoose.model('Collection', CollectionSchema);
Collection.find({}).populate('category').exec(function(error, categories) {
/* `categories.members` is now an array of instances of `Category` */
});
This link has additional Information
I have following schema where the item type might vary, and is mentioned in connections.kind.
var userSchema = new Schema({
name: String,
connections: [{
kind: String,
item: { type: ObjectId, refPath: 'connections.kind' }
}]
});
var organizationSchema = new Schema({ name: String });
I am trying to do a dynamic lookup so that the item object is populated. But this doesn't seem to work.
db.users.aggregate([
{
$lookup:{
from: '$connections.kind',
localField: 'connections.item',
foreignField: '_id',
as: 'items'
}
}
])
I know I can do it with mongoose.populate, but want to know if it is possible with $lookup
As of now, you can't. The from field cannot be an expression and must be a string literal. However, there is an open issue which you can track here that appears to be exactly what you need: https://jira.mongodb.org/browse/SERVER-22497.
I want to get query of the mongoose in nodejs application as describe below out put.
user.js, comment.js and post.js are the model files I used.
user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var userSchema = new Schema({
nick_name:{type:String},
email: {
type: String,
trim: true,
required: '{PATH} is required!',
index: true,
},
},{ collection: 'user'});
var User = mongoose.model('User', userSchema);
module.exports = User;
comment.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var commentSchema = new Schema({
comment: type:String,
user_id:{
type:Schema.Types.ObjectId, ref:'User'
},
is_active :1
},{ collection: 'comment'});
post.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var postSchema = new Schema({
post: type:String,
user_id:{
type:Schema.Types.ObjectId, ref:'User'
},
is_active :1
},{ collection: 'post'});
wants to get out put as follows:
{
"nick_name":"prakash",
"email":"prakash#mailinator.com",
"comments":[
{
"comment":"this is a comment text1",
"is_active":1,
},
{
"comment":"this is a comment text2",
"is_active":1,
}
],
"posts":[
{
"post":"this is a post text1",
"is_active":1,
},
{
"post":"this is a post text2",
"is_active":1,
},
{
"post":"this is a post text3",
"is_active":1,
},
]
}
dependencies
"express" => "version": "4.7.4",
"mongoose" => "version": "4.4.5",
"mongodb" => "version": "2.4.9",
"OS" => "ubuntu 14.04 lts 32bit",
if query is not possible ,please suggests me a proper mongoose plugn.
but I don't want to any changes in user.js file and its userSchema object.
There are no 'joins' in Mongo. But what you would do is change your User Schema to store the ObjectId's of the Comment and Post documents in an array of your User. Then use 'populate' when you need the data with the user.
const userSchema = new Schema({
nick_name:{type:String},
email: {
type: String,
trim: true,
required: '{PATH} is required!',
index: true,
},
comments: [{ type: Schema.Types.ObjectId, ref:'Comment' }],
posts: [{ type: Schema.Types.ObjectId, ref:'Post' }]
}, {timestamps: true});
mongoose.model('User', userSchema);
Your query would then look something like this:
User.find()
.populate('comments posts') // multiple path names in one requires mongoose >= 3.6
.exec(function(err, usersDocuments) {
// handle err
// usersDocuments formatted as desired
});
Mongoose populate docs
It is possible .you should use aggregation.
it should work.
Initiate the variable
var mongoose = require('mongoose');
var userCollection = require('./user');//import user model file
var resources = {
nick_name: "$nick_name",
email: "$email"};
userCollection.aggregate([{
$group: resources
}, {
$lookup: {
from: "Comments", // collection to join
localField: "_id",//field from the input documents
foreignField: "user_id",//field from the documents of the "from" collection
as: "comments"// output array field
}
}, {
$lookup: {
from: "Post", // from collection name
localField: "_id",
foreignField: "user_id",
as: "posts"
}
}],function (error, data) {
return res.json(data);
//handle error case also
});
Of course it is possible, you just have to use populate, let me tell you how:
Import your schemas
var mongoose = require('mongoose');
var userSch = require('userSchema');
var postSch = require('postSchema');
var commSch = require('commentSchema');
Init all the necessary vars
var userModel = mongoose.model('User', userSch);
var postModel = mongoose.model('Post', postSch);
var commModel = mongoose.model('Comment', commSch);
And now, do the query
postModel.find({}).populate('User')
.exec(function (error, result) {
return callback(null, null);
});
commModel.find({}).populate('User')
.exec(function (error, result) {
return callback(null, null);
});
This way you get the user inside of your comment and your post, to get the post and comments inside of your user, you have to do 3 queries, one for the user, one for the comments and one for the post, and mix all together
you can consider using populate virtual with both comments and posts like docs (https://mongoosejs.com/docs/populate.html#populate-virtuals) in model User. And using like this:
User.find({filter}).populates('virtualComments').populate('virtualPosts')
Adding on to the answers above: What if we only want a few specific fields returned for the populated documents? This can be accomplished by passing the usual field name syntax as the second argument to the populate method:
Story.
findOne({ title: /casino royale/i }).
populate('author', 'name'). // only return the Persons name
exec(function (err, story) {
if (err) return handleError(err);
console.log('The author is %s', story.author.name);
// prints "The author is Ian Fleming"
console.log('The authors age is %s', story.author.age);
// prints "The authors age is null"
});
Reference: https://mongoosejs.com/docs/populate.html#field-selection
Use aggregate to do your work in a single query which is almost like a join.
var mongoose = require('mongoose');
var userModel = require('./user');//import user model file
let result = await userModel.aggregate([
{
$match: {
user: mongoose.Types.ObjectId(req.body.user_id),//pass the user id
}
},
{
$lookup: {
from: "comments",//your schema name from mongoDB
localField: "_id", //user_id from user(main) model
foreignField: "user_id",//user_id from user(sub) model
pipeline: [
{
$project:{ //use to select the fileds you want to select
comment:1, //:1 will select the field
is_active :1,
_id:0,//:0 will not select the field
}
}
],
as: "comments",//result var name
}
},
{
$lookup: {
from: "post",//your schema name from mongoDB
localField: "_id", //user_id from user(main) model
foreignField: "user_id",//user_id from user(sub) model
pipeline: [
{
$project:{//use to select the fileds you want to select
post:1,//:1 will select the field
is_active :1,
_id:0,//:0 will not select the field
}
}
],
as: "posts",//result var name
}
},
{
$project:{//use to select the fileds you want to select
nick_name:1,//:1 will select the field
email:1,
_id:0,//:0 will not select the field
comments:1,
posts:1
}
}
])