Mongoose updating document with findOne() matches only wrong results - node.js

I have a collection of fixtures that 'belong' to a competitor and look something like this:
{
"_id": {
"$oid": "59dbdf6dbe628df3a80419bc"
},
"timeOfEntrance": "1507581805813",
"timeOfFinish": null,
"competitor": {
"$oid": "59db5a3f3d6119e69911a61a"
},
"__v": 0
}
My goal is to update only the document's timeOfFinish by sending a PUT request with competitor's ID as a param in the url and timestamp in the body. However I'm struggling to compose the update query.
The following is what I have currently, it never finds the right match and to my surprise it's always updating the wrong document.
fixtureController.put = (req, res) => {
const competitorId = req.params.id;
const timeOfFinish = req.body.timeOfFinish;
Fixture.findOne({'competitor.$oid': competitorId}, (err, fixture) => {
fixture.set({ timeOfFinish });
fixture.save()
.then(updatedFixture => {
return res.status(200).json({
success: true,
fixture: updatedFixture
})
})
.catch(err => {
return res.status(500).json({
message: err
});
});
});
};
Bit of a beginner in the MongoDB field, will appreciate your comments and solutions.

Turns out there was no need to specify the exact field in the match parameter. Mongoose matches by the id field automatically.
Fixture.findOne({'competitor': competitorId}, (err, fixture) => {
...
});

Related

ExpressJS: Sequelize method update need to show updated data as result not num of row updated

I've API using ExpressJS and ORM Sequelize. I'm trying to do update using method update() from Sequelize. By default, it method will return number of row updated. But I want the result is the new data that just updated to show as response.
Here is my code:
update: async function (req, res, next) {
var current_address_id = req.body.current_address_id,
address = req.body.address
PersonalInfo.findOne({
where: {
id: req.params.id
}
}).then(personal => {
Address.create(
{
address: address,
}
).then( resAddress => {
PersonalInfo.update(
{
current_address_id: resAddress.dataValues.id
},
{
where: {
id: req.params.id
}
}
).then(resultUpdate => {
console.log(resultUpdate);
responseUtil.success(res, resultUpdate);
}).catch(err => {
responseUtil.fail(res, err);
})
})
})
}
When I do console.log(resultUpdate); It give me [1] as the num of row updated. What I need is the data of PersonalInfo that just updated.
After consulting the documentation for what returns from the update operation for Sequelize, it returns the following:
an array with one or two elements. The first element is always the
number of affected rows, while the second element is the actual
affected rows (only supported in postgres with options.returning
true.)
So, as you can see from your code, your update is returning an array with the number of affected rows, which is what the documentation says it will do. You can't change what the library itself will return.
You do have access to the values you are updating earlier on in your function, and if you really want, you could do a find on the record you are updating, which will return your model: http://docs.sequelizejs.com/class/lib/model.js~Model.html#static-method-findOne
You only need to add returning: true at your query. Your code would be like
update: async function (req, res, next) {
var current_address_id = req.body.current_address_id,
address = req.body.address
PersonalInfo.findOne({
where: {
id: req.params.id
}
}).then(personal => {
Address.create(
{
address: address,
}
).then( resAddress => {
PersonalInfo.update(
{
current_address_id: resAddress.dataValues.id
},
{
where: {
id: req.params.id
},
returning: true
}
).then(resultUpdate => {
console.log(resultUpdate);
responseUtil.success(res, resultUpdate);
}).catch(err => {
responseUtil.fail(res, err);
})
})
})
}

Mongodb/mongoose omit a field in response [duplicate]

I have a NodeJS application with Mongoose ODM(Mongoose 3.3.1). I want to retrieve all fields except 1 from my collection.For Example: I have a collection Product Which have 6 fields,I want to select all except a field "Image" . I used "exclude" method, but got error..
This was my code.
var Query = models.Product.find();
Query.exclude('title Image');
if (req.params.id) {
Query.where('_id', req.params.id);
}
Query.exec(function (err, product) {
if (!err) {
return res.send({ 'statusCode': 200, 'statusText': 'OK', 'data': product });
} else {
return res.send(500);
}
});
But this returns error
Express
500 TypeError: Object #<Query> has no method 'exclude'.........
Also I tried, var Query = models.Product.find().exclude('title','Image'); and var Query = models.Product.find({}).exclude('title','Image'); But getting the same error. How to exclude one/(two) particular fields from a collection in Mongoose.
Use query.select for field selection in the current (3.x) Mongoose builds.
Prefix a field name you want to exclude with a -; so in your case:
Query.select('-Image');
Quick aside: in JavaScript, variables starting with a capital letter should be reserved for constructor functions. So consider renaming Query as query in your code.
I don't know where you read about that .exclude function, because I can't find it in any documentation.
But you can exclude fields by using the second parameter of the find method.
Here is an example from the official documentation:
db.inventory.find( { type: 'food' }, { type:0 } )
This operation returns all documents where the value of the type field is food, but does not include the type field in the output.
Model.findOne({ _id: Your Id}, { password: 0, name: 0 }, function(err, user){
// put your code
});
this code worked in my project. Thanks!! have a nice day.
You could do this
const products = await Product.find().select(['-image'])
I am use this with async await
async (req, res) => {
try {
await User.findById(req.user,'name email',(err, user) => {
if(err || !user){
return res.status(404)
} else {
return res.status(200).json({
user,
});
}
});
} catch (error) {
console.log(error);
}
In the updated version of Mongoose you can use it in this way as below to get selected fields.
user.findById({_id: req.body.id}, 'username phno address').then(response => {
res.status(200).json({
result: true,
details: response
});
}).catch(err => {
res.status(500).json({ result: false });
});
I'm working on a feature. I store a userId array name "collectedUser" than who is collected the project. And I just want to return a field "isCollected" instead of "collectedUsers". So select is not what I want. But I got this solution.
This is after I get projects from database, I add "isCollected".
for (const item of projects) {
item.set("isCollected", item.collectedUsers.includes(userId), {
strict: false,
})
}
And this is in Decorator #Schema
#Schema({
timestamps: true,
toObject: {
virtuals: true,
versionKey: false,
transform: (doc, ret, options): Partial<Project> => {
return {
...ret,
projectManagers: undefined,
projectMembers: undefined,
collectedUsers: undefined
}
}
}
})
Finally in my controller
projects = projects.map(i => i.toObject())
It's a strange tricks that set undefined, but it really work.
Btw I'm using nestjs.
You can do it like this
const products = await Product.find().select({
"image": 0
});
For anyone looking for a way to always omit a field - more like a global option rather than doing so in the query e.g. a password field, using a getter that returns undefined also works
{
password: {
type: String,
required: true,
get: () => undefined,
},
}
NB: Getters must be enabled with option { toObject: { getters:true } }
you can exclude the field from the schema definition
by adding the attribute
excludedField : {
...
select: false,
...
}
whenever you want to add it to your result,
add this to your find()
find().select('+excludedFiled')

mongoose to display ref object in json

I'm new to mongoose, using the following controller:
const Creport = require('../models/creport.model.js');
exports.save = (req, res) => {
const creport = new Creport({
curso_id: req.body.curso_id,
nombre: req.body.nombre,
....
});
creport.save()
.then(data => {
res.send(data);
}).catch(err => {
res.status(500).send({
message: err.message
});
});
};
in the creport.model.js:
curso_id: {
type: Schema.Types.ObjectId,
ref: 'Curso'
},
this will create a json file like:
{"curso_id":"5b5a14e8ej1a18ac0b5e5433","nombre":"el nombre",....}
while I'm looking for:
{"curso_id":"curso No. 1","nombre":"el nombre",....}
EDIT:
using populate:
exports.findAll = (req, res) => {
Creport.find().populate('curso_id')
.then(creports => {
res.send(creports);
}).catch(err => {
res.status(500).send({
message: err.message
});
});
};
will output:
[{"_id":"5b5ce554967f6a36f0c84fe6","curso_id":{"_id":"5b5a14e8ej1a18ac0b5e5433","name":"curso No. 1"},"nombre":"el nombre"....}]
For return the data in this format, you need to use the aggregate method.
In my tests, I created one course and one Creport and after I executed this aggregate:
CreportModel.aggregate([
{"$match":{_id:creport._id}},
{"$lookup":{
from:"cursos",
localField:"curso_id",
foreignField:"_id",
as:"cursos"
}
},
{"$project":{"curso_id": {"$arrayElemAt":["$cursos.name",0]},"nombre": "$nombre","_id":0}}
])
.then(result=>{
console.log(result)
})
Result:
If you want to add more fields in the result, you need to change the $project phase.
e.g
{"$project":{"curso_id": {"_id":1,"$arrayElemAt":["$cursos.name",0]},"nombre": "$nombre"}}
0 : means that will remove the field in the return
1 : means that will show the field in the return
Mongoose Documentation: Aggregate Lookup
You could use populate method, if you read the mongoose documentation you will find there's very easy way to apply.
http://mongoosejs.com/docs/populate.html
curso_id is an ObjectId. The returned json confirms this.
The semantic is correct.
You should probably add a
curso_number: {
type: Schema.Types.String
},

Deleting a single item from a database with mongodb

{
"_id": {
"$oid": "5a2de0a00d6baa43e8b925d0"
},
"name": "test",
"playList": [
{
"url": "https://p.scdn.co/mp3-preview/8aa799e60164f8a1fb311188d9d85ef65d7782c6?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "Kenny G",
"songName": "My Heart Will Go On (Love Theme from \"Titanic\")",
"_id": {
"$oid": "5a2de0ad0d6baa43e8b925d1"
}
},
{
"url": "https://p.scdn.co/mp3-preview/7c49854f18e6dfda6cd97ab5e8bc139d7ca82b7c?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "PRODUCE 101",
"songName": "PICK ME",
"_id": {
"$oid": "5a2de13b0d6baa43e8b925d2"
}
}
],
"__v": 0
}
I have a database called channels where each channels contain a playList as shown below. I want to delete a single item when a button is clicked. I can handle the onClick event part, but I am not sure how to implement the routes part.
I know that I start by doing something like
router.delete(''/channels/:id', function(req, res){
something here...
})
but how can I access a particular item (probably with a unique id?) and delete it from the DB?
EDIT
By using the GET below
router.get('/channels/:id',
isLoggedIn,
function(req, res) {
channel.findOne({'name':req.params.id},function(err,channeldata){
if(err || channeldata === null){
res.status(404).send({
message: 'Channel Not Found',
data: []
})
}
else {
res.status(200).json({
message: "channel to "+req.params.id+"success",
data:channeldata
})
}
})
});
I get the data for a single channel in my DB.
But since I am new to this stuff, I am not sure how to access each item of the playList and delete a single data.
EDIT2
var mongoose = require('mongoose');
var ChannelSchema = new mongoose.Schema({
name: {type:String,required:true},
playList: [{
songName: { type : String },
artist: { type : String },
url: { type : String }
}]
})
module.exports = mongoose.model('Channel',ChannelSchema);
You can try the following snippet that contains the DELETE (part of CRUD) endpoint for your resource collection (i.e. the channels):
router.delete('/channels/playlist/song', isLoggedIn, (req, res) => {
const channel_id = req.query.channelId;
const song_id = req.query.songId;
// the following query deletes a song form a playlist of a certain channel
channel.update({_id: ObjectId(channel_id)},{$pull:{playList:{_id:ObjectId(song_id)}}})
.exec()
.then(result => {
// for checking if document was found and deleted
// mongodb actually returns special object `result`
// which has its own certain fields
res.status(200).send({
status: "success",
message: result
});
})
.catch(error => {
// here we see if we had any problem with server or db itself
console.log(error)
res.status(500).send({
success: false,
message: "Something went wrong with DELETE /channels/:id"
})
})
});
I assume that you know what ObjectId() function does
if you do not have it declared, declare the following comment
in the beginning of the file (where you require everything)
const mongoose = require('mongoose'); // you must have this
const ObjectId = mongoose.Types.ObjectId; // gets the function
Let me know if this helps, or if you do not understand something - I will make an edit so that you get it.

Mongoose findOneAndUpdate not returning raw Mongo response

I'm trying to determine whether the document was found in my findOneAndUpdate operation. If it wasn't, I return a 404 not found error. I figured I'd use the "passRawValue" option Mongoose provides, and check for a raw value- if raw is undefined, I know the doc was not found.
However regardless whether the doc is found or not, my raw value is undefined. I've verified that the doc I'm trying to update is in the DB at the time of the query by running a simple "findOne" query just before the update. Where am I going wrong?
let updateItemById = (userId, itemId, params, cb) => {
//this finds and prints the document I'm testing with -- I know its in the DB
// Item.findOne({ "_id" : itemId, ownerId: userId }, (err, doc) => {
// if (doc) {
// console.log("This is the doc: ", doc);
// }
// });
Item.findOneAndUpdate({ "_id" : itemId, ownerId: userId },
{
$set: {
params
}
}, { runValidators: 1, passRawResult: true}, (err, doc, raw) => {
if (err) {
//winston.log
return cb(ErrorTypes.serverError(), false);
}
else if (raw) {
return cb(null, true);
}
else {
return cb(ErrorTypes.notFound(), false);
}
});
}
Hi I have a hunch that you are passing params that has a property that doesn't exist in the document in the database. In such case, nothing was modified, hence db doesn't return raw as the third parameter.
Update:
So I did some few tests of my own, and I see that if we pass option strict:false then your code should work as intended. So your options section will look like this
{ runValidators: 1, passRawResult: true, strict: false, new:true}
Explanation:
Mongoose has a strict option which by default is true. It makes sure that the values being updated is defined in the schema. So when we provide the option strict as false, as described in the [mongoose documentation] (http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate) we can achieve updating document with new field.
I also added new:true option which will return you the updated document.
P.S.
I would like to add though, since our upsert is false, which means it won't insert new document when a match is not found, it will return null for doc, and you can simple check on that. Why are you checking on raw? Is there any particular reason for this?
I know it's been awhile but I had the same problem here so I decided to leave an answer that maybe can help other people.
I was able to check whether the findOneAndUpdate() method found a document or not by checking if the doc parameter was null on the callback function:
async Update(request: Request, response: Response) {
const productId = request.params.id;
const query = { _id: productId };
const options = { new: true };
try {
await Product.findOneAndUpdate(query, request.body, options, (err, doc, res) => {
if (doc === null)
return response.status(404).send({
error: 'Product not found'
})
return response.status(204).send();
});
}
catch (err) {
return response.status(400).send({
error: 'Product update failed'
});
}
}

Resources