Create custom getter - node.js

How can I make a field in a document have a custom getter? I want the subtotal field to return productTotal + tax anytime I get subtotal.
const item = new mongoose.Schema({
payment: {
productTotal: Number,
tax: Number,
subtotal: Number, // (productTotal + tax)
}
});
const Item = mongoose.model('Item', item);
I can't use virtualization since I want to also be able to use find on subtotal.

Hi I've never used mongoose but we can practicly create a prototype for Item model I'm guessing maybe this code would work?
const item = new mongoose.Schema({
payment: {
productTotal: Number,
tax: Number,
},
});
const Item = mongoose.model("Item", item);
Item.prototype.subtotal = function () {
return this.payment.productTotal + this.payment.tax;
};
const newItem = new Item({ payment: { productTotal: 10, tax: 10 } });
// Obv you need to call it as function :)
console.log(newItem.subtotal());
I've checked the docs from mongoose couldn't find anything related with getter

Related

How to list all documents in a mongodb collection in ascending order mongoose

I am trying to list the documents in my level collection by the lowest level to the highest, but that is not working. My code is
const lvl = await Level.find({}).sort({level: 1});
for the filter, and I list it over here:
lvl.forEach(entry => {
embed.addFields({ name: `Level ${entry.level}`, value: `Xp Required: ${entry.xp}`});
})
The code for the level schema is:
const mongoose = require("mongoose");
const LevelSchema = new mongoose.Schema({
level: mongoose.Schema.Types.String,
xp: mongoose.Schema.Types.String,
});
module.exports = mongoose.model("levels", LevelSchema);
This code is working completely fine, the only issue is that it comes in the order that the documents are in the collection, and not by the lowest level to the highest like I want it to. I did what the documentation told me to do in the .sort({level: 1}) part, but that didn't change anything either.
You can use mapping your array, lets re-construct your code for a bit:
this part of your code is okay:
await Level.find({}).sort({level: 1});
But you might need to do it as Array Object. So to work this out. Change the code line to
Level.find({}).sort([['level', 'ascending']]); //remove the await
//ascending = 1, descending = -1
Adding the .exec() will do the trick:
Level.find({})
.sort([['level', 'ascending']])
.exec((err, res) => {
if(res) {
//There's an existing data
//Let's use your method on embed.
let embed = new MessageEmbed()
.setDescription("OPTIONAL")
.setColor("YOUR_COLOR")
.setTitle("OPTIONAL")
.setTimestamp() //OPTIONAL
const result = res.map((value, index) => {
return(embed.addFields({name: `Level: ${res[index].level}`, value: `Xp Required: ${res[index].exp}`}))
}).join("\n");
result
message.channel.send({embeds: [embed]})
} else {
//No data message
}
})
Since the sorting detects only the first number, you need to add .collation({locale: "en_US", numericOrdering: true}).
from this:
Level.find({}).sort([['level', 'ascending']])
Turn it to this:
Level.find({}).sort([['level', 'ascending']]).collation({locale: "en_US", numericOrdering: true})

Access other fields in getter

I'm trying to make it that every time I get subtotal, it adds 2 other fields, productTotal, and tax.
Here's my code:
const cost = new mongoose.Schema({
payment: {
productTotal: Number,
tax: Number,
subtotal: Number, // (productTotal + tax)
}
});
const Cost = mongoose.model('Cost', cost);
How can I add 2 feilds from the same schema when getting a different field?
You can achieve that in mongoose by creating a virtual field for subtotal. The mongoose reference documents this pretty well here: https://mongoosejs.com/docs/tutorials/virtuals.html.
EDIT:
The code snippet below shows how you can define a cost schema that has a subtotal virtual on the payment subdocument:
const PaymentSchema = new Schema({
productTotal: { type: Number, default: 0 },
tax: { type: Number, default: 0 },
});
PaymentSchema.virtual('subtotal').get(function () { return this.productTotal + this.tax; });
const CostSchema = new Schema({
payment: PaymentSchema,
});
From the above snippet you can get the subtotal from a Cost document instance via cost.payment.subtotal.

How to create field that will auto increment after insertion of new record in MongoDB?

I'm using Mongoose and Node.js
The Schema of the model is as follows:
let orderSchema = new Schema({
'product': String,
'orderNumber': Number,
'totalPrice': Number,
'customer': {
'type': Schema.Types.ObjectId,
'ref': 'Users'
});
I want to set the orderNumber as an incrementing integer.
Is there any way to do it in MongoDB?
I don't want to use the pre-hook technique to do it
You need to create a collection with counters and a plugin with two hooks inside:
schema.pre - to get the current value of counter
schema.post - to save new value of counter
Counter schema will look like this:
const conterSchema = new Schema({
name: String,
value: Number
});
While the plugin will can be defined like this:
function incrementOrderNumber (schema) {
schema.pre('save', next => {
CounterModel.findOne({ name: 'orderNumberCounter' })
.then(counterDoc => counterDoc.toObject())
.then(({ value}) => {
this.orderNumber = value;
next();
});
});
schema.post('save', next => {
CounterModel.findOneAndUpdate({ name: 'orderNumberCounter' }, { $inc: { value: 1 }}).exec();
});
}
After creating such plugin function you will need to plug it into your schema:
orderSchema.plugin(incrementOrderNumber);
Do not forget to insert orderNumberCounter into counters collection.

Finding Mongoose subdocument by time updated

I have two Mongoose Schemas:
var ItemSchema = new Schema({
trade: {
type: Schema.Types.ObjectId,
ref: 'Trade'
}
});
var Item = mongoose.model('Item', ItemSchema);
and
var TradeSchema = new Schema({
expiresOn: {
type: Date
}
});
var Trade = mongoose.model('Trade', TradeSchema);
I am trying to use Item.find() to find a item if its trade date is less than the date the user passes in via the query string in the request. I'm using the following code:
if (req.query.expiresBefore) {
Item.find({
'trade.expiresOn': {
$lte: req.query.expiresBefore
}
}, function (err, trades) {
console.log(trades)
})
}
However, I am receiving an empty array on the console.log() call. Using $gte also returns an empty array (though my research tells me that I need to use $lte for my purposes). What do I need to do to find the item document by matching the property (expiresOn) of its child (Trade)?
Referring to this one Stackoverflow question, what you want to do is not possible.I am not sure why you set the schemas like this, but if you want to keep them as they are. I would suggest you make a little change like the following
var ItemSchema = new Schema({
trade: {
type: Schema.Types.ObjectId,
ref: 'Trade'
}
});
var Item = mongoose.model('Item', ItemSchema);
and
var TradeSchema = new Schema({
itemId: { //add this
type: Schema.Types.ObjectId,
ref: 'Item'
},
expiresOn: {
type: Date
}
});
var Trade = mongoose.model('Trade', TradeSchema);
if (req.query.expiresBefore) {
Trade.
find({
'expiresOn': {
$lte: req.query.expiresBefore
}
}).
populate('itemId').
exec(function (err, trades) {
console.log(trades)
});
}

Create unique autoincrement field with mongoose [duplicate]

This question already has answers here:
Mongoose auto increment
(15 answers)
Closed 2 years ago.
Given a Schema:
var EventSchema = new Schema({
id: {
// ...
},
name: {
type: String
},
});
I want to make id unique and autoincrement. I try to realize mongodb implementation but have problems of understanding how to do it right in mongoose.
My question is: what is the right way to implement autoincrement field in mongoose without using any plugins and so on?
const ModelIncrementSchema = new Schema({
model: { type: String, required: true, index: { unique: true } },
idx: { type: Number, default: 0 }
});
ModelIncrementSchema.statics.getNextId = async function(modelName, callback) {
let incr = await this.findOne({ model: modelName });
if (!incr) incr = await new this({ model: modelName }).save();
incr.idx++;
incr.save();
return incr.idx;
};
const PageSchema = new Schema({
id: { type: Number , default: 0},
title: { type: String },
description: { type: String }
});
PageSchema.pre('save', async function(next) {
if (this.isNew) {
const id = await ModelIncrement.getNextId('Page');
this.id = id; // Incremented
next();
} else {
next();
}
});
Yes, here's the "skinny" on that feature.
You need to have that collection in your mongo database. It acts as concurrent key allocation single record of truth if you want. Mongo's example shows you how to perform an "atomic" operation to get the next key and ensure that even there are concurrent requests you will be guaranteed to have the unique key returned without collisions.
But, mongodb doesn't implement that mechanism natively, they show you how to do it. They only provide for the _id to be used as unique document key. I hope this clarifies your approach.
To expand on the idea, go ahead and add that mongo suggested implementation to your defined Mongoose model and as you already guessed, use it in Pre-save or better yet pre-init event to ensure you always generate an id if you work with a collection server side before you save it to mongo.
You can use this.
This package every time generate unique value for this.
Package Name : uniqid
Link : https://www.npmjs.com/package/uniqid
Ignore all the above. Here is the solution
YourModelname.find().count(function(err, count){
req["body"].yourID= count + 1;
YourModelname.create(req.body, function (err, post) {
if (err) return next(err);
res.json(req.body);
});
});

Resources