Unique key only in array of subschemas? - node.js

I have a schemas. One of its keys takes the type of an array of schemas.
let instanceSchema = new Mongoose.Schema({
name: { type: String, required: true, unique: true }
})
let mainSchema = new Mongoose.Schema({
instances: { type: [instanceSchema], required: true }
})
So the mainSchema has a key that is an array of instanceSchemas. I want the behavior to be such that I can have instanceSchemas with the same name if they are members of different mainSchemas, like so:
let MainModel = mongoose.model("MainModel", mainSchema);
// Succeeds
main1 = new MainModel({
"instances": [ {"name": "Instance1"}, {"name": "Instance2"} ];
});
// Succeeds
main2 = new MainModel({
"instances": [ {"name": "Instance1"}, {"name": "Instance2"} ];
});
As shown, we have two named "Instance1" and two named "Instance2" but they are members of different documents (main1 and main2) so my target behavior is that this should be allowed. However, using unique prevents this from happening as Mongoose checks all instanceSchema models. Is there a way to allow duplicates as long as they are members of different documents?

Short answer: no, unique doesn't work like that.
When you tag a field unique in a mongoose schema, it will create a unique index on that field in MongoDB.
MongoDB index entries are made per-document, not per array entry.
For example, if you have an index on {field :1} and insert the document {field:["a","b","a","b","c","d"]}, then the entries in that index for this document will be:
"a"
"b"
"c"
"d"
In MongoDB, when an index is created with the unique: true option, it enforce that any value only appears once #in the index#. This means that the above document would be perfectly acceptable even if the index on {field: 1} were unique.
To quickly demonstrate this, I used your model defined above, and executed:
res1 = await (new MainModel({instances:[{name:"1"},{name:"2"},{name:"1"}]})).save();
console.log("Inserted: ",JSON.stringify(res1));
res2 = await MainModel.collection.getIndexes({full: true});
console.log("Indexes: ",JSON.stringify(res2));
res3 = await (new MainModel({instances:[{name:"3"},{name:"2"},{name:"4"}]})).save();
console.log("Inserted: ",JSON.stringify(res3));
This logged:
Inserted: {"_id":"60d273060ffb1ac8e48359e5","instances":[{"_id":"60d273060ffb1ac8e48359e6","name":"1"},{"_id":"60d273060ffb1ac8e48359e7","name":"2"},{"_id":"60d273060ffb1ac8e48359e8","name":"1"}],"__v":0}
Indexes: [{"v":2,"key":{"_id":1},"name":"_id_"},{"v":2,"unique":true,"key":{"instances.name":1},"name":"instances.name_1","background":true}]
/Users/joe/temp/mongoose/node_modules/mongodb/lib/core/error.js:57
return new MongoError(options);
^
MongoError: E11000 duplicate key error collection: test.mainmodels index: instances.name_1 dup key: { instances.name: "2" }
at Function.create
... snip ... {
driver: true,
index: 0,
code: 11000,
keyPattern: { 'instances.name': 1 },
keyValue: { 'instances.name': '2' }
}
As you can see, it create a unique index on { 'instances.name': 1 }, permitted duplicate entries within a single document, and prohibited an identical entry in the other document.

Related

Storing and querying JSON arrays in Redisjson with nodejs

What I was hoping to do was store an array of objects using RedisJSON very simply and then query that array.
I have something similar to this:
const data = [
{
_id: '63e7d1d85ad7e2f69df8ed6e',
artist: {
genre: 'rock',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6f',
artist: {
genre: 'metal',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6g',
artist: {
genre: 'rock',
},
},
]
then I can easily store and retrieve this:
await redisClient.json.set(cacheKey, '$', data)
await redisClient.json.get(cacheKey)
works great. but now I want to also query this data, I've tried creating an index as below:
await redisClient.ft.create(
`idx:gigs`,
{
'$.[0].artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS',
}
)
and when I try and search this index what I expect is it to return the 2 documents with the correct search filter, but instead it always returns the entire array:
const searchResult = await redisClient.ft.search(`idx:gigs`, '#genre:(rock)')
produces:
{
total: 1,
documents: [
{ id: 'cacheKey', value: [Array] }
]
}
I can't quite work out at which level I'm getting this wrong, but any help would be greatly appreciated.
Is it possible to store an array of objects and then search the nested objects for nested values with RedisJSON?
The Search capability in Redis stack treats each key containing a JSON document as a separate search index entry. I think what you are doing is perhaps storing your whole array of documents in a single Redis key, which means any matches will return the document at that key which contains all of your data.
I would suggest that you store each object in your data array as its own key in Redis. Make sure that these will be indexed by using the GIGS prefix in the key name, for example GIGS:63e7d1d85ad7e2f69df8ed6e and GIGS:63e7d1d85ad7e2f69df8ed6f.
You'd want to change your index definition to account for each document being an object too so it would look something like this:
await redisClient.ft.create(
`idx:gigs`,
{
'$.artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS:',
}
)
Note I also updated your PREFIX to be GIGS: not GIGS - this isn't strictly necessary, but does stop your index from accidentally looking at other keys in Redis whose name begins GIGS<whatever other characters>.

Mongoose: Sort Documents based on array properties

Good morning/afternoon.
I'm trying to sort out documents based on multiple array properties to make a "Ranking" system.
The user Schema that i want to sort:
const UserSchema = new mongoose.Schema(
{
// ... Rest of the code
modules: {
party: Array,
bank: Array,
// ... Rest of the code
},
{ strict: false }
)
Which every new 'partner' added to the user, pushes This to the modules.bank (or party):
{
_id: "some_randomgenerated_id",
id: "id of the monster on the database",
level: Number,
exp: Number,
stats: {
attack: Number
defense: Number,
additional: {
attack: Number,
defense: Number
}
}
I want to sort the Users based on each partner's streght combined, like partner[0].stats.attack + .defense + (additional stats) and so on...
I Tried using aggregate, but haven't got too far with it.

Node Js and Moongoose: How to loop into array of objects and delete a particular object and then update the whole document

My document schema is as follows:
const CollectionSchema = mongoose.Schema({
ImageCategory:{type:String,required:true},
imgDetails: [
{
_id: false,
imageUrl:{type:String},
imageName:{type:String},
imageMimeType:{type:String},
}
],
Date: {
type: String,
default: `${year}-${month}-${day}`,
},
},{timestamps: true,})
So in the database for example one document has multiple images with a single image category. What I am trying to do is I want to delete an object from imgDetails array.
Let me explain my question more precisely: imgDetails is an array
Explanation: I want to loop in imgDetails and then find (where imgageUrl === req.body.imageUrl) if its match delete that whole object which have that req.body.imageUrl and then update the document.
Please guide me on how to write such a query. Regards
Demo - https://mongoplayground.net/p/qpl7lXbKAZE
Use $pull
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
db.collection.update(
{},
{ $pull: { "imgDetails": { imageUrl: "xyz" } } }
)

Mongoose js validation on sub object multiple fields

I am attempting to validate an entire object (multipel fields) within mongoose.
I have the following custom schema definition:
module.exports.ItemQuantityPair = (ItemModel, strictStacking)=>{
return {
type: {
itemID: { type:Number, required:true },
quantity: { type:Number, min:1, default:1 },
},
validate: {
validator: async value => { //stacking
if(!strictStacking || value.quantity===1) //if no strict stacking, or only quantity of 1 don't validate
return true;
let item = await ItemModel.findOne({itemID:value.itemID}); //otherwise get the item and check if its stackable
if(!item.stackable)
return false; //invalid, quantity > 1 and not stackable
else
return true; //valid, quantity > 1 and stackable
},
message: props=>`${props.value} this item was supplied with a quantity larger than 1, but the item was found to not be stackable. Ensure only stackable items have quantity > 1`
},
}
}
Then later in my schema I use this custom object like so:
const AccountSchema = new mongoose.Schema({
inventory: [ItemQuantityPair(ItemModel, true)] //inventory is a list of ItemQuantity pairs
});
When I try this, I'm presented with the error TypeError: Invalid schema configuration: 'undefined' is not a valid type within the array 'inventory' I assume this is because I can't assign an object tp the type field since these are the only built-in types: https://mongoosejs.com/docs/schematypes.html#what-is-a-schematype. Normally I would validate each field separately, but this specific validation requires both fields' values (quantity and itemID) to be determined as valid or not.
How can I cleanly accomplish validation on multiple fields?
Thanks

Mongoose create new data with incremented index-like field

What I want to do:
Whenever I add a new item to the collection (in my case a game), it will have a incremented "index"-like value (in my case I'm naming it index too).
My games collection should looks like:
[
{ "index":0, ... data }
{ "index":1, ... data }
{ "index":2, ... data }
]
The term is so hard to search. I always end up with:
$inc for update. Not this, I want to have incremented number on create.
Schema.index does look like what I want, but somehow it doesn't work at all:
const gameModel = new Schema({
index: {
type: Number,
default: 0
},
players: [{
name: String,
score: Number
}]
}, {
timestamps: {
createdAt: 'date'
}
});
gameModel.index({
index: 1
});
With this I always get index: 0. If I turn off default no index is created.
What do I do now? (I would prefer to keep the _id intact)
You can use a npm package named mongoose-auto-increment which provides this functionality. It is also very easy and well documented

Resources