How to modify virtual fields from sequelize findAll result? - javascript

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;
});

Related

Node.js: Mongoose filter data from array of ObjectIDs stored in collection [duplicate]

This question already has answers here:
mongodb/mongoose findMany - find all documents with IDs listed in array
(9 answers)
Closed 3 months ago.
I am trying to search using node.js, ejs and mongoose. All the filter parameters are working perfectly but only categoryIds is not (stored as a collection of ObjectIDs in the mongodb document, referring to the respective document in categories collection), always giving me the empty record set.
For example:
If I need to find the a movie called Cosmos (see the attached screenshot) then I can easily find it with all or any filter except categories. Once I select any category, the record-set will go blank even if the I have selected the one which it belongs to.
model.js
const Model = mongoose.model('Movie', new Schema({
...
categoryIds: [{
type: Schema.Types.ObjectId,
trim: true,
default: null,
ref: 'Category',
}],
copyrightId: {
type: Schema.Types.ObjectId,
trim: true,
default: null,
ref: 'Copyright',
},
...
}, {
timestamps: true
});
Controller.js
Router.get('/', (req, res) => {
const search = req.query;
const conditions = (() => {
let object = {};
['releaseYear', 'languageId', 'copyrightId'].forEach(filter => {
if (search[filter] != '') {
object[filter] = search[filter];
}
});
if (typeof search.categoryIds !== 'undefined') {
object.categoryIds = [];
search.categoryIds.forEach(item => object.categoryIds.push(item));
}
if (search.keywords != '') {
object.title = {
$regex: search.keywords,
$options: 'i'
};
}
return object;
})();
const count = await Model.count(conditions);
const items = await Model.find(conditions, {
__v: false,
imdb: false,
trailer: false,
createdAt: false,
updatedAt: false,
}).sort({
status: -1,
releaseYear: -1,
title: 1
})
.populate('languageId', ['title'])
.populate('copyrightId', ['title'])
.populate('categoryIds', ['title'])
.skip(serialNumber)
.limit(perPage);
...
});
All the fields in the search form
{
categoryIds: [
'6332a8a2a336e8dd78e3fe30',
'6332a899a336e8dd78e3fe2e',
'6332a87ba336e8dd78e3fe2c',
'634574ab339b1a6b09c1e144'
],
languageId: '',
copyrightId: '',
releaseYear: '',
rating: '',
seen: '',
status: '',
keywords: '',
submit: 'search' // button
}
filtered search parameters
{
categoryIds: [
'6332a8a2a336e8dd78e3fe30',
'6332a899a336e8dd78e3fe2e',
'6332a87ba336e8dd78e3fe2c',
'634574ab339b1a6b09c1e144'
]
}
Here is the screenshot of mongodb document.
...
if (typeof search.categoryIds !== 'undefined') {
object.categoryIds = {
$in: []
};
search.categoryIds.forEach(item => object.categoryIds.$in.push(
mongoose.Types.ObjectId(item))
);
}
console.log(object);
return object;
The is the final filter object
{
categoryIds: {
'$in': [
new ObjectId("6332a87ba336e8dd78e3fe2c"),
new ObjectId("634669f4a2725131e80d99f1")
]
}
}
Now, all the filters are working perfectly.
Thank you everyone.
The filter should contain all categoryIds and in the same order to match the document. It's not quite clear from the question if it is the intended functionality. If not, most popular usecases are documented at https://www.mongodb.com/docs/manual/tutorial/query-arrays/
I don't recall how mongoose handles types when you query with array function like $all, so you may need to convert string IDs to ObjectIDs manually, e.g.:
search.categoryIds.forEach(item => object.categoryIds.push(
mongoose.Types.ObjectId(item))
);

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 insert data into bridging/mapping table using Sequelize.js

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

GraphQL - passing an object of non specific objects as an argument

I am very new to GraphQL. I'm trying to pass an object like this one as an argument:
{
filters: {
status: 'approved',
id: {
LESS_THAN: 200
}
}
}
Or this object can be like this either;
{
filters: {
status: ['approved', 'pending'],
id: 200
}
}
I know all properties that can be in this object, but all of these properties can be a string/int or an object.
I tried to define it like this but it obviously didn't work:
args: {
filters: { type: new GraphQLNonNull(new GraphQLNonNull(GraphQLString)) },
},
I'm trying to define the argument with a GraphQL type GraphQLInputObjectType.
const OffersFiltersType = new GraphQLInputObjectType({
name: 'Filters',
description: '...',
fields: () => ({})
id: {
type: new GraphQLNonNull({
name: 'Id',
description: '...',
fields: {
}
}),
resolve: (offer) => offer.id
},
}),
});
But how can i specify to this type that my id can be either a int or an object?
This is my Query definition:
const QueryType = new GraphQLObjectType({
name: 'Query',
description: '...',
fields: () => ({
offers: {
type: OffersType,
args: {
limit: { type: GraphQLInt },
page: { type: GraphQLInt },
sort: { type: GraphQLString },
filters: { [HERE] }
},
resolve: (root, args, context, info) => {
const gqlFields = graphqlFields(info);
const fields = Object.keys(gqlFields.offer);
const queryArgs = args;
queryArgs.fields = fields;
return getOffers(queryArgs);
}
},
}),
});
And this is my request with superagent
const getOffers = (args) => {
const queryArgs = args;
if (typeof queryArgs.limit !== 'undefined') {
queryArgs.limit = args.limit;
} else {
queryArgs.limit = Number.MAX_SAFE_INTEGER;
}
return new Promise((fulfill, reject) => {
request
.get(API_URL)
.query(qs.stringify(args))
.end((err, res) => {
if (err) {
reject(err);
}
fulfill(res);
});
});
};
I need this object to construct a query in my resolve function. Thank you all for your help! I only need simple advices!
This is not allowed, by design: https://github.com/graphql/graphql-js/issues/303
GraphQL does not support unknown property names, largely because it would make the schema meaningless. The example given is a simple typo:
If you have the query query ($foo: String) { field(arg: $foo) } and the variables { "fooo": "abc" }, we currently flag this as an error, but we could potentially miss this typo if we did not raise errors.
The schema is meant to ensure compatibility between servers and clients, even across versions, and allowing unknown properties would break that.
There is a merge request open for this in the GraphQL-JS repo, but it is still being debated and has the same problems with typos and general inconsistency.
The idea of returning a primitive or object runs into a similar problem. When accepting an object, you need to list the properties you're expecting and the query will validate those against the schema. The properties, and their types and null-ness, must be known ahead of time for you (and the parser) to build the query and definitely need to be known when you validate.
If you could accept a primitive or object, you would have to specify the fields on that object, but those could not possibly exist on the primitive. That's a problem.

Categories