Reputation:
I am trying to create an application that the user added to the database a json file. This file has three properties: Name, Id and another value, I need that the id will be unique, and if the user tries to create another user with this id he will get an error message.
I tried to create a unique object, but of course it didn't worked (Using mongoose Scema):
var userSchema = mongoose.Schema({
list_users_ticket: {type: [{
whoChecked_name: String,
whoChecked_id: String,
what_checked: String
}], unique: true},
name : { type: String, required: true } ,
reg_id: { type: String, required: true, unique: true }
});
mongoose.createConnection('mongodb://------------');
var User = mongoose.model('User', userSchema);
// make this available to our users in our Node applications
module.exports = User;
And this is how I use it in dbRequests:
exports.update = function(cur_reg_id, user_ticked) {
User.find({reg_id: cur_reg_id},function(err, users){
//removeusers(cur_reg_id);
if (err) throw err;
var len = users.length;
console.log("Len" + len)
if(len == 0 || len > 1){
callback({'response':"You didn't register"});
} else {
// Here I want to check if the value exist or not, and if yes, update the jsn filed like the code below :)
users[0].list_users_ticket.push(user_ticked);
console.log(users[0])
users[0].save(function(err) {
if (err) throw err;
callback({'response':"User Saved " + user_ticked});
});
}});
//console.log('User successfully updated!');
}
This code does not work for me. I can to iterate through all the elements in this list, but this will consume a lot of power in the server.
Upvotes: 0
Views: 1028
Reputation: 50406
Indexes do not work like that. The intention is that the elements are "unique" thoughout the "collection" and not the document, not certainly not the the array items within a document
For your very simple case of "unique" items with all values to each key of the array sub-document, then the answer is $addToSet
.
Whilst the $addToSet
operator would be the correct thing to do here, Mongoose is presently not playing nicely, and specifically it is re-ordering the keys in the objects to be added, thus making an otherwise identical object no longer identical.
From the MongoDB perpective, if the keys are not in the same order as an object already in the array, then it is not the "same" and a new object would be added. Such re-ordering renders $addToSet
as an operator useless in this case.
The only workaround here is to test for the presence of the object in an array in a way where the order of keys is not important, as is generally the case with query operators. So using an $elemMatch
with a $not
condition should do the trick:
{
"list_users_ticket": {
"$not": {
"$elemMatch": {
"whoChecked_name": "bob",
"whoChecked_id": "1",
"what_checked": "something"
}
}
}
}
As a complete demonstration:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/usertest');
mongoose.set("debug",true);
var ticketSchema = new Schema({
whoChecked_name: String,
whoChecked_id: String,
what_checked: String
},{ "_id": false });
var userSchema = new Schema({
list_users_ticket: [ticketSchema]
});
var User = mongoose.model( "User", userSchema );
var userData = {
whoChecked_name: "bob",
whoChecked_id: "1",
what_checked: "something"
};
var query = {
"list_users_ticket": {
"$not": {
"$elemMatch": userData
}
}
};
function logger(data) {
return JSON.stringify( data, undefined, 2 );
}
async.waterfall(
[
function(callback) {
User.remove({},function(err) {
callback(err);
})
},
function(callback) {
var user= new User({ "list_users_ticket": userData });
user.save(function(err,user) {
callback(err,user);
});
},
function(user,callback) {
console.log("Previous State: %s", logger(user));
query["_id"] = user._id;
User.findOneAndUpdate(
query,
{ "$push": { "list_users_ticket": userData } },
{ "new": true },
function(err,doc) {
if (err) callback(err);
console.log("Modified: %s", logger(doc));
callback();
}
);
},
function(callback) {
User.findOne({},function(err,user) {
if (err) callback(err);
console.log("Current: %s", logger(user));
callback();
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
With output:
Mongoose: users.remove({}) {}
Mongoose: users.insert({ _id: ObjectId("55cffae0b5fd68c324e6934b"), list_users_ticket: [ { whoChecked_name: 'bob', whoChecked_id: '1', what_checked: 'something' } ], __v: 0 })
Previous State: {
"__v": 0,
"_id": "55cffae0b5fd68c324e6934b",
"list_users_ticket": [
{
"whoChecked_name": "bob",
"whoChecked_id": "1",
"what_checked": "something"
}
]
}
Mongoose: users.findAndModify({ _id: ObjectId("55cffae0b5fd68c324e6934b"), list_users_ticket: { '$not': { '$elemMatch': { whoChecked_name: 'bob', whoChecked_id: '1', what_checked: 'something' } } } }) [] { '$push': { list_users_ticket: { what_checked: 'something', whoChecked_id: '1', whoChecked_name: 'bob' } } } { new: true, upsert: false, remove: false }
Modified: null
Mongoose: users.findOne({}) { fields: undefined }
Current: {
"_id": "55cffae0b5fd68c324e6934b",
"__v": 0,
"list_users_ticket": [
{
"whoChecked_name": "bob",
"whoChecked_id": "1",
"what_checked": "something"
}
]
}
The check in here essentially makes sure that no matching element is found in the array for the data to be inserted and hence otherwise will not match the document at all. Whilst this is not exactly the same behaviour as an $addToSet
operation, the result is the same in terms of whether the item is added to the array or not.
Depending on your needs, this may or may not require a little more work, but it is the workaround until something is done to preserve and not alter the the order of object keys when performing an $addToSet
operation such as this.
A little demonstration assuming User
as the model to the associated schema, and that the "index" constraint is removed and purged from the database. The async.waterfall helps with the flow here as well:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/usertest');
mongoose.set("debug",true);
var ticketSchema = new Schema({
whoChecked_name: String,
whoChecked_id: String,
what_checked: String
},{ "_id": false });
var userSchema = new Schema({
list_users_ticket: [ticketSchema]
});
var User = mongoose.model( "User", userSchema );
var userData = {
whoChecked_name: "bob",
whoChecked_id: "1",
what_checked: "something"
};
function logger(data) {
return JSON.stringify( data, undefined, 2 );
}
async.waterfall(
[
function(callback) {
User.remove({},function(err) {
callback(err);
})
},
function(callback) {
var user= new User({ "list_users_ticket": userData });
user.save(function(err,user) {
callback(err,user);
});
},
function(user,callback) {
console.log("Previous State: %s", logger(user));
User.findByIdAndUpdate(user._id,
{ "$addToSet": { "list_users_ticket": userData } },
{ "new": true },
function(err,doc) {
if (err) callback(err);
console.log("Modified: %s", logger(doc));
callback();
}
);
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
And that will show that when you add the "exact same detail" to the member of the"set" then the result is that nothing additional is added.
So you dot not use indexes for this, but rather control logic as demonstrated.
Big Edit
Another Huge Mongoose bug. Here are the details on a current mongoose release from the listed code:
Mongoose: users.remove({}) {}
Mongoose: users.insert({ _id: ObjectId("55cf60bc5fd3f5a70937190b"), list_users_ticket: [ { whoChecked_name: 'bob', whoChecked_id: '1', what_checked: 'something' } ], __v: 0 })
Previous State: {
"__v": 0,
"_id": "55cf60bc5fd3f5a70937190b",
"list_users_ticket": [
{
"whoChecked_name": "bob",
"whoChecked_id": "1",
"what_checked": "something"
}
]
}
Mongoose: users.findAndModify({ _id: ObjectId("55cf60bc5fd3f5a70937190b") }) [] { '$addToSet': { list_users_ticket: { what_checked: 'something', whoChecked_id: '1', whoChecked_name: 'bob' } } } { new: true, upsert: false, remove: false }
Modified: {
"_id": "55cf60bc5fd3f5a70937190b",
"__v": 0,
"list_users_ticket": [
{
"whoChecked_name": "bob",
"whoChecked_id": "1",
"what_checked": "something"
},
{
"what_checked": "something",
"whoChecked_id": "1",
"whoChecked_name": "bob"
}
]
}
See how the "key order" is different in the newly inserted "set" entry. Still even wondering how MongoDB itself accepts this.
Whereas the standard node driver listing:
var async = require("async"),
mongodb = require("mongodb");
MongoClient = mongodb.MongoClient;
var userData = {
who_checked_name: "bob",
whoChecked_id: "1",
what_checked: "something"
};
MongoClient.connect('mongodb://localhost/usertest',function(err,db) {
function logger(data) {
return JSON.stringify( data, undefined, 2 );
}
var collection = db.collection('users');
async.waterfall(
[
function(callback) {
collection.remove({},function(err) {
console.log("did that");
callback(err);
});
},
function(callback) {
collection.insert({ "list_users_ticket": [userData] },function(err,doc) {
console.log("that too");
callback(err,doc);
});
},
function(user,callback) {
console.log( logger(user) );
collection.findOneAndUpdate(
{ "_id": user._id },
{ "$addToSet": { "list_users_ticket": userData } },
{ "returnOriginal": false },
function(err,doc) {
if (err) callback(err);
console.log( logger(doc) );
callback();
}
);
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
Does things correctly and and expected:
{
"result": {
"ok": 1,
"n": 1
},
"ops": [
{
"list_users_ticket": [
{
"who_checked_name": "bob",
"whoChecked_id": "1",
"what_checked": "something"
}
],
"_id": "55cf62024cceb3ed181a7fbc"
}
],
"insertedCount": 1,
"insertedIds": [
"55cf62024cceb3ed181a7fbc"
]
}
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"value": {
"_id": "55cf62024cceb3ed181a7fbc",
"list_users_ticket": [
{
"who_checked_name": "bob",
"whoChecked_id": "1",
"what_checked": "something"
}
]
},
"ok": 1
}
Upvotes: 1