Updating a Subdocument in MongoDB - javascript

I am trying to create a database for my Giveaway Bot. It consist of 2 collections, Main (holding settings) and Giveaway which is nested in under the Main collection. I can create my giveaway's without problems. However I want to add some data later on using findOneAndUpdate.
Running the code below I always get this error: Updating the path 'giveaways.duration' would create a conflict at 'giveaways'. Can anyone help solving this issue ?
schema.js
const giveawaySchema = new mongoose.Schema({
_id: String,
destination: String,
duration: String,
winners: String,
price: String,
})
const mainSchema = new mongoose.Schema({
_id: String,
log_channel_id: String,
admin_roles: [],
giveaways: [giveawaySchema],
const Main = mongoose.model("mainSchema", mainSchema);
const Giveaway = mongoose.model("giveawaySchema", giveawaySchema);
module.exports = { Main, Giveaway }
});
Part of my code used for updating:
const mongoose = require("mongoose")
const {Main, Giveaway} = require("../models/schema.js")
const newestGiveaway = await Main.findOneAndUpdate(
{
_id: guildId,
'giveaways._id': giveaway_id,
},
{
"$set":{
"giveaways.duration": "3d",
"giveaways.winners": "20",
"giveaways.price": "Price to Win",
},
},
{
upsert: true,
}
Thank you for your help :)
A small side question. I have fetched the Main document (the parent) before already can I make my search cheaper/ more efficent by only searching through this instead of running the findOneandUpdate method on the whole database ?
Edit 1:
I found that it is neccesary to use the $ operator and have updated my code. However I still get the same error:
{
$set:{
"giveaways.$.duration": "3d",
"giveaways.$.winners": "20,
"giveaways.$.price": "Price to Win",
},
},
Edit 2:
Just to clarify, the creation and nesting of the giveawaySchemas works but I am not able to update the nested document by using the code above.
My child component is already created by using the code below. I now want to update this child (newGiveaway with _id of 1)
const currentGuild = await Main.findOne({_id: guildId})
const newGiveaway = await Giveaway.create({
_id: 1,
destination: 12345678,
});
currentGuild.giveaways.push(newGiveaway)

You can change your schema declaration to use a ref to the giveawaySchema:
const giveawaySchema = new mongoose.Schema({
_id: String,
destination: String,
duration: String,
winners: String,
price: String,
})
const mainSchema = new mongoose.Schema({
_id: String,
log_channel_id: String,
admin_roles: [],
giveaways: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'giveawaySchema'
}],
const Main = mongoose.model("mainSchema", mainSchema);
const Giveaway = mongoose.model("giveawaySchema", giveawaySchema);
module.exports = { Main, Giveaway }
Then, you will just need to update your giveaways directy:
const mongoose = require('mongoose');
const { Giveaway } = require('../models/schema.js');
const newestGiveaway = await Main.findByIdAndUpdate(
giveaway_id,
{
duration: '3d',
winners: '20',
price: 'Price to Win',
},
{
new: true,
}
);

In the mainSchema you define giveaways field as an array of giveawaySchema object. So, you have to treat it as an array, not an object. If you want to treat it as an object, you will have to update mainSchema by removing square bracket at giveawaysSchema.
Relevant Question for how to pushing item into mongo array

Related

Mongoose auto increment for existing collection

I have an existing collection named users with the following schema. I want to add a new auto incremented field named userNumber in it. I have seen the counter based solution but failed to implement those mainly because I don't see the working where it will do the numbering for the existing documents plus where to place that code. So my question is how to add userNumber field with auto incrementing values and how to populate values for this column in existing records
user.model
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
userType: {
type: String,
required: false,
},
uid: {
type: String,
required: false,
},
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
},
email: {
type: String,
lowercase: true,
},
});
module.exports = mongoose.model('User', userSchema);
here is how it works with counter:
export const CounterSchema = new mongoose.Schema({
_id: {type: String, required: true},
seq: {type: Number, default: 0}
},
the _id could be anything you want. In that case i would name it user
the seq (sequence) is your current count, starting at 0.
to update the counter you will have to call it every time you create a new user.
const callBack: MongooseDocument = await counterModel.findOneAndUpdate({_id:
'user'}, {$inc: {seq: 1}}, {new: true});
to access the new count you call: newCount = callBack.toJSON().seq
to make it work for you current situation, you will have to loop through your users and update them one by one. When that is done, you update your counter and after that you do it whenever you create a user..
First you can add userNumber to your User schema and when you want to create new user then add value to userNumber automatically by get old value from database and incremented.

Mongoose schema with nested object

I'm trying to create a document in my Schema that essentially works like a dictionary with an array of values (dates):
I want to use this to keep track of email correspondence I am sending users:
let d = {
'generic-contact' = ['11/02/2019', '11/05/2020'],
'update-profile' = ['1/01/2018']
}
I would then update this document field with something like:
let emailAddresses = ['joebloggs#yahoo.com', 'foobar#googlemail.com']
let recipients = await Profile.find({ email: { "$in" : emailAddresses } })
let emailTopic = 'newsletter03'
recipients.forEach(user => {
user.correspondenceHistory[emailTopic].push(Date.now())
user.save()
})
I want to do this so that I make sure that I don't send the same user the same email within a certain period of time.
However I can't work out how to set this up in my schema. Is this sort of structure possible?
I've tried many different structures for correspondenceHistory, but none of them have worked and I can't work out what I'm doing wrong. Here is my schema as it stands:
const mongoose = require("mongoose");
const passportLocalMongoose = require("passport-local-mongoose");
var profileSchema = new mongoose.Schema({
email: String,
firstname: String,
lastname: String,
correspondenceHistory: { type: Array } ### Here ###
}, { discriminatorKey: 'accountType', retainKeyOrder: true, timestamps: true });

Mongoose: Remove referencing ObjectIds using deleteMany hook

My schema is like the following:
const Author = new Schema({
name: String,
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: "Post" }]
});
const Post = new Schema({
text: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: "Author" }
});
If I use deleteMany() to delete a bunch of posts, what is the best way to remove the ObjectId entries from the Author's object?
I tried to use the deleteMany pre hook, however it only passes how many doc are deleted and not the actual ObjectIds of the posts.
You could create the query object that is used for the deleteMany, and instead use it to query for the documents via find. That will give you all of the targeted documents, which you can then delete via deleteMany.
Afterwards, if the deleted count matches the length of fetched documents, then you can safely remove all from the author. If the total deleted does not match the length of fetched documents, then you can iterate through all initially-fetched documents, attempt to fetch it, and if it's not found, then that means it's deleted and you can safely remove from author.
Example in typescript:
const queryObj = { foo: 'bar' };
const postsCollection = mongoDb.getCollection<{ _id: ObjectId }>('posts');
const postsToDelete = await postsCollection.find(queryObj).toArray();
const deleteManyResult = await postsCollection.deleteMany(queryObj);
if (deleteManyResult.deletedCount === postsToDelete.length) {
for (const deletedPost of postsToDelete) {
// delete from author
}
} else {
for (const potentiallyDeletedPost of postsToDelete) {
const post = await postsCollection.findOne({ _id: potentiallyDeletedPost._id });
if (!post) {
// delete from author
} else {
// handle non-deleted post
}
}
}

Return undefined when query to model

I want to access the content of array of document in my model, but I can't and return undefined.
here is my model(Project.js):
var mongoose = require('moongoose');
var Schema = mongoose.Schema;
var User = require("./Users");
var ProjectSchema = new Schema({
name: String,
description: String,
owner: {
type: mongoose.SchemaTypes.ObjectId,
ref: "User"
},
contributor: [{
type: mongoose.SchemaTypes.ObjectId,
ref: "User"
}]
});
module.exports = mongoose.model('Project', ProjectSchema);
and my Api:
var Project = require('./Project')
await Project.find({owner: userId, name: name})
.then(project => {
console.log(project);
console.log(project.contributor);
}).catch(err => {
res.status(500).send({
message: err.message
});
});
when i try console.log(project); return expected output but in console.log(project.contributor); return undefined
I've also searched the web but couldn't find anything right and clear solution
I appreciate any help :)
As you are expecting to find only one project, change find by findOne method. Other case you are searching for several projects and you are going to receive an array instead of an object.
Your output from Project.find() (See) will be an array of objects from the database.
If you will only have 1 object as a result then you can use project[0].contributor because project is an array with 1 object inside it, which is on the index 0.
If the result might have many objects in the array then you should iterate through the result to get each of the data individually.
project.forEach(p => console.log(p.contributor))

how do I query from parent model a array of other model in Mongoose?

I have two Schema for user & todo. Every todo has an owner as a user, every user has an array of todos.
// user.js
const TodoSchema = require('./todo').TodoSchema;
var UserSchema = mongoose.Schema({
name: {
type: String,
required: true
},
todos: {
type: [TodoSchema]
}
});
module.exports.UserSchema = UserSchema;
module.exports.UserModel = mongoose.model('UserModel', UserSchema);
// todo.js
var TodoSchema = mongoose.Schema({
body: {
type: String, required: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UserModel',
required: true
}
});
module.exports.TodoSchema = TodoSchema;
module.exports.TodoModel = mongoose.model('TodoModel', TodoSchema);
I entered data like this.
var nUser = new UserModel({
name: "Alex
)};
nUser.save().then(user => {
var t = new TodoModel({
body: "my new todo",
owner: user._id
});
t.save().then();
});
But the problem is I want to get all the todos from a specific user, something like this...What is the correct way?
UserModel.findOne({name: "Alex"})
.then(user => {
// user.todos
});
P.S.
I can do this like TodoModel.find({owner: specific_user._id}), but I want it from UserModel.
Since you're asking for the proper way of doing it, I am gonna start with your User Schema. If you want to find all the todos of a user, then putting the todo documents inside an array in the User document is not required. So you should probably remove that from your schema.
After that you can use a simple aggregation to get your desired outcome.
UserModel.aggregate([
{
$match:{
name:"Alex"
}
},
{
$lookup:{
from:"todomodels",
localField:"$_id",
foreignField:"$owner",
as:"todos"
}
}
])
this will return all the todos for that user in an array of the same name.

Categories