Using $or in a FindOne query - MongoDB - node.js

UPDATE: Changed if (!result.length) to if (!result) as I'm using .findOne and that seems to be working so far. If you spot anything else that can be improved within the code snippet that would be awesome!
Just starting to learn Node JS and MongoDB (using Mongoose) so excuse me if I'm totally out of context.
Im trying to find a row in my MongoDB with the following query:
exports.findById = function(req, res) {
var id = req.params.id;
Team.findOne({'teamid':id, $or:[{'creator':req.user.id}, {userlist: { $in : [req.user.id]}}]}, function(err, result) {
if (err) console.log(err);
if (!result.length)
res.redirect('/');
else
res.render('team', { team : result.teamid });
});
};
I want to retrieve a row that has the field teamid equal to id as well as to check if the field creator is equal to req.user.id OR if req.user.id is in the userlist field. I am expecting only one result from the above query.
Note that this query works just fine, but I just need to look inside the userlist array:
Team.findOne({'teamid':id, 'creator':req.user.id}, function(err, result) {...
And finally the Team schema
var Team = new Schema({
team_name: { type: String, required: true, trim: true},
teamid: { type: String },
creator: String,
created_at: Date,
userlist: Array
});
Any help to figure out what's the problem is greatly appreciated!

Related

Store value of a subquery - mongoose

What im doing:
When I call getData() the backend server .find() all my data.
My documents:
My test document has an _id a name and stuff fields. The stuff field contains the _id to the data document.
My data document has an _id and a age field
My goal:
When I send the data to the frontend I donĀ“t want the stuff field to appear with the _id, I want it to appear with the age field from the correspondingdata.
What I have:
router.route('/data').get((req, res) => {
Test.find((err, aval) => {
if (err)
console.log(err);
else{
var result = [];
aval.forEach(e => {
var age;
// Get the age, only 1
Data.findById(e.stuff, 'age', function (err, a) {
age = a.age;
});
result.push({name: e.name, age: age});
});
res.json(result);
}
});
});
I find all the test documents then, for each one of them, I find the age and put the result in the array. Finaly I send the result array.
My problem:
The age field on my result array is always undefined, why? Any solutions?
UPDATE 1 - The schemas
The test schema
var TestSchema = new Schema(
{
stuff: {type: Schema.Types.ObjectId, ref: 'Data', required: true},
name: {type: String, required: true}
}
);
The data schema
var DataSchema = new Schema(
{
age: {type: Number, required: true}
}
);
router.route('/data').get((req, res) => {
Test.find({})
.populate('stuff')
.exec((err, aval) => {
if (err) console.log(err);
res.json(aval);
});
});
Mongoose model has a populate property that uses the value in the model attribute definition to get data matching the _id from another model.
It's a scop problem with your code try this out :
Data.findById(e.stuff, 'age', function (err, a) {
result.push({name: e.name, age: a.age});
});
But as a better solution think to use the Aggregation Framework

Mongoose can't search by number field

I have a schema that has an id field that is set to a string. When I use collection.find({id: somenumber}) it returns nothing.
I've tried casting somenumber to a string and to a number. I've tried sending somenumber through as a regex. I've tried putting id in quotes and bare... I have no idea what's going on. Any help and input would be appreciated.
Toys.js
var Schema = mongoose.Schema;
var toySchema = new Schema( {
id: {type: String, required: true, unique: true},
name: {type: String, required: true},
price: Number
} );
My index.js is as such
app.use('/findToy', (req, res) => {
let query = {};
if (req.query.id)
query.id = req.query.id;
console.log(query);
// I've tried using the query variable and explicitly stating the object as below. Neither works.
Toy.find({id: '123'}, (err, toy) => {
if (!err) {
console.log("i'm right here, no errors and nothing in the query");
res.json(toy);
}
else {
console.log(err);
res.json({})
}
})
I know that there is a Toy in my mongoDB instance with id: '123'. If I do Toy.find() it returns:
[{"_id":"5bb7d8e4a620efb05cb407d2","id":"123","name":"Dog chew toy","price":10.99},
{"_id":"5bb7d8f7a620efb05cb407d3","id":"456","name":"Dog pillow","price":25.99}]
I'm at a complete loss, really.
This is what you're looking for. Visit the link for references, but here's a little snippet.
For the sake of this example, let's have a static id, even though Mongo creates a dynamic one [ _id ]. Maybe that what is the problem here. If you already a record in your DB with that id, there's no need for adding it manually, especially not the already existing one. Anyways, Drop your DB collection, and try out this simple example:
// Search by ObjectId
const id = "123";
ToyModel.findById(id, (err, user) => {
if(err) {
// Handle your error here
} else {
// If that 'toy' was found do whatever you want with it :)
}
});
Also, a very similar API is findOne.
ToyModel.findOne({_id: id}, function (err, toy) { ... });

Mongoose NodeJS Schema with array of ref's

I know there is allot's of answers about it but still I didn't quite get the idea.
I have CourseSchema:
const CourseSchema = new Schema({
course_name: String,
course_number: {type: String, unique : true },
enrolledStudents:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student' }]
});
And a StudentSchema:
const StudentSchema = new Schema({
first_name: String,
last_name: String,
enrolledCourses:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'CourseSchema'
}]
});
I want to reffer enrolledStudents at CourseSchema with a student, and enrolledCourses at StudentSchema with a course.
router.post('/addStudentToCourse', function (req, res) {
Course.findById(req.params.courseId, function(err, course){
course.enrolledStudents.push(Student.findById(req.params.studentId, function(error, student){
student.enrolledCourses.push(course).save();
})).save();
});
});
but when posting I get an error:
TypeError: Cannot read property 'enrolledStudents' of null
Ok so after readying Query-populate I did that:
router.post('/addStudentToCourse', function (req, res) {
Course.
findOne({ _id : req.body.courseId }).
populate({
path: 'enrolledStudents'
, match: { _id : req.body.studentId }
}).
exec(function (err, course) {
if (err) return handleError(err);
console.log('The course name is %s', course.course_name);
});
});
And when i'm hitting POST on postman I get on the console:
The course name is intro for cs
but it is loading for ever and later on console I get:
POST /courses/addStudentToCourse - - ms - -
You are missing the populate instruction. For example:
see more about it here
Course.
findOne({ courseId : req.params.courseId }).
populate('enrolledStudents').
exec(function (err, course) {
if (err) return handleError(err);
console.log('The course name is %s', course.name);
});
It is working by using the ref field that "knows" how to populate withput using the push syntax. it is like a foreign key population.
Just call the populate method on the query and an array of documents will be returned in place of the original _ids. you can learn more on the internals of the populate methods in the official docs

Mongoose - REST API - Schema With Query to different model

I'm trying to avoid DB Callback Queries.
Assuming that you have two schemas that looks like so :
1st) User Schema
username : {type: String, unique: true},
age : {type: Number}
2nd) Activity Schema
owner: [{type: Schema.Types.ObjectId, ref: 'User'}],
city: {type: String},
date: {type: Date}
So far so good.
Now lets say you have a route to /user/:id, what you would expect is to get the username and the age, but what if I would also like to return on that route the latest activity?
EDIT: Please note that latest activity isn't a value in the database. it's calculated automatically like activity.find({owner: ObjectId(id)}).sort({date: -1}).limit(1)
What is done right now:
User.findOne({username:req.params.username}).lean().exec(function(err,userDoc)
{
if(err) return errHandler(err);
Activity.findOne({owner:userDoc.username}).sort({date:-1}).exec(function(err,EventDoc){
if(err) return errHandler(err);
userDoc.latest_activity = EventDoc._id;
res.json(userDoc);
res.end();
})
})
The problem with the snippet above is that it is hard to maintain,
What if we want to add more to this API functionality? We would end in a callback of hell of queries unless we implement Q.
We tried to look at Virtual but the issue with that is that you can't
really query inside a mongoose Virtual, since it returns a
race-condition, and you are most likely not get that document on time.
We also tried to look at populate, but we couldn't make it since the documentation on populate is super poor.
QUESTION:
Is there anyway making this more modular?
Is there any way avoiding the DB Query Callback of Hell?
For example is this sort of thing possible?
User.findOne({username:req.params.username}).lean().populate(
{path:'Event',sort:{Date:-1}, limit(1)}
).exec(function(req,res))...
Thanks!
In this case, the best way to handle it would be to add a post save hook to your Activity schema to store the most recent _id in the latest_activity path of your User schema. That way you'd always have access to the id without having to do the extra query.
ActivitySchema.post('save', function(doc) {
UserSchema.findOne({username: doc.owner}).exec(function(err, user){
if (err)
console.log(err); //do something with the error
else if (user) {
user.latest_activity = doc._id;
user.save(function(err) {
if (err)
console.log(err); //do something with the error
});
}
});
});
Inspired by #BrianShambien's answer you could go with the post save, but instead of just storing the _id on the user you store a sub doc of only the last activity. Then when you grab that user it has the last activity right there.
User Model
username : {type: String, unique: true},
age : {type: Number},
last_activity: ActivitySchema
Then you do a post save hook on your ActivitySchema
ActivitySchema.post('save', function(doc) {
UserSchema.findOne({username: doc.owner}).exec(function(err, user){
if (err) errHandler(err);
user.last_activity = doc;
user.save(function(err) {
if (err) errHandler(err);
});
});
});
**********UPDATE************
This is to include the update to the user if they are not an owner, but a particpant of the the activity.
ActivitySchema.post('save', function(doc) {
findAndUpdateUser(doc.owner, doc);
if (doc.participants) {
for (var i in doc.participants) {
findAndUpdateUser(doc.participants[i], doc);
}
}
});
var findAndUpdateUser = function (username, doc) {
UserSchema.findOne({username: username}).exec(function (err, user) {
if (err) errHandler(err);
user.last_activity = doc;
user.save(function (err) {
if (err) errHandler(err);
});
});
});

Mongoose error - elemMatch() must be used after where() when called with these arguments

I have the following schemas:
UserSchema = new Schema({
'username': { type: String, validate: [validatePresenceOf, 'a username is required'], index: { unique: true } },
'hashed_password': String,
'salt': String,
'locations': [LocationSchema]
});
LocationSchema = new Schema({
'lat': Number,
'lng': Number,
'address': { type: String, validate: [validatePresenceOf, 'The address is required in order to add a new location.'] },
'name': String
});
I'm trying to find a particular single location document by it's id. To do this I'm attempting to query the users collection items by location key, which is an array of location documents. My query looks like this:
var query = User.find({"locations.$": 1})
.where()
.elemMatch({locations: {_id : ObjectId('531283690315992f05bcdc98')}})
.exec(function(err, data){
console.log(err);
console.log(data);
});
When it runs I get the following error:
Error: elemMatch() must be used after where() when called with these arguments
What does this mean? I can't seem to find a good explanation.
Forgot to mention, I can get the data I want from the mongo shell by running the following: db.users.find({locations: {$elemMatch : {_id : ObjectId('531283690315992f05bcdc98')}}}, {"locations.$": 1});
You need to provide a path to your where call and reorder things a bit:
User.find()
.where('locations')
.elemMatch({_id : ObjectId('531283690315992f05bcdc98')})
.select({'locations.$': 1})
.exec(function(err, data){
console.log(err);
console.log(data);
});
But you could also simplify it somewhat as you don't need to use $elemMatch here and you can let Mongoose take care of the casting:
User.find()
.where('locations._id', '531283690315992f05bcdc98')
.select({'locations.$': 1})
.exec(function(err, data){
console.log(err);
console.log(data);
});

Resources