MongoDB (mongoose): Cast to [string] failed for value - mongodb

There's something wrong happens when I try to update a document in DB.
Here is Schema:
const prodSchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
description: {
type: [String]
},
})
Then I get some product from elsewhere:
const some_product = axios.get(blah blah)
And update mu document.
Note that I set up a condition for a 'description': if it has a null value, it is updated - else it remains the same:
const newProduct = {
$set: {
name: some_product.name,
description: {
$cond: {
if: {
$eq: [null, '$description']
},
then: [some_product.description],
else: '$description'
}
}
}
}
Go update (I use mongoose):
Product.updateOne({name: some_product.name}, newProduct, {some params})
And I see this:
Cast to [string] failed for value
"[{"$cond":{"if":{"$eq":[null,"$description"]},"then":["Here is some
new description of the prosuct"],"else":"$description"}}]" at path
"description"
I think the point is that the type of description in the schema is array of strings, and 'description' field in the requested 'some_product' is just a string. May be this is the issue.
But how do I solve it?
Many thanks.

var description = []
description.push(some_product.description)
const newProduct = [{
$set: {
name: some_product.name,
description: {
$cond: {
if: {
$eq: [null, '$description']
},
then: description,
else: '$description'
}
}
}
}]
put update operation in [] use update pipeline
for example
model.update({},[{$set: ...}])

Related

How can I get count of document in one to many relation with mongoose

I have two schemas.
// tutorial
export const TutorialSchema = new mongoose.Schema({
title: String,
author: String,
tags: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Tag"
}
]
})
// tag
export const TagSchema = new mongoose.Schema({
name: String,
companyId: Number
})
constructor(#InjectModel('Tutorial') private readonly _tutorialModel: Model<any>) { }
I want to get count of tags for each tutorial (in one query). How can I do that ?
I know how to get list of tutorial.
const result = await _tutorialModel.find()
You can use group aggregation in the following way -
_tutorialModel.aggregate([
{
$project: {
title: 1,
_id:1,
numberOfTags: { $cond: { if: { $isArray: "$tags" }, then: { $size: "$tags"
}, else: "NA"} }
}
}
] )
For Size operator you need to ensure first that tags is always an array! If for any reason tags is not present then error will be thrown.
If you make sure tags will be always present and is an array then you can simplify to following -
_tutorialModel.aggregate([
{
$project: {
title: 1,
_id:1,
numberOfTags: {$size: "$tags"}
}
}
] )
Take a look at -
Size aggregation operator -
https://www.mongodb.com/docs/manual/reference/operator/aggregation/size/

Conditional operator in mongoose

I am trying to conditionally execute two different mongoose operators but it just return no error and doesn't work to update the document.
My Schema:
const CartSchema: Schema<Document<ICart>> = new Schema({
clientID: { type: String, required: true },
sourceID: { type: String, required: true },
items: { type: Array },
source: { type: String, required: true },
}, { collection: "carts", timestamps: true });
The way I am trying to implement that:
await CartModel.findOneAndUpdate(
{ sourceID: clientID, 'items.id': Types.ObjectId(itemID) },
{
$cond: {
if: {
$eq: 1,
},
then: { $pull: { 'items.$.id': Types.ObjectId(itemID) }},
else: { $inc: { 'items.$.amount': -1 }},
}
},
{ new: true }
).lean({ virtuals: true })
And I also tried to have this kind of query: { sourceID: clientID } but it didn't help. I thought maybe I could not find the element and it just silently pass through.
The main idea here of what I am gonna do is - have a conditional mongoose request where I'll either remove the object from the array if the current value in the field amount is equal to 1, or decrement the value to -1.
Apparently, the $cond operator works only in the aggregation framework but in my Atlas tier I cannot check if the query works properly, but I suppose it should look something like this:
await CartModel.aggregate([
{ $match: {
sourceID: clientID,
'items.id': Types.ObjectId(itemID)
}
},
{
$cond: {
if: {
$eq: 1,
},
then: { $pull: { 'items.$.id': Types.ObjectId(itemID) }},
else: { $inc: { 'items.$.amount': -1 }},
}
}
])

$in operator not working in MongoDB Aggregation

I'm trying to make a discover page for a social media website. The discover page queries the database for all posts that satisfy four things:
User has not already liked post
Post tags do not violate user's filtered tag content
Post text content does not violate user's filtered post content
And finally the part of the aggregation giving me trouble:
Post tagIds contain a given tagId from user (a post using the same tag that the user already follows)
Here's the function:
const asyncFetchTagPosts = async (
query,
//here's a given tag that a user already follows
tagId,
likedPostIds,
Post,
User,
mongoose,
handleFilterTagRegex,
handleFilterPostContentRegex
) => {
var recastTagId = mongoose.Types.ObjectId(tagId)
var user = await User.findOne({ blogName: query })
var filteredTagRegex = handleFilterTagRegex(user)
var filteredPostContentRegex = handleFilterPostContentRegex(user)
var posts = await Post.aggregate([
{
$lookup: {
from: 'posts',
let: {
likedPostIds: likedPostIds,
tagId: recastTagId,
filteredTagRegex: filteredTagRegex,
filteredPostContentRegex: filteredPostContentRegex
},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $not: { $in: ["$_id", "$$likedPostIds"] } },
{ $not: [
{
$regexMatch: {
input: "$tagTitles",
regex: "$$filteredTagRegex"
}
}
]
},
{ $not: [
{
$regexMatch: {
input: "$allText",
regex: "$$filteredPostContentRegex"
}
}
]
},
{ $or: [
//here's the bad expression, $tagIds won't resolve to an array
{ $in: [ "$$tagId", "$tagIds" ] },
]
}
]
}
}
}
],
as: 'posts'
}
},
{ $unwind: '$posts' },
{ $replaceRoot: { "newRoot": "$posts" } },
{ $sort: { "notesHeatLastTwoDays": -1 } },
{ $limit: 5 }
])
return posts
}
Here's the Post model:
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const options = { discriminatorKey: 'kind' }
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
allText: {
type: String
},
descriptions: [
{
kind: String,
content: String,
displayIdx: Number
}
],
descriptionImages: [
{
type: Schema.Types.ObjectId,
ref: 'Image'
}
],
tagIds: [
{
type: Schema.Types.ObjectId,
ref: 'Tag'
}
],
tagTitles: {
type: String
},
mentions: [
{
type: Schema.Types.ObjectId,
ref: 'Mention'
}
],
notesCount: {
type: Number,
default: 0
},
notesHeatLastTwoDays: {
type: Number,
default: 0
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
},
kind: {
type: String,
default: 'Post'
}
}, options)
const Post = mongoose.model('Post', PostSchema, 'posts')
export default Post;
I keep getting this error:
Error: $in requires an array as a second argument, found: missing
When I comment out the last part of the query the aggregation works. It returns data in this shape:
{
_id: 60c18ee43730198901cfae9b,
descriptionImages: [],
//here's the array I'm trying to get to resolve in the aggregation
tagIds: [],
mentions: [],
notesCount: 1,
notesHeatLastTwoDays: 0,
kind: 'VideoPost',
descriptions: [],
createdAt: 2021-06-10T04:02:44.744Z,
updatedAt: 2021-06-11T08:51:38.166Z,
user: 608f213bb4a094bd91e02936,
videoLink: 60c3241a6c9ed4d1fc908270,
allText: '',
__v: 1,
tagTitles: ''
},
I thought using the $ operator in the aggregation gave me access to each document, does it just not work if you try to use the variable as the first expression?
you need to handle missing "$tagIds" by setting it to empty array []
{
$ifNull: [
"$tagIds",
[]
]
}
https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/
so you pipeline stage would be
{ $or: [
//here's the bad expression, $tagIds won't resolve to an array
{ $in: [ "$$tagId", { $ifNull: [ "$tagIds", [] ] } ] },
]
}

MongoDB Document Validation in Meteor?

How would one approach doing this (https://docs.mongodb.com/v3.2/core/document-validation/):
db.createCollection( "contacts",
{ validator: { $or:
[
{ phone: { $type: "string" } },
{ email: { $regex: /#mongodb\.com$/ } },
{ status: { $in: [ "Unknown", "Incomplete" ] } }
]
}
} )
In this:
// database.js
import { Mongo } from 'meteor/mongo';
export const Test = new Mongo.Collection('Test');
Thanks
you first need to define your schema in meteor.
Lists.schema = new SimpleSchema({
name: {type: String},
incompleteCount: {type: Number, defaultValue: 0},
userId: {type: String, regEx: SimpleSchema.RegEx.Id, optional: true}
});
This example defines a schema with a few simple rules:
We specify that the name field of a list is required and must be a
string.
We specify the incompleteCount is a number, which on insertion is
set to 0 if not otherwise specified.
We specify that the userId, which is optional, must be a string that
looks like the ID of a user document.
It’s pretty straightforward to validate a document with a schema. We can write:
const list = {
name: 'My list',
incompleteCount: 3
};
Lists.schema.validate(list);
In this case, as the list is valid according to the schema, the validate() line will run without problems. If however, we wrote:
const list = {
name: 'My list',
incompleteCount: 3,
madeUpField: 'this should not be here'
};
Lists.schema.validate(list);
Then the validate() call will throw a ValidationError which contains details about what is wrong with the list document.

In MongoDb how to compare two arrays in find() method?

I would like to compare two arrays in find() method. I did do that but it doesn seem to work :
var userOne = group.users[0];
var userTwo = group.users[1];
const selector = {
$and: [{
$or: [{
'users[0]': userOne
}, {
'users[0]': userTwo
}]
}, {
$or: [{
'users[1]': userOne
}, {
'users[1]': userTwo
}]
}, {
type: 0
}]
};
var exists = Groups.find(selector).fetch();
console.log(exists);
When userOne and userTwo exist, it doesnt return anything, but when one of them is undefined it returns all the collection.
Someone can help me please ?
Note:
Ths schema of the collection is :
GroupsSchema = new SimpleSchema({
name: {
type: String,
optional: true
},
description: {
type: String,
optional:true
},
type: {
type: Number
},
users: {
type: [String],
optional: true
} });
No need to use the $or operators here. You could try something like this (See this SO question for reference):
const selector = {
$and: [{
'users': userOne
},
{
'users': userTwo
},
{
type: 0
}]
};
Also, check the values in your database to make sure they are correct.