How can I set composite primary key in mongodb through mongoose - javascript

I want to set primary key for two fields in a collection in mongodb through mongoose. I know to set composite primary key in mongodb as
db.yourcollection.ensureIndex( { fieldname1: 1, fieldname2: 1 }, { unique: true } )
but am using mongoose to handle mongodb I don't know how to set composite primary key from mongoose
update
I used mySchema.index({ ColorScaleID: 1, UserName: 1}, { unique: true });
see my code
var mongoose = require('mongoose')
var uristring ='mongodb://localhost/fresh';
var mongoOptions = { db: { safe: true } };
// Connect to Database
mongoose.connect(uristring, mongoOptions, function (err, res) {
if (err) {
console.log ('ERROR connecting to: remote' + uristring + '. ' + err);
} else {
console.log ('Successfully connected to: remote' + uristring);
}
});
var mySchema = mongoose.Schema({
ColorScaleID:String,
UserName:String,
Range1:Number,
})
mySchema.index({ ColorScaleID: 1, UserName: 1}, { unique: true });
var freshtime= mongoose.model("FreshTimeColorScaleInfo",mySchema)
var myVar = new freshtime({
ColorScaleID:'red',
UserName:'tab',
Range1:10
})
myVar.save()
mongoose.connection.close();
When I execute this code for first time I see a line {"_id":...,ColorScaleID:'red',UserName:'tab',Range1:10 } in mongodb's fresh database. When I execute the same code for second time I see two same lines.
{"_id":...,ColorScaleID:'red',UserName:'tab',Range1:10 }
{"_id":...,ColorScaleID:'red',UserName:'tab',Range1:10 }
If composite primary key worked then it shouldn't allow me to insert same data for second time. what would be the problem?

The way that you have defined your schema is correct and will work. What you are probably experiencing is that the database has already been created and that collection probably already exists even though it might be empty. Mongoose won't retro fit the index.
As an experiment, set your database to a DB that does not exist. e.g.:
var uristring ='mongodb://localhost/randomname';
and then try running those two lines against this database and see if you can still insert those two documents.
Then compare the contents of the "system.indexes" collection in each of those collections. You should see that the randomname db has the composite index correctly set.

As everybody mentioned, you got to use index method of a Schema to set composite unique key.
But this isn't enough, try restarting MongoDB after that.

May be you can try this in your mongoose schema model,
const AppSchema1 = new Schema({
_id :{appId:String, name:String},
name : String
});

Related

Mongoose ODM saves wrong model name in MongoDB collection

Hello Stackoverflowers!
I got a strange issue with Mongoose creating a collection named "Safes".
here is my example code:
const mongoose = require('mongoose')
mongoose.connect('mongodb://mongodb:27017/test', { useNewUrlParser: true })
const Safe = mongoose.model('Safe', { name: String })
const safe = new Safe({ name: 'foobar' })
safe.save().then(() => console.log('done'))
when I open the database shell and issue this command:
mongo test --eval "db.getCollectionNames()"
its responds with:
MongoDB shell version v4.0.6
connecting to: mongodb://127.0.0.1:27017/test?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("f9cfa8b9-58e2-40b8-9907-ecd18039935a") }
MongoDB server version: 4.0.6
[ "saves" ]
Now, I tried to create a model with a collection names "Safes" and mongoose seems to change it from safes > saves ...
Has mongoose some kind of protected models that cannot be used?
Seems like they set a rule on words ending with "fe" because they normally convert to plural as "ves" (knife -> knives).
You can set your own collection name by adding another argument to Schema:
const safeSchema = new Schema({ name: String }, { collection: 'safes' })
Mongooses util.toCollectionName generates a name of the collection based on the Schema name. It does use some regexes, one of them being:
[/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'],
Which maches safe and replaces it with saves.
source

Update Array attribute using Mongoose

I am working on a MEAN stack application in which i defined a model using following schema:
var mappingSchema = new mongoose.Schema({
MainName: String,
Addr: String,
Mapping1: [Schema1],
Mappings2: [Schema2]
},
{collection : 'Mappings'}
);
I am displaying all this data on UI and Mapping1 & Mapping2 are displayed in the 2 tables where I can edit the values. What I am trying to do is once I update the values in table I should update them in database. I wrote put() api where I am getting these two updated mappings in the form of object but not able to update it in database. I tried using findAndModify() & findOneAndUpdate() but failed.
Here are the Schema1 & Schema2:
const Schema1 = new mongoose.Schema({
Name: String,
Variable: String
});
const Schema2 = new mongoose.Schema({
SName: String,
Provider: String
});
and my put api:
.put(function(req, res){
var query = {MainName: req.params.mainname};
var mapp = {Mapping1: req.params.mapping1, Mapping2: req.params.mapping2};
Mappings.findOneAndUpdate(
query,
{$set:mapp},
{},
function(err, object) {
if (err){
console.warn(err.message); // returns error if no matching object found
}else{
console.log(object);
}
});
});
Please suggest the best to way update those two arrays.
UPDATE :
I tried this
var mapp = {'Mapping2': req.params.mapping2};
Mappings.update( query ,
mapp ,
{ },
function (err, object) {
if (err || !object) {
console.log(err);
res.json({
status: 400,
message: "Unable to update" + err
});
} else {
return res.json(object);
}
});
what I got is
My array with size 3 is saved as String in Mapping2 array.
Please help. Stuck badly. :(
From Mongoose's documentation I believe there's no need to use $set. Just pass an object with the properties to update :
Mappings.findOneAndUpdate(
query,
mapp, // Object containing the keys to update
function(err, object) {...}
);

Mongoose find/update subdocument

I have the following schemas for the document Folder:
var permissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
var folderSchema = new Schema({
name: { type: string },
permissions: [ permissionSchema ]
});
So, for each Page I can have many permissions. In my CMS there's a panel where I list all the folders and their permissions. The admin can edit a single permission and save it.
I could easily save the whole Folder document with its permissions array, where only one permission was modified. But I don't want to save all the document (the real schema has much more fields) so I did this:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }, function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
but the problem is that perm is always undefined! I tried to "statically" fetch the permission in this way:
var perm = data.permissions[0];
and it works great, so the problem is that Underscore library is not able to query the permissions array. So I guess that there's a better (and workgin) way to get the subdocument of a fetched document.
Any idea?
P.S.: I solved checking each item in the data.permission array using a "for" loop and checking data.permissions[i]._id == permission._id but I'd like a smarter solution, I know there's one!
So as you note, the default in mongoose is that when you "embed" data in an array like this you get an _id value for each array entry as part of it's own sub-document properties. You can actually use this value in order to determine the index of the item which you intend to update. The MongoDB way of doing this is the positional $ operator variable, which holds the "matched" position in the array:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$": permission
}
},
function(err,doc) {
}
);
That .findOneAndUpdate() method will return the modified document or otherwise you can just use .update() as a method if you don't need the document returned. The main parts are "matching" the element of the array to update and "identifying" that match with the positional $ as mentioned earlier.
Then of course you are using the $set operator so that only the elements you specify are actually sent "over the wire" to the server. You can take this further with "dot notation" and just specify the elements you actually want to update. As in:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$.role": permission.role
}
},
function(err,doc) {
}
);
So this is the flexibility that MongoDB provides, where you can be very "targeted" in how you actually update a document.
What this does do however is "bypass" any logic you might have built into your "mongoose" schema, such as "validation" or other "pre-save hooks". That is because the "optimal" way is a MongoDB "feature" and how it is designed. Mongoose itself tries to be a "convenience" wrapper over this logic. But if you are prepared to take some control yourself, then the updates can be made in the most optimal way.
So where possible to do so, keep your data "embedded" and don't use referenced models. It allows the atomic update of both "parent" and "child" items in simple updates where you don't need to worry about concurrency. Probably is one of the reasons you should have selected MongoDB in the first place.
In order to validate subdocuments when updating in Mongoose, you have to 'load' it as a Schema object, and then Mongoose will automatically trigger validation and hooks.
const userSchema = new mongoose.Schema({
// ...
addresses: [addressSchema],
});
If you have an array of subdocuments, you can fetch the desired one with the id() method provided by Mongoose. Then you can update its fields individually, or if you want to update multiple fields at once then use the set() method.
User.findById(userId)
.then((user) => {
const address = user.addresses.id(addressId); // returns a matching subdocument
address.set(req.body); // updates the address while keeping its schema
// address.zipCode = req.body.zipCode; // individual fields can be set directly
return user.save(); // saves document with subdocuments and triggers validation
})
.then((user) => {
res.send({ user });
})
.catch(e => res.status(400).send(e));
Note that you don't really need the userId to find the User document, you can get it by searching for the one that has an address subdocument that matches addressId as follows:
User.findOne({
'addresses._id': addressId,
})
// .then() ... the same as the example above
Remember that in MongoDB the subdocument is saved only when the parent document is saved.
Read more on the topic on the official documentation.
If you don't want separate collection, just embed the permissionSchema into the folderSchema.
var folderSchema = new Schema({
name: { type: string },
permissions: [ {
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
} ]
});
If you need separate collections, this is the best approach:
You could have a Permission model:
var mongoose = require('mongoose');
var PermissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
module.exports = mongoose.model('Permission', PermissionSchema);
And a Folder model with a reference to the permission document.
You can reference another schema like this:
var mongoose = require('mongoose');
var FolderSchema = new Schema({
name: { type: string },
permissions: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Permission' } ]
});
module.exports = mongoose.model('Folder', FolderSchema);
And then call Folder.findOne().populate('permissions') to ask mongoose to populate the field permissions.
Now, the following:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }).populate('permissions').exec(function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
The perm field will not be undefined (if the permission._id is actually in the permissions array), since it's been populated by Mongoose.
just try
let doc = await Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{ "permissions.$": permission},
);

Mongoose: Finding a user in a collection and checking if this user's array contains x?

I have a collection called 'users' where a typical user entry looks something like this:
{
"__v" : 0,
"_id" : ObjectId("536d1ac80bdc7e680f3436c0"),
"joinDate" : ISODate("2014-05-09T18:13:28.079Z"),
"lastActiveDate" : ISODate("2014-05-09T18:13:48.918Z"),
"lastSocketId" : null,
"password" : "Johndoe6",
"roles" : ['mod'], // I want this to be checked
"username" : "johndoe6"
}
I want to create an if function that finds a user variable targetuser, and checks to see if his 'roles' array contains a 'mod'.
How can this be done with mongoose?
It can be done easily. Code below describes in detail what must be done to achieve this.
Steps:
get mongoose module
connect to mongo and find the right database
make a schema of your collection (in this case only users)
add a custom method that returns true if the role 'mod' exists in the array. Note: mongo collection doesn't have structure, so it might be good to run a check if the property 'roles' exists and it is an array.
model the created schema.
test it by finding random (one) document/user and check if it is a moderator.
So, this is programmed as:
// get mongoose.
var mongoose = require('mongoose');
// connect to your local pc on database myDB.
mongoose.connect('mongodb://localhost:27017/myDB');
// your userschema.
var schema = new mongoose.Schema({
joinDate : {type:Date, default:Date.now},
lastActiveDate: Date,
lastSocketId : String,
username : String,
password : String,
roles : Array
});
// attach custom method.
schema.methods.isModerator = function() {
// if array roles has text 'mod' then it's true.
return (this.roles.indexOf('mod')>-1);
};
// model that schema giving the name 'users'.
var model = mongoose.model('users', schema);
// find any user.
model.findOne({}, function(err, user)
{
// if there are no errors and we found an user.
// log that result.
if (!err && user) console.log(user.isModerator());
});

mongoDB array inside document

When i update this data, the deslon and deslat part is not inserted in the document.
var locationData = { update_time: new Date() ,
location: [
{curlon: req.payload.loclon , curlat: req.payload.loclat},
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]};
the update
userLocationModel.update({uid: req.params.accesskey}, locationData, { upsert: true }, function (err, numberAffected, raw) {
//DO SOMETHING
});
I cannot understand why this is happining.
Here is the mongo document that gets inserted. The deslon and deslat are missing even if a new document is created.
{
_id: ObjectId("52f876d7dbe6f9ea80344fd4"),
location: [
{
curlon: 160,
curlat: 160,
_id: ObjectId("52f8788578aa340000e51673")
},
{
_id: ObjectId("52f8788578aa340000e51672")
}
],
uid: "testuser6",
update_time: ISODate("2014-02-10T06:58:13.790Z")
}
Also : Should I be using a structure like this if the document is updated frequently.
This is the mongoose model:
var userLocationSchema = mongoose.Schema({
uid: String, //same as the user access key
update_time: Date, //time stamp to validate, insert when updating. created by server.
location:[
{
curlon: Number, //current location in latitude and longitude <INDEX>
curlat: Number
},
{
deslon: Number, //destination in latitude and longitude <INDEX>
deslat: Number
}
]
});
I wish to update both of the elemets. I don't wan't to insert a new one. But even when I update a non existent document(ie- which results in the creation of a new one), the deslon and deslat are missing.
I have a real problem with this structure but, oh well.
Your Schema is wrong for doing this. Hence also the superfluous _id entries. To do what you want you need something like this:
var currentSchema = mongoose.Schema({
curlon: Number,
curlat: Number
});
var destSchema = mongoose.Schema({
destlon: Number,
destlat: Number
});
var userLocationSchema = mongoose.Schema({
uid: String,
update_time: Date,
location: [ ]
});
This is how mongoose expects you to do embedded documents. That will allow the update in your form you are using to work.
Also your logic on upsert is wrong as you have not included the new uid that is not found in the updated document part. You should take a look at $setOnInsert in the MongoDB documentation, or just live with updating it every time.
Actually, I'm just pointing you to how to separate the schema. As your usage in code stands location will accept anything by the above definition. See the mongoose docs on Embedded Documents for a more detailed usage.
This will work with your update statement as stands. However I would strongly urge you to re-think this schema structure, especially if you intend to do Geo-spatial work with the data. That's out of the scope of this question. Happy googling.
You have to tell mongo how to update your data. So add a simple $set to your update data:
var locationData = {
$set: {
update_time: new Date(),
location: [
{curlon: req.payload.loclon , curlat: req.payload.loclat},
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]
};
EDIT:
If you do not want to exchange the location property as a whole, but insert a new item into the array, use:
var locationData = {
$set: {
update_time: new Date()
},
$push: {
location: [
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]
};
What you should consider is, if it is a good idea to put the current location and the destinations in one array, just because they have the same properties (lon/lat). If for example, there is always one current location and zero to many destinations, you could put the current location into a separate property.
To modify a specific location within an array, you can address it via.
var index = 2, // this is an example
arrayElement = 'location.' + n,
locationData = { $set: {} };
locationData.$set[arrayElement] = {deslon: req.payload.deslon , deslat: req.payload.deslat};
userLocationModel.update({uid: req.params.accesskey}, locationData );
could it be that the intial collection was built with an other version of the schema? i.e. one that had only curlon and curlat? you may have to update the documents then to reflect the amended schema with the deslon and deslat properties.

Categories

Resources