mongoose create function in not function error(sub document) - javascript

schema
const UserSchema = new mongoose.Schema({
...,
suported: [{name:String, id:String}],
suporting: [{name:String, id:String}]
},
{ timestamps: true });
Query
const requester = await User.findOne({ _id })
const suporter = await User.findOne({ _id: _idSuporter })
// Result ok
requester.suported.create(data); // causing error
suporter.suporting.create(data);
Error message: requester.suported.create is not a function.
Edited
Links to where you can see what I am expecting
https://mongoosejs.com/docs/subdocs.html#adding-subdocs-to-arrays
https://attacomsian.com/blog/mongoose-subdocuments

The error is happening because it is not possible to call the create function on the "supported" attribute of the User object. What you can do is create a static function that takes the data as a parameter and does something when the function is called, like this:
userSchema.statics.createSupported = function(data: any) {
// do something here..
}
userSchema.statics.createSupporting = function(data: any) {
// do something here..
}
And when you call the query:
const requester = await User.findOne({ _id })
const supporter = await User.findOne({ _id: _idSuporter })
// Result ok
User.createSupported(date)
User.createSupporting(data)

Related

How to push data with Mongoose to a nested array in MongoDB

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

mongoose how to send transaction multiple collections

Side-Note I connect to DB with the following code:
const mongoose = require('mongoose');
const connectDB = (url) => {
return mongoose.connect(url);
}
Problem Description:
I have two different Collections. Both Operations, findByIdAndUpdate and create must run as an atomic operation. This should be possible with mongoose Transactions.
const registerCustomer = async (req, res) => {
await CustomerRegistrationCode.findByIdAndUpdate(req.body._id, { used: true });
const customer = await Customer.create({firstName: req.body.firstName});
}
What I tried:
const registerCustomer = async (req, res) => {
const session = await mongoose.startSession();
await session.startTransaction();
try {
await CustomerRegistrationCode.findByIdAndUpdate(req.body._id, { used: true }); //updates even though
const customer = await Customer.create({ firstName: req.body.firstName });// this line will throw error
await session.commitTransaction();
session.endSession();
} catch (error) {
console.error('abort transaction');
await session.abortTransaction();
session.endSession();
throw error;
}
}
Problem The CustomerRegistrationCode Collection gets updated even though the Customer.create method throws an error. How can this be solved?
New approach to understand MongoDB Transactions fails, but this is official code from https://mongoosejs.com/docs/transactions.html
const mongoose = require('mongoose');
const debugMongo = async () => {
const db = await mongoose.createConnection("mongodb://localhost:27017/mongotest");
const Customer = db.model('Customer', new mongoose.Schema({ name: String }));
const session = await db.startSession();
session.startTransaction();
await Customer.create([{ name: 'Test' }], { session: session }); //(node:20416) UnhandledPromiseRejectionWarning: MongoServerError: Transaction numbers are only allowed on a replica set member or mongos
let doc = await Customer.findOne({ name: 'Test' });
assert.ok(!doc);
doc = await Customer.findOne({ name: 'Test' }).session(session);
assert.ok(doc);
await session.commitTransaction();
doc = await Customer.findOne({ name: 'Test' });
assert.ok(doc);
session.endSession();
}
debugMongo();
At Customer.create an error gets thrown and i don't know why. Does somebody have an minimal working example?
You are using the transaction in a wrong way, that is why it does not work.
You need to pass the session object to your operations.
const registerCustomer = async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
await CustomerRegistrationCode.findByIdAndUpdate(req.body._id, { used: true }, { session });
const customer = await Customer.create({ firstName: req.body.firstName }, { session });
await session.commitTransaction();
} catch (error) {
console.error('abort transaction');
await session.abortTransaction();
} finally {
session.endSession();
}
}
Also, I have refactored your code a bit.
You can read more about transactions here

Issue with checking and adding document item to collection in MongoDB

I'm new in Nodejs and I'm trying to create Video with hashtag. There are hashtags already storaged in DB, and hashtag that user will create (which will be added when submit video).
For example, I add more than 2 hashtags, the code below works for 2 cases:
If there is no hashtag storaged in DB, it created and add all hashtags to video successfully
If hashtags is already in DB, it added video successfully
But when there are some hashtags already in DB and the other is not. It add only few hashtags to video, not all hashtags added. I don't know why. I want to fix this case.
I have 2 schemas like this:
// Video schema
const videoSchema = new mongoose.Schema({
url: {
type: String
},
hashtag: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Hashtag'
}
})
and
// Hashtag schema
const hashtagSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
}
})
User will POST like this on server. On this example, Tag-In-DB-Already is in DB already and New-Tag is typed by user
{
"url": "https://youtube.com/JvDAQD4",
"hashtag": ["Tag-In-DB-Already", "New-Tag"]
}
videoObj like this
const videoObj = new Video({
url: data.URL,
hashtag: []
})
The checking code like this, I need to push ObjectId (of hashtag) to hashtagArr above. I'm checking for each hashtag, if hashtag not in DB, it will add to DB and then push to array. If hashtag is in DB, it also added to array. I want all hashtags that user submited will be added.
export const addHashtagToVideo = async (hashtagArr, videoObj) => {
await hashtagArr.forEach(hashtagName => { // for each hashtag check
Hashtag.findOne({ name: hashtagName.toLowerCase() }, (err, resp) => {
if (err) return
if (!resp) { // if there is no hashtag in DB
addNewHashtagToDB(hashtagName) // run add new hashtag function
.then(hashtagId => { // newHashtag._id returned from below function
videoObj.hashtag.push({ _id: hashtagId })
})
} else {
videoObj.hashtag.push({ _id: resp._id }) // if found in DB, also pushed to video
}
})
})
}
export const addNewHashtagToDB = async (hashtagName) => {
const newHashtag = await new Hashtag({
name: hashtagName.toLowerCase(),
})
newHashtag.save()
return newHashtag._id
}
Thank you for help
you need to know what is function return...
export const addHashtagToVideo = async (hashtagArr, videoObj) => {
const waitAllDone = hashtagArr.map(async tag => { // tag in hashtagArr
const doc = await addNewHashtagToDB(tag) // find it or create it
return doc.id
})
const ary = await Promise.all(waitAllDone) // [id1, id2]
videoObj.hashtag = ary
// return videoObj
}
export const addNewHashtagToDB = async tag => {
const name = tag.toLowerCase() // whatever tag is, fix it
let doc = await Hashtag.findOne({ name }).exec() // try to find it
if (!doc) {
// not exist
doc = new Hashtag({ name }) // create new doc
await doc.save()
}
return doc
}
another version
export const addHashtagToVideo = async (hashtagArr, videoObj) => {
const waitAllDone = hashtagArr.map(addNewHashtagToDB) // amazing
const ary = await Promise.all(waitAllDone) // [id1, id2]
videoObj.hashtag = ary // replace it to prevent duplicat
// return videoObj
}
export const addNewHashtagToDB = async tag => {
const name = tag.toLowerCase() // whatever tag is, fix it
let doc = await Hashtag.findOne({ name }).exec() // try to find it
if (!doc) {
// not exist
doc = new Hashtag({ name }) // create new doc
await doc.save()
}
return doc.id // return it here
}

remove array element with remove() is not working?

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.

MongoDB/Express: Why does Array.includes return false instead of true?

I'm working on an tiny app that allows user to participate in polls, but I'm having problems checking if the current user has already voted in the poll. Everything else works fine, save for the IIFE that checks for said condition, as seen in the code snippet included. Indeed, I'm getting false as opposed to true with the data I have i.e. I already seeded the DB with sample data, including a random poll that contains the array of IDs for users who've already voted. I tried testing one ID against said array, which returns false as opposed to the expected true. What gives?
Below are the relevant snippets.
Model
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const ChoiceSchema = new Schema({
name: { type: String },
votes: { type: Number }
});
const PollSchema = new Schema({
title: { type: String },
category: { type: String },
choices: [ChoiceSchema],
addedBy: { type: Schema.Types.ObjectId, ref: 'User' },
votedBy: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
const Poll = mongoose.model('Poll', PollSchema);
export default Poll;
Controllers
import Poll from '../models/poll';
export default {
fetchAllPolls: async (req, res) => {
/*...*/
},
fetchSpecificPoll: async (req, res) => {
/*...*/
},
voteInPoll: async (req, res) => {
const { category, pollId } = req.params;
const { name, choiceId, voterId } = req.body;
try {
const poll = await Poll.findById(pollId);
const choice = await poll.choices.id(choiceId);
const votedChoice = {
name,
votes: choice.votes + 1,
};
// Check if user has already voted in poll
const hasVoted = ((votersIds, id) => votersIds.includes(id))(
poll.votedBy,
voterId
);
if (!voterId) {
res
.status(400)
.json({ message: 'Sorry, you must be logged in to vote' });
} else if (voterId && hasVoted) {
res.status(400).json({ message: 'Sorry, you can only vote once' });
} else {
await choice.set(votedChoice);
await poll.votedBy.push(voterId);
poll.save();
res.status(200).json({
message: 'Thank you for voting. Find other polls at: ',
poll,
});
}
} catch (error) {
res.status(404).json({ error: error.message });
}
},
createNewPoll: async (req, res) => {
/*...*/
},
};
I think you are trying to compare ObjectId with String representing the mongo id.
This should work:
const hasVoted = ((votersIds, id) => votersIds.findIndex((item) => item.toString() === id) !== -1)(
poll.votedBy,
voterId
);
or
const hasVoted = ((votersIds, id) => votersIds.findIndex((item) => item.equals(new ObjectId(id))) !== -1)(
poll.votedBy,
voterId
);
EDIT:
As #JasonCust suggested, a simpler form should be:
const hasVoted = poll.votedBy.some(voter => voter.equals(voterId));
It is more than likely that poll.votedBy is not an array of ID strings. If you are using it as a reference field then it is an array of BSON objects which would fail using includes because it uses the sameValueZero algorithm to compare values. If that is true then you could either convert all of the IDs to strings first or you could use find and the equals methods to find a match.
Update: showing actual code example
Also, some would provide an easier method for returning a boolean value.
const hasVoted = poll.votedBy.some((voter) => voter.equals(voterId));

Categories