How do I create mongoose unique index with specific value? - node.js

I have collection named TradeRequest with the following schema
const TradeRequestSchema = mongoose.Schema({
status: {
type: String,
enum: ["opened", "rejected", "accepted"],
default: "opened"
},
_requester: {
required: true,
type: mongoose.Schema.Types.ObjectId
},
_requestedBook: {
required: true,
type: mongoose.Schema.Types.ObjectId
}
});
My database should have only one open request for a book by any person.
So I added a unique index like so -
TradeRequestSchema.index({_requester: 1, _requestedBook: 1, status: 1}, {unique: true});
Problem is this prevents some data to be entered in the database which should be allowed.
For example it prevents my database to have following documents which is fine for my use case -
{_id: 1, _requester: 12345, _requestedBook: 9871212, status: "opened"}
{_id: 2, _requester: 12345, _requestedBook: 9871212, status: "opened"}
But it also prevents the following which is wrong for my use case.
For example if my database already has following documents -
{_id: 3, _requester: 12345, _requestedBook: 9871212, status: "closed"}
{_id: 4, _requester: 12345, _requestedBook: 9871212, status: "opened"}
I cannot change request with id 4 to closed now which is wrong.
Basically what I want is to have a single opened request for a book but multiple closed requests.
How do I achieve this?

Mongodb has many types of indices like unique indices, sparse indices and partial indices. Your case doesn't need just the unique index, it needs unique partial index.
Try creating something like -
db.<<collectionName>>.createIndex(
{_requester: 1, _requestedBook: 1, status: 1},
{unique: true, partialFilterExpression: { "status" : "opened" }}
)
Hope it helps...

Related

Node JS, Mongoose How to query schema via aggregate?

I am new to using MongoDB and I am trying to update update my document using aggregate $set pipeline. However I have been trying this for a few days and there is still no success. I am trying to update by querying ObjectId and replacing matched result (singular) with the key value pair I specified. I tested the aggregate on Mongo Compass and it works. Does anyone know how to use aggregate for Node JS?
updateOne query I tried
let query = {"_id": ObjectId('theObjectIamUpdating')};
response = await newForm.updateOne(query, payload);
aggregate query I tried
response = await newForm.updateOne([
{$match: {"_id": ObjectId(update_id)}},
{$set: {
"data.velo": [
[1, 2, 3], [5, 7]
]
}}
]);
newForm Mongoose Schema
data: {
date: {
type: Array,
required: false,
trim: true,
},
speed: {
type: Array,
required: false,
trim: true,
},
velo: {
type: Array,
required: false,
trim: true,
}
},
calc: {
date: {
type: Array,
required: false,
trim: true,
},
speed: {
type: Array,
required: false,
trim: true,
},
velo: {
type: Array,
required: false,
trim: true,
}
}
UPDATE
I my updateOne() has succeeded, but my documents are not getting updated.
the result I got after logging response that I awaited
Data received {
acknowledged: true,
modifiedCount: 0,
upsertedId: null,
upsertedCount: 0,
matchedCount: 0
}
POST /api/compute/calculate 200 16 - 79.821 ms
Additionally, this is the MongoDB aggregate that worked when I tried on Mongo Compass
pipeline = $match > $set
**$match**
{
"_id": ObjectId('62e2295060280132dbcee4ae')
}
**$set**
{
"data.velo": [
[1, 2, 3], [5, 7]
]
}
where velo is one of the key value pairs in data, and the set result replaced only the data in data.velo.
As prasad_ mentioned in the comment section, I indeed found some mistakes with regards to my syntax of updateOne().
Correct Syntax for updateOne()
let response = await Form_csv_metadata2.updateOne({ '_id': [update_id] }, [
{//delete this line $match: {"_id": ObjectId(update_id)},
$set: {
"data.velo": [[1, 2, 3], [5, 7]]
}}
]);
As the official document has mentioned the parameters are: Query, UpdateData and Option. I made the mistake as my query was placed in my UpdateData() param (the $match). It should have been a param by itself as a query (query uses same syntax as find()). Another note is that if I were to use a pipeline aggregate, it should have been { $match: {...}, $set: {...} } instead of { {$match: {...}}, {$set: {...}} }

Mongoose: Model.find() returns an empty array when filtering by boolean

I have the following schema defined in my app (node.js w/ express):
const ArtistSchema = new mongoose.Schema({
name: {type: String, required: true},
year: {type: Number, required: true},
genres: {type: [String], required: true},
imageUrl: {type: String, required: false},
trending: {type: Boolean, required: false, default: false},
trendingDate: {type: Date, required: false}
});
and a route that is supposed to retrieve those entries, who have trending set to true:
// GET trending
router.get('/trending', (req, res) => {
artist.Artist.find({trending: true}).exec((err, trendingArtists) => {
if (err) {
console.error(err);
res.status(500).json({message: err.message});
return;
}
res.json(trendingArtists);
});
});
However, it always returns an empty array when i try to filter by trending field, even though there are items in my collection that have trending set to true. I have tried wrapping everything in single and double quotes and using 1 instead of true, but no query returns results. Filtering by other fields works just fine, not filtering at all returns all entries as expected.
The entries in mongo shell look like this:
> db.artists.find({},{name:1,trending:1}).pretty()
{
"_id" : ObjectId("5de942074a486e2c21246fb9"),
"name" : "Unwound",
"trending" : "true"
}
{
"_id" : ObjectId("5de942074a486e2c21246fba"),
"name" : "Ladytron",
"trending" : "true"
}
{
"_id" : ObjectId("5de942074a486e2c21246fbb"),
"name" : "Depeche Mode",
"trending" : "true"
}
console.loging the results in the app produces this:
[
{
genres: [ 'post-hardcore', 'noise rock', 'indie rock' ],
trending: true,
_id: 5de942074a486e2c21246fb9,
name: 'Unwound',
year: 1991,
imageUrl: '2a7f00a1f8e0ab37c647600f6abff67e.jpg',
trendingDate: 2019-12-20T18:48:53.000Z
},
{
genres: [ 'synthpop', 'electroclash', 'dream pop' ],
trending: true,
_id: 5de942074a486e2c21246fba,
name: 'Ladytron',
year: 1999,
imageUrl: 'f26cc1ae1fef371793622bd199b4bb52.jpg',
trendingDate: 2019-12-20T18:49:05.000Z
},
{
genres: [ 'synthpop', 'new wave' ],
trending: true,
_id: 5de942074a486e2c21246fbb,
name: 'Depeche Mode',
year: 1980,
imageUrl: 'e5328919dac971af86dd034781a2da71.jpg',
trendingDate: 2019-12-20T18:49:43.000Z
}
]
I am at my wits' end. What could cause filtering by a boolean field to break the query regardless of what i specify as the value (i have tried true, 'true', "true", 1, '1', as well as the falsy counterparts)?
edit: i tried a few things since:
1) filtering the results after the query was executed works fine (i.e just writing res.json(trendingArtists.filter(a => a.trending === true));), though it's obviously not the way i would like to deal with filtering my queries
2) the collection i'm querying was created and edited manually, and not via api that my app implements. If i insert a new entry using a POST request via api, that entry will be returned by the query, provided trending was set to true
3) editing existing entries with PATCH requests where i set trending to false and then back to true also works, though it messes up the trendingDate field that is set to current date each time trending is changed to true
4) if the query works for an entry, it works no matter what i put as the value for the filter, as long as it's truthy for mongoose. .find({trending: true}) works just as well as .find({trending: '1'})
I guess the problem is mongoose not recognizing my manually inserted values as truthy at all, even though theoretically they should be cast to true. My problem is solved, I guess? I do not plan to insert values manually in the future, this was just for testing, and those that few that are inserted can be fixed fairly easily, but i don't think it should matter for mongoose whether i edit the entries manually or via Model.save. It seems like a bug - should i close the question and open a bug report on their github?

Efficiently querying indexed subarray using mongoose

I'm trying to store in MongoDB a collection of cards for each user. A collection is basically an array of owned cards with an associated quantity.
Collection {
user: ...,
cards: [ {cardId: 133, qty: 3}, {cardId: 22, qty: 1} ]
}
I'm building an API with node.js and mongoose where I receive a request in the form of [ {cardId: 133, qty: 2}, {cardId: 111, qty: 4} ].
Now I need to either create the card entry in the cards array if it doesn't exist or update the quantity if it is already there.
I need to do this efficiently as collections may contain thousands of cards so I came up with this Schema:
var OwnedCard = new Schema({
cardId: { type: String, index: true, required: true},
qty: { type: Number, required: true}
});
var Collection = new Schema({
userId: { type: String, index: true },
cards: [OwnedCard]
});
I'm not sure however how to take advantage of the index on cardId to quickly locate and update (or create if missing) cards in the subarray
Essentially, for each { cardId: ..., qty: xxx } in request => find/update, or create the right entry in the cards array.
So far I have (to locate the collection of the user):
Collection.findOne({userId: userId}, function (err, collection) {
var cards = collection.cards; // the cards
});
But I don't want to filter through them as a Javascript object since it doesn't take advantage of the index and might be slow, and instead look for a way to get mongo to retrieve the individual card entry quickly.
Any ideas on how to achieve this?

Unable to update the data in mongodb

I need to update some fields
i am using mongoose driver and express js
schema:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ProfilesSchema = new Schema({
presentRound: {
type: Number,
default: 1
},
scheduleInterviewStatus: {
type: Boolean,
default: false
},
interviewStatus: String,
ratings: [{
round: Number,
rating: Number,
feedback: String,
interviewer: String,
roundStatus: String
}]
});
module.exports = mongoose.model('Profiles', ProfilesSchema);
so in these i need to update by id presentRound scheduleInterviewStatus interviewStatus and roundStatus(in ratings array by matching round number)
Before updating:
presentRound: 1,
scheduleInterviewStatus: true,
interviewStatus: "on-hold",
ratings: [{
round: 1,
rating: 3,
feedback: "good communication skills",
interviewer: "Vishal",
roundStatus: "second opinion"
}]
After Updating:
presentRound: 2,
scheduleInterviewStatus: false,
interviewStatus: "in-process",
ratings: [{
round: 1,
rating: 3,
feedback: "good communicationskills",
interviewer: "Vishal",
roundStatus: "selected"
}]
i have tried to run the query in robomongo first but getting error
Error: Fourth argument must be empty when specifying upsert and multi with an object.
Query:
db.getCollection('profiles').update({
"_id": ObjectId("57a9aa24e93864e02d91283c")
}, {
$set: {
"presentRound": 2,
"interviewStatus":"in process",
"scheduleInterviewStatus": false
}
},{
"ratings.$.round": 1
},{
$set: {
"ratings.roundStatus":"selected"
}
},
{ upsert: true },{multi:true})
I have no idea where i am going wrong
Please help.
Your update statement is incorrect, it has misplaced arguments - you are putting multiple $set operations and options as different parameters to the update method; they should be under separate designated update parameters. The correct Node.js syntax is:
update(selector, document, options, callback)
where selector is an object which is the selector/query for the update operation, document is also an object which is the update document and finally an optionsobject which by default is null and has the optional update settings.
Here you are doing
update(selector, document, selector, document, options, options, callback)
In which mongo is updating the collection using the first two parameters as correct and it naturally throws the error
Error: Fourth argument must be empty when specifying upsert and multi
with an object.
because you have too many incorrect parameters specified.
Also, you have incorrect usage of the positional operator. It should be part of the document to be updated, not in the query.
For the correct implementation, follow this update
db.getCollection('profiles').update(
/* selector */
{
"_id": ObjectId("57a9aa24e93864e02d91283c"),
"ratings.round": 1
},
/* update document */
{
"$set": {
"presentRound": 2,
"interviewStatus": "in process",
"scheduleInterviewStatus": false,
"ratings.$.roundStatus": "selected"
}
},
/* optional settings */
{ upsert: true, multi: true }
)
replace {upsert:true} -> {upsert:true,strict: false}

Mongoose - Array in Schema not working + Mongoose find/match element in array

Issue one
For some reason, when I post to my API using Postman (x-www-form-urlencoded), the Array of strings I created in the Schema (see below under issue 2) aren't being separated when I post using Postman. Either i'm not correctly posting, or I haven't set up my Schema correctly.
However I have tried multiple different ways of doing both. I originally had [String] in the Schema, I tried setting the key in Postman to day[] etc. but nothing seemed to work. Any ideas?
Issue Two
(which cannot be completed until I get to the bottom of issue one.)
I am trying to return all results that contain 'friday' in the 'days' Array in the Schema, but I have searched everywhere and can't find this.
So given the Schema:
var PeopleSchema = new Schema({
name: {
type: String,
required: true
},
days: {type: [String]}
});
and this data:
{
_id: 1,
name: 'Brian Smith',
days: [ 'friday', 'monday', 'tuesday' ],
available: true,
},
{
_id: 2,
name: 'Fred Hill',
days: [ 'friday', 'wednesday', 'saturday' ],
available: true,
}
I would like to find/match all results with say, 'friday' in the days Array. How would I do this?
Thanks very much

Resources