Meteor Calculated Value in Collection Field - javascript

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.

Related

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.

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.

update array element mongodb query take too much time

I have collection like this :
{
"_id" : ObjectId(),
"user_id":"10"
"movie_rate":[
{
rate:2,
movie_id:"120"
},
{
rate:null,
movie_id:"230"
},
]
}
I want to update movie rate array element with movie id and I build a query for doing this:
db.rates.update({
user_id: data.user_id,
"movie_rate.movie_id": data.movie_id
}, {
$set: {
"movie_rate.$.rate": data.rate
}
}
All movie id are unique, so there is just one element in the movie rate array that I wanted to update; however my update query take to much time to execute, I have 7000 document and each document have movie rate array which length is 3700.
I found out another solution which in the first place it seems to be very awful idea,I solve this problem in three query first I find document with find query and user id,then I loop over movie rate array and find out the index of that element, I wanted to update next I pull the element from array with movie id and at last I push document in the array with it's position that I found out in the find query my awful solution was significantly faster than the first one.
Here my pull query:
db.rates.update(
{
user_id: data.user_id
},
{
$pull: {
movie_rate: {
movie_id: data.movie_id
}
}
}
and here is my push query:
db.rates.update(
{
user_id: data.user_id
},
{
$push: {
movie_rate: {
$each: [{
'rate': data.rate,
'movie_id': data.movie_id
}],
$position: index
}
}
}
So why my second solution is faster than my first one?

How can I select the latest version of an object from a ForerunnerDb collection

I have a collection which contains a series of objects generated over time. Since I have disparate types stored in the same collection, I have a TypeId and a UID per object (where the UID identifies objects that refer to the same entity over time). I am trying to choose the most recent object from the collection, and running into serious difficulties grasping how to do so without manually iterating a query result - something I'd rather avoid since I think it could become expensive when the collection gets larger.
For example:
var db; // assigned elsewhere
var col = db.collection("res");
col.primaryKey("resId");
col.insert({
resId: 1,
TypeId: "Person",
UID: "Bob",
Data: {Age: 20, Name:Bob}
});
col.insert({
resId: 2,
TypeId: "Person",
UID: "Bob",
Data: {Age: 25, Name:Bob}
});
col.insert({
resId: 3,
TypeId: "Car",
UID: "TeslaModelX",
Data: {Manufacturer: "Tesla", Owner:"Bob"}
});
col.insert({
resId: 4,
TypeId: "Person",
UID: "Bill",
Data: {Age: 22, Name:Bill}
});
From col, I want the query to select all objects with TypeId="Person" ranked by resId descending, i.e. I'd expect to select objects 4 and 2, in that order.
The collection above is contrived, but in reality I'd expect there to be certainly 000s of entries and potentially 0000s, with maybe 00s of versions of each UID. In other words, I'd rather not return the full collection of objects, grouped or otherwise, and iterate it.
I have tried the following, but since the $distinct operator is applied before the $orderBy one, this doesn't help:
col.find(
{
TypeId : {$eq : "Person"}
$distinct: { UID: 1}
},
{
$orderBy: {
resId : -1
}
}
);
I have in mind that I should be able to use the $groupBy, $limit and $aggregate clauses to identify the per group desired IDs, and then use a subquery to find the precise (non-aggregated) elements, but as yet I haven't managed to get anything to do what I want. Any ideas?
My current solution is to include a Deleted property amongst my objects, and set it to true for all existing non-deleted objects in the DB before I insert new entries. This lets me do what I want but also stops me from, for instance, choosing the best available within a known timeframe or similar.
You can do this like:
var tmpObj = {};
col.sort({resId: -1}, coll.find({
"TypeId": "Person"
})).filter(function (doc) {
return col._match(doc, {
$distinct: {
UID: 1
}
}, {}, 'and', tmpObj);
});
It's a bit dirty since it's not neatly wrapped up in a single command, but it's as clean as you'll get it in ForerunnerDB v1.x.
Version 2 will have a new query pipeline system that would allow for exactly this sort of usage, something like this:
col.pipeline()
.find({"TypeId": "Person"})
.orderBy({"resId": 1})
.distinct({"UID": 1})
.then(function (err, data) {
console.log(data);
});
Source: I'm the developer of ForerunnerDB.

Categories

Resources