Using Mongoose to findbyid and update sibling - node.js

I'm trying to locate a reference to another schema and update a sibling field. Specifically, I'm trying to manipulate the hasResponded field below based on a particular 'survey' ObjectId.
My schema looks like this:
var userSchema = new mongoose.Schema({
// some other stuff
surveys: [{
survey: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Survey'
},
hasResponded: {
type: Boolean,
default: false
}
}]
});

If you have a survey id, simply search for all the users that have this particular id in the array.
Something like that:
Users.find({surveys: { "$elemMatch": { type: <ID> } } });
Then, iterate through the users and their corresponding survey array to find the ones that match the id you gave.
Got to say I would structure this db a little different if this query takes place often.
Make a new Schema - UserSurveys that holds the id of the user and the survey + hasResponded. Like this:
var UserSurveySchema = new mongoose.Schema({
user_id: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
survey_id: {type: mongoose.Schema.Types.ObjectId, ref: 'Survey'}
hasResponded: {type:Boolean, 'default':false}
...
});
You might also want to keep an index on the user and survey ids.
Then, it will be much easier to update the field, requests will take much shorter times.
Hope this helps.

Related

Mongoose references: Should documents cross reference each other

I need some input on schema design with mongodb and mongoose.
I have the following scenario data entities:
Posts
Comments
Users
Requirements:
A user can make a comment on a post.
Comments can be served for a post.
A list of all comments of a user can be retrieved.
I'm thinking to make all 3 of them a seperate schema and connect them by using ref.
I see two approaches here and need input on what might be smarter. Should every entity link to its relations or is it enough if only the comments are a "link" to the other data objects?
export const commentSchema = new Schema<CommentDocument>({
content: { type: String, required: true },
userId: { required: true, type: Schema.Types.ObjectId, ref: "user" },
postId: { type: Schema.Types.ObjectId, ref: "post" },
});
And then for both user and post, should they also link to the comment again or is it enough if the relationship is stored once in comment?
export const userSchema = new Schema<UserDocument>({
// ... all my user data
comments: [{ type: Schema.Types.ObjectId, ref: "comment" }] // <--- Is the referencing on the other documents useful?
});
export const postSchema = new Schema<PostDocument>({
// ... all my post data
comments: [{ type: Schema.Types.ObjectId, ref: "comment" }] // <--- Is the referencing on the other documents useful?
});
Is there any rule of thumb for declaring the references between the documents? Is this generally a good schema design approach?
your commentSchema is good
But no need to store commentsId in userSchema& postSchema.
Because, it will be good for fews comment but for large no of comments it is not a good approach.

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
},
});

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);

Find documents in MongoDB where each value in array of subdocuments is less than param

I have 'Patients' and 'Visits.' I would like to find the all the patients who have not had a visit in the last month. I am using mongoose.
Here are my schemas:
var VisitSchema = new mongoose.Schema({
patient: {type: mongoose.Schema.Types.ObjectId, ref: 'patients'},
created: {
type: Date,
default: Date.now
}
});
var PatientSchema = new mongoose.Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
visits: [{type: mongoose.Schema.Types.ObjectId, ref: 'visits'}]
});
I would like to be able to do something like...
PatientModel.find({"visits.created": {$not: {$gt: monthAgo} }, err, result)
I have thought about maintaining a field in the Patient model to keep track of the latest visit. Would this be a better approach? If so, what would be the best way to maintain such a field, as visits are added, updated, and deleted?
Assuming all patients have had at least one visit you could query the visits collection using an aggregate pipeline (necessary for grouping):
VisitModel.aggregate() // Make an aggregate pipeline
.sort({created: -1}) // Reverse sort by created date (latest to oldest)
.group({_id: '$patient', lastVisit: {$first: '$created'}}) // get latest visit and project new objects
.match({lastVisit: {$gt: monthAgo}}) // only show patients with lastVisit greater than a month ago
.then(successCallback, errorCallback);
This will result in an array of POJOs that represent the IDs for the patient and the date of their last visit.

How can I save multiple items to an array field when creating a new record in MongoDB?

I have the following schema that is used for a "Groups" collection. I want to be able to create this record and push an arbitrary number of "members" to this group when it is first created. I am unable to get the "members" field to populate when I save the record. All other fields are saved without a problem.
var groupSchema = mongoose.Schema({
creator : String,
name : String,
members: [{
type: String,
ref: 'User'
}],
created : {
type: Date,
default: Date.now
}
});
app.post('/create-group', function(req, res) {
//req.body.users = ['12345', '23456', '34567'] for example
var group = new Group({
name : req.body.group_name,
creator : req.user._id,
members: {$push: req.body.users}
});
group.save(function (err, data) {
if (!err) {
return res.json(data);
}
});
});
No results are ever stored in "members", even though all other fields are saved correctly. What am I doing wrong?
EDIT
In your schema when you wrote ref :"User" it means that you have to provide a User schema and not a string or integer. If you just want an array of string you can simply use [String].
According to the doc you have to use an objectID http://mongoosejs.com/docs/populate.html
members: [{ type: Schema.Types.ObjectId, ref: 'User' }]
In the link provided above you will be able to check how to save your group and adding an arbitrary number of members.
members: [{ type: Schema.Types.ObjectId, ref: 'User' }]
As Su4p has pointed out. Don't worry about req.body.users containing strings instead of ObjectIds. mongoose will cast the strings into objectIDs.
There's also another mistake,
members: {$push: req.body.users}
should be
members: req.body.users
$push is an update operator. It's not meant for assigning arrays.

Resources