Comapring ObjectId using mongoose problem even after using .toString() - javascript

I understand this is a really long post, but I would truly appreciate it lots if someone could have a look and help me out, I'm really stuck.
So, I'm really new to node mongoose and express. I'm basically building an e-commerce node app and I'm working with mongoose, my Schema of the users, has an embedded cart array with productIds which reference to a products document. Here's how a user record looks like:
_id: ObjectId("60e4945782dd90266470044d")
email: "test#test.com"
password: "$2a$12$ARK.2yNm/AiRJ/rkNRLXu.ZGZiQkm/hXnacxe407KlUdeZ8lzKCh6"
cart: [
{
_id: ObjectId("60e9f7102a4c6217045f68ba")
productId: ObjectId("60e765882e3162092451966c")
quantity: 2
},
{
_id: ObjectId("60e9f7122a4c6217045f68c0")
productId: ObjectId("60e764b62e31620924519667")
quantity: 1
}
]
This would be the product document record:
_id: ObjectId:("60e764b62e31620924519667")
images:[]
title:"Book"
price:200,
userId : ObjectId("60e4945782dd90266470044d")}
I have added a method to my user model removeFromCart to delete a product from the cart it accepts a productId, which is sent from a controller, which handles a post request, the prodId comes from a ejs input where I loop through cart and send the product Id(check code below for the console.log of prodId here). I stored the user with a session in req.user, so I can use the mongoose features :
exports.postDeleteFromCart = (req, res, next) => {
const prodId = req.body.productId;
console.log('this is the id from the post request body', prodId)
req.user.removeFromCart(prodId)
res.redirect('/cart')
};
This is the method removeFromCart, which I have included in my user model.
userSchema.methods.removeFromCart = function (prodId) {
console.log('this is the argument from the function', prodId.toString())
const updatedCart = this.cart.filter((item) => {
console.log('this is the productId inside the cart', item.productId.toString())
return item.productId.toString() !== prodId.toString()
});
console.log(updatedCart);
};
Here is the interesting part, when I run this I'm expecting a console.log of the updatedCart which doesn't have the product I wanted to remove. Instead I get this console.log in the terminal:
this is the id from the post request body 60e765882e3162092451966c
this is the argument from the function 60e765882e3162092451966c
this is the productId inside the cart 60e765882e3162092451966c
this is the productId inside the cart 60e764b62e31620924519667
this is the updated cart: [
{
_id: 60e9f7102a4c6217045f68ba,
productId: 60e765882e3162092451966c,
quantity: 2
},
{
_id: 60e9f7122a4c6217045f68c0,
productId: 60e764b62e31620924519667,
quantity: 1
}
]
I have two products inside the cart array and I'm expecting one to be deleted since the filter method should return only those products that don't have the same productId, which comes from the cart elements and prodId from the argument. But from the log up there we can see that nothing changed from the updatedCart even though these console.logs that display the id are all the same which would mean one element must be removed.
this is the argument from the function 60e765882e3162092451966c
this is the productId inside the cart 60e765882e3162092451966c
But, it isn't the case since I get returned the updatedCart array with an element with that exact productId which should be removed but isn't.
this is the updated cart: [
{
_id: 60e9f7102a4c6217045f68ba,
productId: 60e765882e3162092451966c,
quantity: 2
},
{
_id: 60e9f7122a4c6217045f68c0,
productId: 60e764b62e31620924519667,
quantity: 1
}
]
In advance thank you for your time I know this was a long post. I'm really thankful!

Related

Store data as a reference field in firestore using the addDoc function

I'm currently building an expense-tracker app and I want the transactions collection to have a category field. The category field should be of type "reference" and it should refer to another collection "categories". I tried this, but when I use the addDoc function the field category is stored as a map. And to be honest: It makes sense based on my code, but I don't know how to fix it.
Does anyone know how i can change that code, so the category is stored as a reference field in the firestore database?
const transaction = { category: {}, description: "", date: "", price: 0, userId: null} as Transaction
await addDoc(collection(db, "transactions"), {
...inputValues,
category: {
...selectedOption
},
userId: user?.uid,
})

Is there a way to update an object in an array of a document by query in Mongoose?

I have got a data structure:
{
field: 1,
field: 3,
field: [
{ _id: xxx , subfield: 1 },
{ _id: xxx , subfield: 1 },
]
}
I need to update a certain element in the array.
So far I can only do that by pulling out old object and pushing in a new one, but it changes the file order.
My implementation:
const product = await ProductModel.findOne({ _id: productID });
const price = product.prices.find( (price: any) => price._id == id );
if(!price) {
throw {
type: 'ProductPriceError',
code: 404,
message: `Coundn't find price with provided ID: ${id}`,
success: false,
}
}
product.prices.pull({ _id: id })
product.prices.push(Object.assign(price, payload))
await product.save()
and I wonder if there is any atomic way to implement that. Because this approach doesn't seem to be secured.
Yes, you can update a particular object in the array if you can find it.
Have a look at the positional '$' operator here.
Your current implementation using mongoose will then be somewhat like this:
await ProductModel.updateOne(
{ _id: productID, 'prices._id': id },//Finding Product with the particular price
{ $set: { 'prices.$.subField': subFieldValue } },
);
Notice the '$' symbol in prices.$.subField. MongoDB is smart enough to only update the element at the index which was found by the query.

Realm Delete an Object in One to Many relationship

I have a ONE TO MANY schema like this:
SHOP SCHEMA
const Shop = {
name: "Shop",
properties: {
_id: "objectId",
products:"Products[]"
}
}
PRODUCTS SCHEMA
const Products = {
name: "Products",
properties: {
_id: "objectId",
name : "string",
}
}
A shop has many products and as it can be seen 'pictorially' below
_id:'60f73ca7c1a70278596cc7d0',
products:[
{_id:1, name:'product1'},
{_id:2, name: 'product2'},
{_id:3, name: 'product3'}
]
Now, say I want to delete product2, How do I do it with mongodb realm?
What I have tried so far
const obj = realm.objects('Shop').filtered("_id == $0 AND products._id == $1", ObjectId('60f73ca7c1a70278596cc7d0'), ObjectId('2'))
realm.write(() => {
realm.delete(obj)
})
But this doesn't delete the item in the products array.
How can I achieve deleting a specific element in products array in this One to Many relationshiop using realm?
The code in the question is very close to being correct, you just need to filter for the product you want to delete instead of the shop. It's not clear if you know the product _id or name but you can filter by either one.
Here's the code to filter for products with an _id of 1 and then delete it (which will also remove it from any lists that contain a reference to it.
const prod = realm.objects('Products').filtered("_id == 1");
realm.write(() => {
realm.delete(prod);
prod == null;
})
The above is taken from the documentation Filter Query and Delete An Object
Keep in mind this will delete all products with id = 1 so as long as _id's are unique it's fine.

Mongoose find by parameters including sub-array

I am creating an app where a job is created and that job's id is added to another collection (client) so the job can be referenced from the client itself. I have been able to add the job's id to the client's collection so far, but I am having trouble figuring out how to remove the job's id from the client's collection if the job is deleted. This is because the id is stored as a sub-collection within the client. The code I am trying to get to work is below:
// delete
app.delete("/jobs/:id", function(req, res){
Client.find({jobs._id: req.params.id}, function (err, foundClient){ //This part doesn't work
if (err) {
console.log(err);
} else {
// Add id identifier to Client
foundClient.jobs.pull(req.params.id);
foundClient.save();
}
});
// Delete Job
Job.findByIdAndRemove(req.params.id, function(err, deletedJob){
if (err){
console.log(err)
} else {
// Redirect
res.redirect("/jobs");
}
});
});
I am trying to get the logic of this part to work:
Client.find({jobs._id: req.params.id},
Here is the Client Schema
// =======================Client Schema
var clientSchema = new mongoose.Schema({
organization_name: String,
first_name: String,
middle_name: String,
last_name: String,
email_address: String,
phone_number: String,
street: String,
city: String,
state: String,
zip: String,
description: String,
active: {type: Boolean, deafult: true},
date_added: {type: Date, default: Date.now},
transactions: [{type: mongoose.Schema.Types.ObjectID, ref: "Transaction"}],
jobs: [{type: mongoose.Schema.Types.ObjectID, ref: "Job"}]
});
module.exports = mongoose.model("Client", clientSchema);
Basically, what I am trying to tell it to do is find the Client where the client's jobs array contains an id equal to the id of the job being deleted. Of course, this syntax is incorrect, so it does not work. I have not been able to find documentation that explains how I would be able to do this. Is there a more straightforward way of doing this, the way I am writing it out here? I know that I can query the db this way if the job itself was not an array and only contained one singular variable. Is there a way to do this or do I need to write a completely separate looping function to get this to work? Thank you.
jobs is an array of ids, so to find some documents in Client collection that have req.params.id in the jobs array, the query should be something like this
Client.find({jobs: req.params.id})
this will return an array of documents, each document has an array of jobs Ids
If you are sure that the req.params.id exists only in one document, you can use findOne instead of find, and this will return only one document with an array of jobs Ids
this is regarding the find part
regarding the remove job Id from jobs array, we can use one of the following methods
1- as you suggested, we can find the clients documents that have this job Id first, then remove this id from all the jobs arrays in all matching documents
like this
Client.find({ jobs: req.params.id }, async function (err, foundClients) {
if (err) {
console.log(err);
} else {
// loop over the foundClients array then update the jobs array
for (let i = 0; i < foundClients.length; i++) {
// filter the jobs array in each client
foundClients[i].jobs = foundClients[i].jobs || []; // double check the jobs array
foundClients[i].jobs = foundClients[i].jobs.filter(jobId => jobId.toString() !== req.params.id.toString());
// return all the jobs Ids that not equal to req.params.id
// convert both jobId and req.params.id to string for full matching (type and value)
await foundClients[i].save(); // save the document
}
}
});
2- we can use $pull array update operator to update the jobs array directly
something like this
Client.updateMany(
{ jobs: req.params.id }, // filter part
{
$pull: { jobs: { $in: [req.params.id] } } // update part
},
function (err) {
if (err) {
console.log(err);
} else {
console.log('job id removed from client documents successfully');
}
}
);
hope it helps

How to get attributes from through table in query?

I'm making a simple shopping cart. I have a Cart model, a Product model and a through table CartItems. These are the associations:
models.Cart.belongsToMany(models.Product, { through: 'CartItems', as: 'items' })
models.Product.belongsToMany(models.Cart, { through: "CartItems" });
These are the definitions of the models:
Cart Model
var Cart = sequelize.define('Cart', {
userId: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'User',
key: 'id',
},
},
totalPrice: DataTypes.FLOAT
});
Product Model
var Product = sequelize.define('Product', {
code: DataTypes.STRING,
name: DataTypes.STRING,
price: DataTypes.FLOAT
});
CartItems Model
In this model I added a quantity and price attribute because I read somewhere that it's good to have a history of what the price was when they made the order. And the quantity attribute because I just want to change the quantity if another product is added instead of adding another row.
var CartItem = sequelize.define('CartItem', {
CartId: DataTypes.INTEGER,
ProductId: DataTypes.INTEGER,
quantity: DataTypes.INTEGER,
price: DataTypes.FLOAT
});
I know this might not be the best way to do things but even if I change the implementation I would like to know: how do I access an attribute that's in the through table?
Specifically I'm trying to do the following for the checkout function:
Cart.prototype.checkout = async function () {
let cartItemArray = await this.getItems({
include: [{
model: Product,
through: {
attributes: ['quantity'],
}
}]
});
cartItemArray = cartItemArray.map((item) => {
return {
code: item.code,
price: item.price,
quantity: item.quantity
};
});
let total = getTotalPrice(cartItemArray);
return total;
};
First, a few warnings
Warning 1. You have a price field both in your Product model and in your CartItem model. Are you sure you want this? In your attempt to write that checkout() method, when you do item.price, which of those prices did you want to get? My intuition tells me you didn't really want to have two fields, but if you really do, consider renaming one of them to avoid ambiguity.
Warning 2. You have a totalPrice in your Cart model... Is this field supposed to keep track of the sum of the prices of the associated products? If yes, that is a bad idea, remove that field altogether and whenever you need the sum, compute it at that very moment, because keeping duplicate data like this is very error prone (you must ensure they are in sync).
Mistake 1
You explicitly defined the junction table model, i.e. CartItem, with the following code:
var CartItem = sequelize.define('CartItem', { /* ... */ });
So far so good. But when you define the many-to-many relationship, you made a mistake. You used through: "CartItems" but you should have used through: "CartItem". Actually, the best practice in this case is to refer directly to the model, since you have it: through: CartItem. Because of this Sequelize ended up ignoring your model and creating a junction table automatically without your extra fields price and quantity.
Mistake 2
In your attempt to write the checkout() method you did:
this.getItems({
include: [{
model: Product,
through: {
attributes: ['quantity'],
}
}]
});
This does not make sense. Recall that Item is just an alias you set up for Product. Running this code yields SequelizeEagerLoadingError: Product is not associated to Product!.
Instead, you can simply perform a this.getItems() without any parameters at all.
Mistake 3
Next, you wrote the code:
return {
code: item.code,
price: item.price,
quantity: item.quantity
};
which suggests that you were expecting that quantity came as another field alongside code. This is incorrect. code is a field from the Product model while quantity is a field from the CartItem model. Sequelize will not retrieve them "flattened" like this. Instead, the fields from the association itself come nested in the query result, like this:
{
"id": 1,
"code": null,
"name": "test",
"price": null,
"createdAt": "2018-03-11T19:11:12.862Z",
"updatedAt": "2018-03-11T19:11:12.862Z",
"CartItem": {
"CartId": 1,
"ProductId": 1,
"quantity": 2,
"price": 1.5,
"createdAt": "2018-03-11T19:11:13.047Z",
"updatedAt": "2018-03-11T19:11:13.047Z"
}
}
Therefore, instead of item.quantity there, you should use item.CartItem.quantity.
Summarizing
The answer to the question in title, "How to get attributes from through table in query?" is simply "just do the query, i.e., this.getItems() in your case, since the attributes from through table come in the result by default".
It's just that you made a few other mistakes and of course, it didn't work.

Categories

Resources