I am trying to update sub documents with mongoose using the request.body without passing of the sub documents _id. Updating works but mongoose is removing the _id from the sub documents.
Assume the following schemas
// schemas.js
const mongoose = require('mongoose');
const motorSchema = new mongoose.Schema({
type: String,
volume: Number,
});
const carSchema = new mongoose.Schema({
manufacturer: String,
model: String,
motors: [motorSchema],
});
const userSchema = new mongoose.Schema({
username: String,
email: String,
cars: [carSchema]
});
const mongoose = require('mongoose');
// import schemas
const userSchema = require('userSchema');
const carSchema = require('carSchema');
const motorSchema = require('motorSchema');
// create models
const User = mongoose.model("User", userSchema);
const Car = mongoose.model("Car", carSchema);
const Motor = mongoose.model("Motor", motorSchema);
module.exports.updateCar = async function (request, response) {
const condition = {
_id: new mongoose.Types.ObjectId(request.body.userId),
"cars._id": new mongoose.Types.ObjectIt(request.body.carId)
};
// Build object for partial update
const set = {};
for(let field in reqest.body){
set[`cars.$.${field}`] = request.body[field];
}
const update = {
$set = set;
}
User.findOneAndUpdate(condition, update, {new: true, overwrite: false},
(error, user) => {
if(error) {response.send(error)}
response.json(user);
}
}
The problem is that all my _id properties will be overwritten in the motors array. How can I force mongoose as default to not change the _id properties?
If I understand you correctly, the equivalent mongoDB syntax will use arrayFilters, so you can modify your query to use that as well:
For example:
User.findOneAndUpdate(
condition,
{$set: {"cars.$[item].size": "large"}},
{arrayFilters: [{"item._id": new mongoose.Types.ObjectIt(request.body.carId)}]}
)
See how it works on the playground example
According to this method, your code needs the arrayFilters in the options part and the $[<identifier>] in the $set part of the query:
const set = {};
for(let field in reqest.body){
set[`cars.$[item].${field}`] = request.body[field];
}
const update = {
$set = set;
}
User.findOneAndUpdate(
condition,
update,
{
arrayFilters: [{"item._id": new mongoose.Types.ObjectIt(request.body.carId)}],
new: true, overwrite: false
},
(error, user) => {
if(error) {response.send(error)}
response.json(user);
}
Related
I'm trying to push data to a nested array in mongodb. I'm using mongoose as well.
This is just mock code to see if i can get it working:
User model:
import mongoose from "mongoose";
const CoinSchema = new mongoose.Schema({
coinID: { type: String },
});
const CoinsSchema = new mongoose.Schema({
coin: [CoinSchema],
});
const WatchlistSchema = new mongoose.Schema({
watchlistName: { type: String },
coins: [CoinsSchema],
});
const NameSchema = new mongoose.Schema({
firstName: { type: String },
lastName: { type: String },
username: { type: String },
});
const UserSchema = new mongoose.Schema({
name: [NameSchema],
watchlists: [WatchlistSchema],
test: String,
});
const User = mongoose.model("User", UserSchema);
export default User;
route:
fastify.put("/:id", async (request, reply) => {
try {
const { id } = request.params;
const newCoin = request.body;
const updatedUser = await User.findByIdAndUpdate(id, {
$push: { "watchlists[0].coins[0].coin": newCoin },
});
await updatedUser.save();
// console.dir(updatedUser, { depth: null });
reply.status(201).send(updatedUser);
} catch (error) {
reply.status(500).send("could not add to list");
}
});
request.body // "coinID": "test"
I've tried a lot of different ways to push this data but still no luck. I still get 201 status codes in my terminal which indicates something has been pushed to the DB, but when I check nothing new is there.
Whats the correct way to target nested arrays and push data to them?
It's not perfect but you could get the user document, update the user's watchlist, and then save the updated watchlist like so:
fastify.put("/:id", async (request, reply) => {
try {
const { id } = request.params;
const newCoin = request.body;
// get the user
let user = await User.findById(id);
// push the new coin to the User's watchlist
user.watchlists[0].coins[0].coin.push(newCoin);
//update the user document
const updatedUser = await User.findOneAndUpdate({ _id: id },
{
watchlists: user.watchlists,
},
{
new: true,
useFindAndModify: false
}
);
reply.status(201).send(updatedUser);
} catch (error) {
reply.status(500).send("could not add to list");
}
});
I have the following schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ProjectSchema = require('./project.js')
const ClientManagerSchema = new Schema({
name : { type : String, required : true},
project : [ProjectSchema]
});
const ClientManager = mongoose.model('clientManager' , ClientManagerSchema);
module.exports = ClientManager;
Inside the clientmanager schema, there is another as you can see. I want to query the database based on a value inside the ProjectSchema.
I am not sure how to do this but I've tried something like:
const find = () => {
ClientManagers.find({ProjectSchema}).then(e => {
console.log(e);
});
}
however, this gives me an empty array.
Easy-peasy you can refer with dot notation:
const result = await ClientManager.find({ 'project.projectName': 'Foo' })
I have used the below code in my API to remove element from an array
deleteCommentLike: async(req, res) => {
const { error } = createComLikeValidation(req.body);
if (!error) {
const { user_id, comment_id } = req.body;
// const likeModel = new likeSchemaModel({user_id: user_id, post_id: post_id});
await commentlikeSchemaModel
.find({ user_id: user_id, comment_id: comment_id })
.remove();
let commenttData = await commentSchemaModel.findById(comment_id);
console.log(commenttData.usersLiked);
commenttData.likes = --commenttData.likes;
commenttData.usersLiked.remove(user_id);
await commenttData.save();
res.status(200).json({ error: false, data: "done" });
} else {
let detail = error.details[0].message;
res.send({ error: true, data: detail });
}
},
In here this one line is not working: commenttData.usersLiked.remove(user_id);. It doesn't give any error but the user_id is not removed from my database.
"use strict";
const mongoose = require('mongoose');
const Joi = require('joi');
var commentSchema = mongoose.Schema({
//other data
usersLiked: [{
type: mongoose.Types.ObjectId,
default: []
}],
//other data
}
var commentSchemaModel = mongoose.model('comments', commentSchema);
module.exports = {
commentSchemaModel,
}
In my mongodb it looks like below
I have alredy tried using it as commenttData.usersLiked.remove(mongoose.Types.ObjectId('user_id'));
but the result is same.
What can be the reason for this and how could I remove the value from the array ?
You should use an update operation:
commenttData.update({_id: mongoose.Types.ObjectId("5f099..")}, {$set: {usersLiked: yourUpdatedUsersLikedArray}})
The error you expect from remove() is missing as you trigger a js noop which is just ignored by the compiler.
Mongoose does not implement the attribute update operation the way you use it.
i tried adding these mongoose plugins mongoose-auto-increment and mongoose-sequence to strapi in config/functions/mongoose.js.. the counter collections are getting created.. but the sequence counts are not getting updated.. Is there a way to get these plugins working or is there a way to implement it myself?
// config/functions/mongoose.js
var autoIncrement = require('mongoose-auto-increment');
module.exports = (mongoose, connection) => {
autoIncrement.initialize(mongoose.connection);
var movieSchema = mongoose.Schema({
title: String
}, { collection : 'Tests' });
movieSchema.plugin(autoIncrement.plugin, { model: 'Test', field: 'movieId', startAt: 1 });
};
In a similar situation I solved it using a new Schema as a Counter for the Ids.
Here is the Counter Schema (models/counter.js):
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CounterSchema = Schema({
_id: {
type: String,
required: true
},
sequence: {
type: Number,
default: 0
}
}, {
collection: 'counters'
});
// export the counter model below and call this method to create the first entry in the counter's table
CounterSchema.statics.createFirstIdForMovie = async () => {
const newCounter = new counter({
_id: "movieid",
sequence: 0
});
newCounter.save();
}
And the Movie Schema would be (models/movie.js):
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const MovieSchema = new Schema({
...,
identifier: {
type: Number,
required: false,
unique: true
},
...
});
MovieSchema.pre('save', async function (next) {
// get the next value for the identifier of 'movieid'
if (this.identifier) {
// just editing, don't need to increment or set a new identifier
return;
}
let c = await counter.findById('movieid');
if (!c) {
c = await counter.createFirstIdForMovie();
}
c.sequence += 1;
await c.save();
this.identifier = c.sequence;
});
Hope it helps!
My workaround was to use a single-type as counter.
Each time I need to use and increment the counter I get the counter single-type and use the built-in createOrUpdate service to get the number.
const counters = await strapi.services.counters.find();
const updatedCounter = await strapi.services.counters.createOrUpdate({
ainumber : counters.ainumber + 1,
});
I know single-types are not ment for this, but it works and is easy to handle.
Hope helps someone
I am trying to insert a subdocument using .create() and also query said document using .id(). I've been following the guide here
I am getting the error: object has no method 'id()' or 'create()'
The following is my code:
/db/schemas/AnnouncementsSchema.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var postSchema = new Schema({
title: String,
dateCreated: String,
dateEdited: { type: Date, default: Date.now() },
summary: String,
body: String
});
var announcementsSchema= new Schema({
categoryName: String,
posts: [postSchema]
});
announcementsSchema.methods.Update = function (callback) {
console.log('Updating object: ' + this);
this.save(function (err, object) {
callback(err, object);
});
}
var announcements = mongoose.model('announcements', announcementsSchema);
var post = mongoose.model('post', postSchema);
module.exports = {
announcements: announcements,
post: post
};
/routes/Announcements.js
var mongoose = require('mongoose');
var announcementsSchema = require('../../db/schemas/AnnouncementsSchema.js');
exports.InsertPost = function (req, res) {
var announcements = announcementsSchema.announcements;
var post = announcementsSchema.post;
var categoryToEdit = req.body.categoryToEdit;
var newPost = req.body.newPost;
announcements.GetById(categoryToEdit._id, function (err, announcment) {
var postToAdd = new post(newPost);
announcment.posts.create(postToAdd);
announcment.Update(function (err, object) {
res.send({ err: err, data: object});
});
});
}
I have the .save method wrapped so I can add extra functionality if needed. I crashes when it calls .create(). The same is true if I am trying to remove a post as well. Here is the code for that:
exports.DeletePost = function (req, res) {
var announcements = announcementsSchema.announcements;
var categoryId = req.body.categoryId;
var postId = req.body.postId;
announcements.findById(categoryId, function (err, object) {
var postToDelete = object.posts.id(postId);
console.log(postToDelete);
res.end();
});
}
Either way they both crash and google is pretty slim on answers. Most people have given a of different ways to expose the schemas and models, and what I have above is pretty much a sum of what they suggested. Any ideas? Thanks!
I found the answer after toying with this forever. It was simple. The order in which you declare your schema matters. So I had to declare my subdocument schema before I declare my parent schema. Such as:
var announcementsSchema= new Schema({
categoryName: String,
posts: [postSchema]
});
var postSchema = new Schema({
title: String,
dateCreated: String,
dateEdited: { type: Date, default: Date.now() },
summary: String,
body: String
});
This worked! I am now able to keep all the rest of the code the same, but now the methods exist!
Even if I recently picked up Express.js, I will do my best in trying to provide a useful answer. Hope it helps!
If you want to access the id of a particular post, it is stored in the ._id property, not id. Moreover, I believe that you first have to loop over object.posts since it is an array of posts:
exports.DeletePost = function (req, res) {
var announcements = announcementsSchema.announcements;
var announcementId = req.body.announcementId;
var postId = req.body.postId; // This is the _id property of an element contained within the posts array
var postToDelete = {};
announcements.findById(announcementId, function (err, object) {
// Get the posts array
var posts = object.posts;
// Loop over it until you find the post with the id that you stored in postId
for (var i = 0; i < posts.length; i ++) {
if (posts[i]._id == postId) {
postToDelete = posts[i];
}
}
console.log(postToDelete);
res.end();
});
}
As for your InsertPost function, you should also take into account the fact that posts is an array. Hence, you could simply push the new post into that array and save it accordingly:
exports.InsertPost = function (req, res) {
var announcements = announcementsSchema.announcements;
var post = announcementsSchema.post;
var announcementId = req.body.announcementId;
var newPost = req.body.newPost;
announcements.findById(announcementId, function (err, announcment) {
var postToAdd = new post(newPost);
announcment.posts.push(postToAdd);
announcment.save(function(err) {
if (err) {
res.send(500, { error: err.stack });
}
console.log('Success');
res.end();
});
});
}