How to insert data into bridging/mapping table using Sequelize.js - javascript

I am trying to insert data into the bridging/mapping table using sequelize.js
I defined the Articles as-
const Articles = db.define('article', {
"slug": {
type: Sequelize.STRING,
primaryKey: true,
allowNull:false
},
"title": {
type: Sequelize.STRING(50),
allowNull: false
},
"description": {
type: Sequelize.STRING(100),
},
"body": Sequelize.STRING,
})
and Tags as-
const Tags = db.define('tag', {
name: {
type: Sequelize.STRING,
primaryKey: true
}
})
I created a many-to-many association between them as
Articles.belongsToMany(Tags, {through:'article_tags'})
Tags.belongsToMany(Articles, {through:'article_tags'})
This created a table article_tags in my Database along with articles and tags. But now how can I insert data into this article_tags bridging table using sequelize?

You have to model that mapping table as well.... then when you are in the resolver for say something like "tagArticle" you would just call the create function on the mapping table with the id of the article and the id of the tag... here is an example of a resolver for a join table i have where i am joining Dealers to Accounts
createUserAccountDealer: async (_, args, { models }) => {
let returnValue = null;
const { username, input } = args;
try {
returnValue = await models.ProfileDealerInfo.create(input);
if (returnValue && returnValue.dataValues.id) {
const tempJoin = { username, dealer_id: returnValue.dataValues.id };
await models.ProfileAccountDealer.create(tempJoin);
}
} catch (err) {
logger.error(err);
}
return returnValue;
}
The difference for me is that dealer is a child of account, so i do the join record right after creating the child dealer record... Your example is a little diff where tags and articles may already exist and you may just only be associating them meaning the id's would just be passed in the args from the client and you would not have the extra step of waiting to get the id back from the child record create

Related

Using findById to find the id of a schema in an array

Hey I was wondering how do I use findById for a schema inside an array? For example, I have the following Schema:
const GameSchema = new mongoose.Schema({
users: [
{
user: { type: mongoose.Schema.ObjectId, ref: 'User' },
role: {
type: String,
required: true,
enum: ['user', 'moderator', 'creator'],
default: 'user',
},
},
]
}]
I want to find the user with a mongoose function like findById, such as the following:
const user = await game.users.findById({ user: req.user.id })
It doesn't seem to work since users is not a mongodb model. I know I can find the user by using find() like the following:
const user = await game.users.find(
(gameUser) => gameUser.user == req.user.id
)
The only problem is that the type of gameUser and req.user.id is not the same and I can't use '==='. Is there some way to go through the array and use the mongoose function findById?
As docs explains, findById method:
Finds a single document by its _id field
So you have to use findOne() instead of findById().
Also, to return only one field from the entire array you can use projection into find.
Check this example. This query find an object by its id (i.e. user field) and return only the object, not the whole array.
db.collection.find({
"users": { "$elemMatch": { "user": 1 } }
},
{
"users.$": 1
})
Using mongoose you can do:
yourModel.findOne(({
"users": { "$elemMatch": { "user": 1 } }
},
{
"users.$": 1
})).then(result => {
console.log(result)
}).catch(e => {
// error
})

Mongoose conditional populate with findById()

I'm attempting to populate a field only if the target model includes a certain property. Below, I want to populate the Book's product only if the Book's gift property is set to false, but it doesn't seem to work:
//Schema
const bookSchema = new mongoose.Schema({
gift: { type: Boolean, default: false },
date: { type: Date, default: Date.now },
author: { type: [authorSchema] },
product: { type: [productSchema] }
}
// Conditional Populate
result = await Book
.findById(bookID)
.populate("author", "name")
.populate("product", "price", { gift: false } )
[EDIT]:
As suggested by Vinícius Belló, populating an existing document works.
// Conditional Populate
const result = await Book
.findById(bookID)
.populate("author", "name");
if (!result.gift) {
await result
.populate("product", "price")
.execPopulate();
}
You can receive the result without populate product then populate the result variable if gift is false. I recommend you to read this part of mongoose docummentation https://mongoosejs.com/docs/populate.html#populate_an_existing_mongoose_document.

mongoose check if id exists but that id is nested inside an array

When i fetch new alerts, i want to check if the ID of the new alert was already recorded. The issue is that that ID is nested inside an array. There's the alertsDetails array, which contains objects and those objects have an _ID filed which is what i want to check. I am not sure how to achieve that. I got the code below but then i have to iterate over the result to check the exists value. Im sure there must be a better way.
const mongoose = require('mongoose');
const { Schema } = mongoose;
const G2AlertsSchema = new Schema(
{
status: { type: String, required: true },
openDate: { type: Date, required: true },
alertType: { type: Array, required: true },
severity: { type: Array, required: true },
locationName: { type: Array, required: true },
history: { type: Array, required: true },
alertDetails: { type: Array, required: false },
assignedTo: { type: Schema.Types.ObjectId, ref: 'user' },
},
{
timestamps: true,
},
);
const G2Alerts = mongoose.model('G2Alert', G2AlertsSchema);
module.exports = G2Alerts;
This is the code i found on mongodb's website. I just want to see if the ID exists only. Basically when i fetch the new alerts i get an array and i iterate over it, i want to check each item's ID against what's inside the Database. If it's there, skip and go to the next. If it's new, then create a new alert and save it.
const exists = await G2Alerts.aggregate([
{
$project: {
exists: {
$in: ['5f0b4f508bda3805754ab343', '$alertDetails._id'],
},
},
},
]);
EDIT: Another thing. I am getting a eslint warning saying i should use array iteration instead of a for loop. The issue is, i need to use await when looking up the Alert ID. If i use, reduce or filter, i can't use await. If i use async inside the reduce or filter function, then it will return promises in or just an empty array.
This below works, based on the answer provided by Tom Slabbaert
const newAlertsData = [];
for (let item of alertData.data.items) {
const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
if (exists.length === 0) {
newAlertsData.push(item);
}
}
if (newAlertsData.length !== 0) {......
But this does not
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
if (exists.length === 0) {
filtered.push(item);
}
return filtered;
}, []);
You're not far off, here is an example using the correct syntax:
const exists = await G2Alerts.findOne({"alertDetails._id": '5f0b4f508bda3805754ab343'}});
if (!exists) {
... do something
}
This can also be achieve using aggregate with a $match stage instead of a $project stage or even better countDocuments which just returns the count instead of the entire object if you do not require it.
One more thing I'd like to add is that make sure alertDetails._id is string type as you're using string in you're $in. otherwise you'll need to cast them to ObjectId type in mongoose like so:
new mongoose.Types.ObjectId('5f0b4f508bda3805754ab343')
And for Mongo:
import {ObjectId} from "mongodb"
...
new ObjectId('5f0b4f508bda3805754ab343')
EDIT
Try something like this?
let ids = alertData.data.items.map(item => item._id.toString());
let existing = await G2Alerts.distinct("alertsDetails._id", {"alertsDetails._id": {$in: ids}});
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
if (!existing.includes(item._id.toString())) {
return [item].concat(filtered)
}
return filtered;
}, []);
This way you only need to call the db once and not multiple times.
Final code based on the provided answer.
const ids = alertData.data.items.map(item => item._id);
const existing = await G2Alerts.find({ 'alertDetails._id': { $in: ids } }).distinct(
'alertDetails._id',
(err, alerts) => {
if (err) {
res.send(err);
}
return alerts;
},
);
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
if (!existing.includes(item._id.toString()) && item.openDate > dateLimit) {
return [item].concat(filtered);
}
return filtered;
}, []);

How to modify virtual fields from sequelize findAll result?

I have looked everywhere and couldn't find any clear answers for this.
I have a complex findAll() with many inclusions and each with their own virtual fields.
What I want is to modify the virtual fields of the result, however as it is returning the model instance trying to access the virtual fields returns undefined as they are not in the result yet.
I have tried 'raw: true' but this removes all virtual fields and as my data has nested tables which also have their own virtual fields which I need, I cannot do that.
Example models
var Book = sequelize.define('Book', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
title: {
type: DataTypes.STRING
},
author: {
type: DataTypes.STRING
}
//....other columns,
myField: {
type: DataTypes.Virtual,
get() {
return this.getDataValue('title:') + this.getDataValue('author');
})
Getting the data
model.Book.findAll({
limit: 100
})
.then((result) => {
const newBook = result.map(row => {
return {...row, myField: 'setMyOwnValueHere'}
}
return newBook
}
Get model data first : get
model.Book.findAll({
limit: 100
}).then(result => {
const books = result.map(row => {
//this returns all values of the instance,
//also invoking virtual getters
const book = row.get();
book.myField = 'setMyOwnValueHere';
return book;
});
return books;
});

Mongoose: updating array in document not working

I'm trying to update an array in document by adding object if it doesn't exist, and replacing the object in array otherwise. But nothing ($push, $addToSet) except the $set parameter does anything, and $set works as expected - overwrites the whole array.
My mongoose schema:
var cartSchema = mongoose.Schema({
mail: String,
items: Array
});
The post request handler:
app.post('/addToCart', function(req, res) {
var request = req.body;
Cart.findOneAndUpdate({
"mail": request.mail
}, {
$addToSet: {
"items": request.item
}
}, {
upsert: true
},
function(err, result) {
console.log(result);
}
);
res.send(true);
});
The data that I'm sending from the client:
{
"mail":"test#gmail.com",
"item":{
"_id":"59da78db7e9e0433280578ec",
"manufacturer":"Schecter",
"referenceNo":"Daemon-412",
"type":"Gitare",
"image":"images/ba9727909d6c3c26412341907e7e12041507489988265.jpeg",
"__v":0,
"subcategories":[
"Elektricne"
]
}
}
EDIT:
I also get this log when I trigger 'addToCart' request:
{ MongoError: The field 'items' must be an array but is of type object in
document {_id: ObjectId('5a19ae2884d236048c8c91e2')}
The comparison in $addToSet would succeeded only if the existing document has the exact same fields and values, and the fields are in the same order. Otherwise the operator will fail.
So in your case, request.item always need to be exactly the same.
I would recommend creating a model of "item". Then, your cart schema would be like:
var cartSchema = mongoose.Schema({
mail: String,
items: [{
type: ObjectId,
ref: 'item',
}],
});
And let MongoDB determine if the item exist.
this should work you just need to implement objectExits function that test if the item is that one you're looking for :
Cart.findOne({ "mail": request.mail })
.exec()
.then(cart => {
var replaced = cart.items.some((item, i) => {
if (item._id == request.item._id)) {
cart.items[i] = request.item;
return true;
}
})
if (!replaced) {
cart.items.push(request.item);
}
cart.save();
return cart;
})
.catch(err => {
console.log(err)
});

Categories