Mongoose - convert date field to array of dates, with the existing entry - javascript

I have a collection of documents, that looks like this:
{
name: String,
phoneNumber: String,
myDate: Date
}
And this is how an actual entry would look like:
{
name: 'John Doe',
phoneNumber: '(402)-123-4444',
myDate: 2020-08-31T08:54:47.000+00:00
}
And I have about 1000 entries in my db. Now I want to change the date field to an array of dates, and I want to modify all the entries in my db to be something like this myDate: [Date]. I want to keep the existing entry from the date field, but move it inside of an array. And I don't want to do it manually, any way I can do this with mongoose?
I tried something like Model.update({}, { $set: { myDate: [] } }), but I don't know what to use inside the array to keep the existing entry, and not to add something else.

I believe this will on one-time activity so I wanna propose two steps answer here,
Step 1. : Change the type of field from the mongo/shell as,
> db.collection.find().forEach(function(individualDocument) {
db.collection.update(
{ _id: individualDocument._id },
{ "$set": { "myDate": [individualDocument.myDate] } }
);
})
Step 2: Update your mongoose model as you tried ,
{
name: String,
phoneNumber: String,
myDate: [Date]
}
OR
myDate: { type: Array, default: [Date] }

Related

MongoDB/Mongoose: AddToSet an array into an array field, but also addToSet a nested array field value if the parent object already exists

I am building an education application and I am trying to add/update a field which is an array of objects with addToSet from a javascript array, and if the object already exists (matched with objectId) I want to update the already existing object's array (addToSet) and change another field of that same object.
My model looks like this (simplified):
const course = new Schema(
{
events: [
{
type: Schema.Types.ObjectId,
ref: 'event'
}
],
students: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user'
},
status: {
type: String,
enum: ['notBooked', 'booked', 'attended', 'completed']
},
events: [
{
type: Schema.Types.ObjectId,
ref: 'event'
}
]
}
],
});
And ideally I would like to use an updateOne query to both addToSet to the course's list of events, while also updating the students list.
Currently I am using this code to accomplish my updates by first finding the course and then using javascript to update it, which works:
const studentsToAdd = this.attendees.map((attendee) => ({
user: attendee._id,
status: 'booked',
events: [this._id]
}));
const studentIds = studentsToAdd.map((student) => student.user);
const course = await courseModel.findById(this.course);
console.log(studentIds);
course.events.push(this._id);
course.students.forEach((student) => {
if (studentIds.some((s) => s.equals(student.user))) {
student.events.push(this._id);
student.status = 'booked';
studentsToAdd.splice(studentsToAdd.indexOf(studentsToAdd.find((s) => s.user.equals(student.user))), 1);
}
});
course.students.push(...studentsToAdd);
course.save();
However I am curious if it is possible to solve this using a single updateOne on the courseModel schema.
This is part of a mongoose middleware function, and "this" references an event object, which has its own _id, attendees of the event, and the course ID (named course).
The goal is to add the student object part of studentsToAdd to the students array of the course IF the student does not exist (signified by user being a reference by objectId), and if the student already exists then I want to add the eventId (this._id) to the events array for that particular student and set status for that student to "booked".
Is this possible? I have tried many iterations using $cond, $elemmatch, "students.$[]" and so forth but I am quite new to mongodb and am unsure how to go about this.

Mongoose update nested object in array of record

I am currently having a problem where I am trying to update an of a nested array in a Mongoose record.My schema is as follows:
const customerSchema = new mongoose.Schema({
kimCustomerId: {
type: Number,
required: true
},
addresses: [
{
created: Date,
updated: Date,
addressInfo: {
type: { type: String },
typeOfAddress: String,
careOf: String,
address: String,
addressRow1: String,
addressRow2: String,
zipcode: String,
city: String,
countryCode: String,
physicalAddressType: String,
validFrom: Date,
validTo: Date
}
}
],
.....
As you can see, the adrress array for each record holds many addresses. I want to be able to pass through an update object and update the properties inside the addressInfo nested object inside a particular array object. Here is my query as it stands:
const updated = await db.models.customers.findOneAndUpdate(
{
_id: customer._id,
'addresses.addressId': addressData.addressId
},
{ $set: { 'addresses.$': addressData } },
{ new: true }
);
and an example of an object I pass through to update a record:
{
addressId: officialAddressExists.addressId,
addressInfo: {
validTo: new Date().toISOString()
}
}
What I want to happen is, when I pass this object to the schema method, I want to select the correct address by the values 'kimCustomerId' and 'addressId' (which I have working fine) then only update the values of the 'addressInfo' nested object that I have passed and keep the ones not passed as they are, in this case the 'validTo' field but it can be any number of them updated. It is overwriting the whole 'addressInfo' nestedObject at the moment so I presume I have to do some kind of set operation on that nested object as well but I am unsure how.
Is anyone able to point me in the right direction here?
Thanks!
There is no straight way to do this in query, you can do it in your client side, something like,
// Sample body request
let addressData = {
addressId: 1,
addressInfo: {
validTo: new Date().toISOString(),
typeOfAddress: "Home",
address: "ABC"
}
};
let set = {};
for (let key in addressData.addressInfo) {
set["addresses.$.addressInfo." + key] = addressData.addressInfo[key];
}
console.log(set);
Pass set variable in to your query,
const updated = await db.models.customers.findOneAndUpdate(
{
_id: customer._id,
'addresses.addressId': addressData.addressId
},
{ $set: set },
{ new: true }
);

mongodb aggregate with find features

I have a model similar to this one:
{
email: String,
name: String,
role: String,
location: {
country: String,
city: String
},
contacts: {
email: String,
phone: String
}
}
I need to show in my view the entire users information but I wish to include also how many users from a country there are.
Using aggregate I don't know how to get the full user over the groups I create.
So at the moment what I'm doing is this:
User.find({}, function(err, users) {
User.aggregate([
{ $group: { _id: { country: '$location.country' }, count: { $sum: 1 }}}
], function(err, results) {
res.render('home', {
users: users,
countries: results
});
});
});
As you can see I'm using Find and then aggregate to get both the information I need... but I'm pretty sure there is a way to get it using only aggregate but I can not find how to do that...
If you need to accumulate the entire user information for each group, then you need to use the $push operator for accumulation and the $$ROOT system variable to access the entire user info.
User.aggregate([
{$group:{"_id":{"country":"$location.country"},
"users":{$push:"$$ROOT"},
"count":{$sum:1}}}
],callback)
In case you would want to accumulate only specific fields of the user information document, you could just push the required fields like:
User.aggregate([
{$group:{"_id":{"country":"$location.country"},
"users":{$push:{"email":"$email","name":"$name"}},
"count":{$sum:1}}}
],callback)

How can I count all dupe values in a JSON collection?

I would like to count all the companies in the following JSON string. I was first planning on doing it manually with loops but it seems like this is a good chance for me to learn to use map/reduce... How can return something that returns {company: 2}?
[
{
_id: '123',
company: 'Acme'
}
{
_id: '123',
company: 'Innatrode'
}
{
_id: '123',
company: 'Acme'
}
]
If you want to get the number of unique company names, then
use _.pluck to get the company attribute out of the list of company objects
Find the unique values out of them with _.uniq, which will return an array.
Compose a new object with the key `com
console.log({company: _.uniq(_.pluck(companies, "company")).length});
# { company: 2 }

MongoDB query on populated fields

I have models called "Activities" that I am querying for (using Mongoose). Their schema looks like this:
var activitySchema = new mongoose.Schema({
actor: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
recipient: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
timestamp: {
type: Date,
default: Date.now
},
activity: {
type: String,
required: true
},
event: {
type: mongoose.Schema.ObjectId,
ref: 'Event'
},
comment: {
type: mongoose.Schema.ObjectId,
ref: 'Comment'
}
});
When I query for them, I am populating the actor, recipient, event, and comment fields (all the references). After that, I also deep-populate the event field to get event.creator. Here is my code for the query:
var activityPopulateObj = [
{ path: 'event' },
{ path: 'event.creator' },
{ path: 'comment' },
{ path: 'actor' },
{ path: 'recipient' },
{ path: 'event.creator' }
],
eventPopulateObj = {
path: 'event.creator',
model: User
};
Activity.find({ $or: [{recipient: user._id}, {actor: {$in: user.subscriptions}}, {event: {$in: user.attending}}], actor: { $ne: user._id} })
.sort({ _id: -1 })
.populate(activityPopulateObj)
.exec(function(err, retrievedActivities) {
if(err || !retrievedActivities) {
deferred.reject(new Error("No events found."));
}
else {
User.populate(retrievedActivities, eventPopulateObj, function(err, data){
if(err) {
deferred.reject(err.message);
}
else {
deferred.resolve(retrievedActivities);
}
});
}
});
This is already a relatively complex query, but I need to do even more. If it hits the part of the $or statement that says {actor: {$in: user.subscriptions}}, I also need to make sure that the event's privacy field is equal to the string public. I tried using $elemMatch, but since the event has to be populated first, I couldn't query any of its fields. I need to achieve this same goal in multiple other queries, as well.
Is there any way for me to achieve this further filtering like I have described?
The answer is to change your schema.
You've fallen into the trap that many devs have before you when coming into document database development from a history of using relational databases: MongoDB is not a relational database and should not be treated like one.
You need to stop thinking about foreign keys and perfectly normalized data and instead, keep each document as self-contained as possible, thinking about how to best embed relevant associated data within your documents.
This doesn't mean you can't maintain associations as well. It might mean a structure like this, where you embed only necessary details, and query for the full record when needed:
var activitySchema = new mongoose.Schema({
event: {
_id: { type: ObjectId, ref: "Event" },
name: String,
private: String
},
// ... other fields
});
Rethinking your embed strategy will greatly simplify your queries and keep the query count to a minimum. populate will blow your count up quickly, and as your dataset grows this will very likely become a problem.
You can try below aggregation. Look at this answer: https://stackoverflow.com/a/49329687/12729769
And then, you can use fields from $addFields in your query. Like
{score: {$gte: 5}}
but since the event has to be populated first, I couldn't query any of its fields.
No can do. Mongodb cannot do joins. When you make a query, you can work with exactly one collection at a time. And FYI all those mongoose populates are additional, distinct database queries to load those records.
I don't have time to dive into the details of your schema and application, but most likely you will need to denormalize your data and store a copy of whatever event fields you need to join on in the primary collection.

Categories

Resources