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.
Related
I'm trying to make a query which calculates the total price of the shopping basket.
A basket contains basketProducts, and a basketProduct has a product which contains all the information such as price.
I tried it as follows:
const basket = await prisma.basketProduct.aggregate({
where: {
basketId: id,
},
_sum: {
select: {
product: {
price: true,
},
},
},
});
Error
Unknown field `select` for select statement on model BasketProductSumAggregateOutputType. Available options are listed in green. Did you mean `amount`?
I also tried it as follows but it doesn't work.
const basket = await prisma.basketProduct.aggregate({
where: {
basketId: id,
},
_sum: {
product: {
price: true,
},
},
});
Error
Unknown field `product` for select statement on model BasketProductSumAggregateOutputType. Available options are listed in green. Did you mean `productId`?
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,
})
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.
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!
Is there a way to have a allways calculated value in Meteor collection field? I am currently developing an app to manage inventory of sandwiches. Each sandwich can depend on ingredients in other collections. I need to have a field always auto calculated to the number of the ingredient that is lowest in stock. How can i achieve this? I can not find anything about this when I Google, is it possible that Meteor does not have any support for this?
This sounds like a job for a collection hook. Collection hooks allow you to execute an action before/after collections are inserted/updated/etc.
Let's say you have an ingredients collection. Perhaps that ingredients collection has a schema like:
Ingredients = new Mongo.Collection('ingredients');
IngredientsSchema = new SimpleSchema({
"name": {
type: String
},
"quantity": {
type: Number
}
});
Ingredients.attachSchema(IngredientsSchema);
Then you have a sandwiches collection with a hypothetical schema:
Sandwiches = new Mongo.Collection('sandwiches');
SandwichesSchema = new SimpleSchema({
"name": {
type: String
},
"ingredients": {
type: [String],
label: "An array of ingredient internal ids (_id)"
},
"quantity": {
type: Number
}
});
Sandwiches.attachSchema(SandwichesSchema);
Your collection hook would be something along the lines of:
Ingredients.after.update(function(userId, doc, fieldNames, modifier, options) {
// Find the ingredient with the lowest value
ingredient = Ingredients.findOne({}, { sort: { quantity: 1 } });
if(ingredient && ingredient._id == doc._id) {
//If the ingredient matches this ingredient, update all sandwiches who have the agreement to reflect the remaining quantity of ingredients.
Sandwiches.update({ ingredients: doc._id }, { $set: { quantity: doc.quantity } }, { multi: true });
}
});
You'll probably also need a collection hook after inserting an ingredient, but this should be plenty to get you started.