best schema model in my case (mongoose js) - node.js

I need advice on the schema.
For now, it looks as shown below:
const accountSchema = new mongoose.Schema({
nickname:{
index:true,
type: String,
},
levels_scores: [Number],
money: Number,
skins: { circles: [Number], sticks: [Number], targets: [Number] },
current_skins: { circles: Number, sticks: Number, targets: Number },
})
my queries are always filtered by nickname so I created index on it.
I very often update money value :
saveMoney(req, res) {
const nickname,money = req.body
Accounts.findOneAndUpdate({ nickname:nickname},{money:money}),{useFindAndModify:false)
res.sendStatus(200)
}
Callback in the case will return all the document data which I don't need.(levels_scores,skins etc.) Performance waste I think
Am I thinking wrong?
Should I make schema with references,like money schema which stores only parent ID and money value?
I also have queries like:
Accounts.findOne({ nickname: req.body.nickname }, (err, account) => {
const part = req.body.skin[0]
const skin_number = req.body.skin[1]
account.skins[part].push(skin_number)
account.markModified("skins")
account.save()
res.sendStatus(200)
})
If i use .select("skins") method to not return all the document data, will I be able to save it?
How would I do that to be the most performant?

Related

MongoDB/Mongoose how to manage incrementation of some Number fields

I'm currently working on an API and I need the users to have at least 4 fields of number which will increase over time. Some of them might be 15000/hour, some of them 500/hour, some will be negative like -8000/hour. How should I handle such data storage using Mongoose? What kind of properties should these user model fields have? If you can provide me a simple Mongoose model for such data storage, I would be happy.
Should I have something in back-end just to increase (or decrease) these fields of users? Or does MongoDB/Mongoose have something to provide this feture? Also, how should I show these fields increasing on the web page? Should I always get the increased fields of user every few seconds? Or should I just use JavaScript on front-end?
Thanks in advance.
It would depending on what you would want to achieve with your data, at the end of the day.
To keep track of all changing logs, persisted in its respective array, then bucket pattern would best solve your problem [bucket pattern explanation][1]
[1]: https://www.mongodb.com/blog/post/building-with-patterns-the-bucket-pattern
, else just ordinary field manipulation should solve your problem.
Schema design for implementation of both, [bucket pattern] and [field manipulation] are shown as follows:
If you plan to keep track of all your changing logs, then bucket pattern is your best bet, your schema could look like this:
const mongoose = require('mongoose')
const { Schema } = mongoose
const numberLogSchema1 = new Schema({
field1: [
{type:Number}
],
field2: [
{type:Number}
],
field3: [
{type:Number}
],
field4: [
{type:Number}
]
})
module.exports = mongoose.model('numberLogs1',numberLogSchema1)
And its corresponding would look like this:
router.post('/numberLog', async (req, res) => {
try {
const saveNumberLog = await numberLog1.updateOne(
{ _id: numberCollectionIdhere },
{
$push: {
field1: req.body.fieldLogValue
}
})
res.json(saveNumberLog)
}catch (err) {
res.json({
message: err
})
}
})
Else if you just want to manipulate field values, at specific intervals from the frontend, using a javascript timer which could also be persisted to the database and fetched on a page Reload, your schema could look like this:
const mongoose = require('mongoose')
const { Schema } = mongoose
const numberLogSchema2 = new Schema({
field1: {
type: Number,
required: true,
default: 0
},
field2: {
type: Number,
required: true,
default: 0
},
field3: {
type: Number,
required: true,
default: 0
},
field4: {
type: Number,
required: true,
default: 0
}
})
module.exports = mongoose.model('numberLogs2',numberLogSchema2)
And its corresponding route paths, could look like this:
//if you intend to just increase the logvalue hourly
//without keeping track of it previous value then we use $inc
router.post('/numberLog2', async (req, res) => {
try {
const saveNumberLog2 = await numberLog2.updateOne(
{ _id: numberCollectionIdhere },
{
$inc: {
field1: req.body.fieldLogValue
}
})
res.json(saveNumberLog2)
}catch (err) {
res.json({
message: err
})
}
})

Store value of a subquery - mongoose

What im doing:
When I call getData() the backend server .find() all my data.
My documents:
My test document has an _id a name and stuff fields. The stuff field contains the _id to the data document.
My data document has an _id and a age field
My goal:
When I send the data to the frontend I don´t want the stuff field to appear with the _id, I want it to appear with the age field from the correspondingdata.
What I have:
router.route('/data').get((req, res) => {
Test.find((err, aval) => {
if (err)
console.log(err);
else{
var result = [];
aval.forEach(e => {
var age;
// Get the age, only 1
Data.findById(e.stuff, 'age', function (err, a) {
age = a.age;
});
result.push({name: e.name, age: age});
});
res.json(result);
}
});
});
I find all the test documents then, for each one of them, I find the age and put the result in the array. Finaly I send the result array.
My problem:
The age field on my result array is always undefined, why? Any solutions?
UPDATE 1 - The schemas
The test schema
var TestSchema = new Schema(
{
stuff: {type: Schema.Types.ObjectId, ref: 'Data', required: true},
name: {type: String, required: true}
}
);
The data schema
var DataSchema = new Schema(
{
age: {type: Number, required: true}
}
);
router.route('/data').get((req, res) => {
Test.find({})
.populate('stuff')
.exec((err, aval) => {
if (err) console.log(err);
res.json(aval);
});
});
Mongoose model has a populate property that uses the value in the model attribute definition to get data matching the _id from another model.
It's a scop problem with your code try this out :
Data.findById(e.stuff, 'age', function (err, a) {
result.push({name: e.name, age: a.age});
});
But as a better solution think to use the Aggregation Framework

Saving data to array in mongoose

Users are able to post items which other users can request. So, a user creates one item and many users can request it. So, I thought the best way would be to put an array of users into the product schema for who has requested it. And for now I just want to store that users ID and first name. Here is the schema:
const Schema = mongoose.Schema;
const productSchema = new Schema({
title: {
type: String,
required: true
},
category: {
type: String,
required: true
},
description: {
type: String,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
requests: [
{
userId: {type: Object},
firstName: {type: String}
}
],
});
module.exports = mongoose.model('Product', productSchema);
In my controller I am first finding the item and then calling save().
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findById(productId).then(product => {
product.requests.push(data);
return product
.save()
.then(() => {
res.status(200).json({ message: "success" });
})
.catch(err => {
res.status(500).json({message: 'Something went wrong'});
});
});
};
Firstly, is it okay to do it like this? I found a few posts about this but they don't find and call save, they use findByIdAndUpdate() and $push. Is it 'wrong' to do it how I have done it? This is the second way I tried it and I get the same result in the database:
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findByIdAndUpdate(productId, {
$push: {requests: data}
})
.then(() => {
console.log('succes');
})
.catch(err => {
console.log(err);
})
};
And secondly, if you look at the screen shot is the data in the correct format and structure? I don't know why there is _id in there as well instead of just the user ID and first name.
Normally, Developers will save only the reference of other collection(users) in the collection(product). In addition, you had saved username also. Thats fine.
Both of your methods work. But, second method has been added in MongoDB exactly for your specific need. So, no harm in using second method.
There is nothing wrong doing it the way you have done it. using save after querying gives you the chance to validate some things in the data as well for one.
and you can add additional fields as well (if included in the Schema). for an example if your current json return doesn't have a field called last_name then you can add that and save the doc as well so that's a benefit..
When using findById() you don't actually have the power to make a change other than what you program it to do
One thing I noticed.. In your Schema, after you compile it using mongoose.modal()
export the compiled model so that you can use it everywhere it's required using import. like this..
const Product = module.exports = mongoose.model('Product', productSchema);

Correct way to determine discriminator type after find

I'd like to use the correct mongoose model to represent a document which has several discriminators.
Say I have the following model:
const membershipSchema = mongoose.Schema({
name: { type: String, required: true },
course: { type: mongoose.Schema.Types.ObjectId, ref: 'Course' },
price: { type: Number, required: true, get: p => `£${p}.00` }
}, { discriminatorKey: 'type' });
const subscriptionSchema = mongoose.Schema({
frequency: {
type: String, enum: [ 'week' , 'month' ],
required: true, get: val => `${val}ly subscription` }
})
const ticketSchema = mongoose.Schema({
frequency: { type: String, default: 'one-off' }
});
const Membership = mongoose.model('Membership', membershipSchema);
const Subscription = Membership.discriminator('Subscription', subscriptionSchema);
const Ticket = Membership.discriminator('Ticket', ticketSchema);
module.exports = { Membership, Ticket, Subscription };
When I .find a set of memberships, I don't know in advance whether they are tickets or subscriptions.
I am currently doing something like this to find the subscriptions a particular user has:
const { Membership, Subscription, Ticket } = require('./membership');
return Membership.find({ user: this._id });
But the Membership model doesn't have the correct getters, which are specified on the discriminators, Subscription and Ticket.
I know I have the type key to tell me whether a particular document is a Subscription or a Ticket.
But what is the correct way of getting each document to be represented with the correct model?
I've solved this the hard way, but this cannot be the right way to do it. In the absence of any documentation to help though, this way will have to do:
const { Subscription, Ticket } = require('./membership');
return Promise.all([
// Find each type of discriminator in turn
Subscription.find({ course: this._id }),
Ticket.find({ course: this._id }),
]).then(membershipArray =>
// Combine the resulting arrays into a single result array
membershipArray.reduce((combined, array) => combined.concat(array), [])
);
Improvements/suggestions welcome.
Update
The mongoose API make model.discriminators available, although this appears to be completely undocumented.
We can use this to call find() for every discriminator using map:
const discriminators = Membership.discriminators;
return Promise.all(Object.keys(discriminators).map(i => discriminators[i].find({ course: this._id }))
).then(promiseResults =>
promiseResults.reduce((arr, el) => arr.concat(el), [])
);
This is a fairly straightforward way to get the correct discriminator every time, assuming that none of your documents are stored against the parent model.
If they are, it is fairly straightforward to append the parent model to the discriminators object before running the Promise.all line.

Mongoose query nested document returns empty array

I have these schemas:
var Store = mongoose.model('Store', new Schema({
name: String
}));
var Client = mongoose.model('Cllient', new Schema({
name: String,
store: { type: Schema.ObjectId, ref: 'Store' }
}));
var Order = mongoose.model('Order', new Schema({
number: String,
client: { type: Schema.ObjectId, ref: 'Client' }
}));
I'm trying to code the Url handler of the API that returns the order details, which looks like this:
app.get('/api/store/:storeId/order/:orderId', function (...));
I'm passing the store id in the Url to quickly check if the logged user has permissions on the store. If not, it returns a 403 status. That said, I think this storeId and the orderId are enough data to get the order, so I'm trying to do a query on a nested document, but it just doesn't work.
Order.findOne(
{ 'client.store': req.params.storeId, _id: req.params.orderId },
function (err, order) { ... });
But the order object is null;
Even when I perform a find, it returns an empty array:
Order.find(
{ 'client.store': req.params.storeId },
function (err, results) { ... });
I know that I could as well pass the cliendId to the Url and check first if the client belongs to the store, and then retrieve the order from the client, but I think the client part is redundant, don't you think? I should be able to get the order in a secure way by using only these two fields.
What am I doing wrong here?
Ok, I found it. The secret was in the match option of populate. The final code looks like this:
Order
.findOne({ _id: req.params.orderId })
.populate({ path: 'client', match: { store: req.params.storeId } })
.exec(function (err, order) { ... });

Resources