how to push object ids to another schema - node.js

I have a schema like this:
var MusicSchema = new Schema({
music_genres:[{
type:Schema.Types.ObjectId,
ref:'music_genres',
}],
music: {
type:String,
required:true,
},
});
now, in front-end, i have a multi select where user chooses one or many different music_genres.
so when clicked submit, sometimes I get (if user chose only one genre)
- 5cab466ed076761558a76148 or if multiple - [ '5cab466ed076761558a76148', '5cab4915d076761558a7614a' ].
So, if user chose only 1 genre, it's string but if user chose multiple, it's array.
router.post('/',async (req,res)=>{
const newMusic = new Music();
if(typeof req.body.music_genres === "string") req.body.music_genres = [req.body.music_genres];
for(var i in req.body.music_genres) newMusic.music_genres.push(req.body.music_genres[i]);
await newMusic.save();
Question: I hate when I wrote if statement and checking if it's string, make it array. I also hate for statement. Is there any way to make this code better without if and for loop?

You could use a ternary with the spread operator instead of a loop withpush()
router.post('/',async (req,res)=>{
const newMusic = new Music();
typeof req.body.music_genres === "string" ? newMusic.music_genres =
[req.body.music_genres]: newMusic.music_genres = [...req.body.music_genres];
await newMusic.save();
Example
let foo = 'foo';
let arr = ['a', 'b'];
let bar = [];
a = foo;
typeof a == 'string' ? bar = [a] : bar = [...a];
console.log(bar);

If you want to remove for loop from the code you should use mongoose insertMany function as you can see in its official document here doc, you can pass an array to this function then it will handle the bulk creation.
but for your first issue I think the best approach is make the request body integrated from client side so you can easily make the string an array with one element in it, I think it will be better for the consistency of server side code.

Related

Mongoose findOneAndUpdate() using current data

I am try to track the number of downloads per click on a website.
server.js
router.post("/download", async (req, res) => {
let id = req.body.id;
id = parseInt(id);
let doc = await db.findOneAndUpdate({_id: id}, {downloads: 100});
});
Note: This works
But I'm trying to increase the number by 1 each time.
For example: Let's say the current number of downloads is 5, how do I do it that if there's a post request. The number of downloads increases by 1.
const { body: { id } } = req;
const intCasetedId = parseInt(id);
const retrievedDocument = await db.findOneAndUpdate({ id }, { $inc: { downloads: 1 } });
A couple things are happening here.
First I get the id value from the the req argument using a destructuring assignment.
I use only const to ensure I do not mutate variable values.
I also use the object property value shorthand notation to skip '_id' key in the search query argument. Quoting mongoose documentation:
Issues a mongodb findAndModify update command by a document's _id field. findByIdAndUpdate(id, ...) is equivalent to findOneAndUpdate({ _id: id }, ...).
Then I am using '$inc' operator to increment the downloads field by 1.
I would also highly recommend for you to research eslint

Can't use variables as key, value for mongoose .find()

I'm using mongoose 5.11.4 and trying to find documents. On the official docs they say
MyModel.find({ name: /john/i })
I wanna know how to use variables for "name" & "john" and get the exact thing done. this is an API that I'm working on. filter (name) and the value (john) gonna decide by the frontend user. We should search for a given field using the value. Any suggestions?
let filter = req.params.filter
let value = req.params.value
MyModel.find({ filter : /value/i })
doesn't work
You can use the RegExp object to convert the value to a regular expression.
let regex = new RegExp(`${value}`,'options');
Now you can use it on mongoose query.
model.find({name:regex});
this worked : )
let filter = req.params.filter
let value = req.params.value
const filterParam = {}
filterParam[filter] = { $regex: .*${value}.* }
const suppliers = await Supplier.find(filterParam)

Mongoose, NodeJS & Express: Sorting by column given by api call

I'm currently writing a small API for a cooking app. I have a Recipe model and would like to implement sorting by columns based on the req Parameter given.
I'd like to sort by whatever is passed in the api call. the select parameter works perfectly fine, I can select the columns to be displayed but when I try to sort anything (let's say by rating) the return does sort but I'm not sure what it does sort by.
The code i'm using:
query = Recipe.find(JSON.parse(queryStr));
if(req.query.select){
const fields = req.query.select.split(',').join(' ');
query = query.select(fields);
}
if(req.query.sort){
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort({ sortBy: 1 });
} else {
query = query.sort({ _id: -1 });
}
The result, when no sorting is set: https://pastebin.com/rPLv8n5s
vs. the result when I pass &sort=rating: https://pastebin.com/7eYwAvQf
also, when sorting my name the result is also mixed up.
You are not using the value of sortBy but the string "sortBy". You will need to create an object that has the rating as an object key.
You need the sorting object to look like this.
{
rating: 1
}
You can use something like this so it will be dynamic.
if(req.query.sort){
const sortByKey = req.query.sort.split(',').join(' ');
const sortByObj = {};
sortByObj[sortByKey] = 1; // <-- using sortBy as the key
query = query.sort(sortByObj);
} else {
query = query.sort({ _id: -1 });
}

NodeJS/Mongoose - How to reference individual models after concatenation?

I am concatenating two models (Blogs and Events) so I can display all records from both collections in a single forEach loop:
const blogs = await Blog.find({});
const events = await Event.find({});
const blogsAndEvents = blogs.concat(events);
blogsAndEvents.forEach(function(blogOrEvent) { etc. etc.
However, within the forEach loop, in some cases, I want to still reference the original model it comes from, e.g. 'if Blog, title equals BLOG, if Event, title equals event). I have a workaround for this where I am using a unique field from each model to determine the underlying model, like this:
if (blogOrEvent.blogPost) {
title = "BLOG"
} elseIf (blogOrEvent.eventDate) {
title = "EVENT"
}
This approach doesn't seem very clean to me though as it relies on their being a unique field in each model. It there a better way I can access the original underlying model within the loop? Thanks.
With mongoose documents it can be distinctly verified which Model they belong to using instance of :
const blogs = await Blog.find({});
const events = await Event.find({});
const blogsAndEvents = blogs.concat(events);
blogsAndEvents.forEach(function (blogOrEvent) {
if (blogOrEvent instanceof Blog) {
title = "BLOG";
} else if (blogOrEvent instanceof Event) {
title = "EVENT";
}
});

How to automatically generate custom id's in Mongoose?

I'd like to manage my own _id's through Mongoose/MongoDB - as the default ones are pretty long and consume bandwidth.
The difficulty is that I need to create them (say, by incrementing a counter), but I need to do this in a concurrent environment (Node.JS). Is there a simple example I could follow that creates a schema, with a custom _id and a static method (or anything better) that automatically generates the next unique _id, whenever a new document is created?
You could use Mongo's findAndModify() to generate sequential values. Below is an example of this:
// (assuming db is a reference to a MongoDB database)
var counters = db.collection('counters');
var query = {'name': 'counterName'};
var order = [['_id','asc']];
var inc = {$inc:{'next':1}};
var options = {new: true, upsert: true};
counters.findAndModify(query, order, inc, options, function(err, doc) {
if(err) {
callback(err);
return;
}
var id = doc.next;
callback(null, id);
});
Although generating sequential IDs looks pretty on applications keep in mind that there are some drawbacks to them (e.g. when you need to split your database geographically) which is why Mongo uses the long pseudo-random keys that it does.
As Chad briefly touched on, Mongo implements a uuid system for you, taking into account the timestamp, network address, and machine name, plus an autoincrementing 2 digit counter (in the event that multiple entries with the same timestamp occur). This schema is used to allow distributed databases (ie, running different database instances on different machines) while ensuring that each entry will still have a unique identifier (because the machine name section will be different).
Trying to role out your own schema would likely greatly limit the scalability that mongo provides.
This should work
import { randomString } from '#/helpers'
const taskSchema = new mongoose.Schema({
_id: {
type: String,
unique: true,
default: randomString
},
title: String,
...
})
Random string function
// helpers
export const randomString = (length?: number) => {
let result = ''
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const charactersLength = characters.length
const n = length || 15
for (let i = 0; i < n; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
Tested result
{ "_id": "EIa9W2J5mY2lMDY", ... }

Resources