I am using mongoose with node.js application. I don't want _id field in record.
I am using this code to save my record without _id field. But it is giving error
document must have an _id before saving
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var PlayerSchema = new Schema({
player_id : { type: Number },
player_name : { type: String },
player_age : { type: Number },
player_country : { type: String }
}
, { _id: false }
);
var Player = mongoose.model('Player', PlayerSchema );
var athlete = new Player();
athlete.player_id = 1;
athlete.player_name = "Vicks";
athlete.player_age = 20;
athlete.player_country = "UK";
athlete.save(function(err) {
if (err){
console.log("Error saving in PlayerSchema"+ err);
}
});
I am using mongoose version 3.8.14
Unfortunately, You can not skip having a primary key for the document but you can override the primary key content, you can define your own primary key for each document.
Try the following schema for the same.
var PlayerSchema = new mongoose.Schema({
_id : { type: Number },
player_name : { type: String },
player_age : { type: Number },
player_country : { type: String },
}
);
I have replaced your player_id with _id. Now you have control over the primary key of the document and the system won't generate the key for you.
There are some plugins which can also do the autoincremet for your primary key. https://github.com/chevex-archived/mongoose-auto-increment. You might try these as well.
Also, about the error you are getting :
Any document is an object and should be wrapped inside the curly brackets you can not define two independent object in the same document. So you are getting this error.
Copying an answer given on a similar Github issue comment:
The _id option exists to prevent one being created and assigned to
every sub-document within your document.
So, it's not for this:
var Schema = mongoose.Schema,
ThingSchema = new Schema({
name: String,
categories: [{
label: {
type: String,
uppercase: true
},
value: String
}]
}, {_id: false});
// ^^^^^^^^^
// this won't work
It's for this:
var Schema = mongoose.Schema,
ThingSchema = new Schema({
name: String,
categories: [{
label: {
type: String,
uppercase: true
},
value: String,
_id: false // <--- this works
}]
});
Took me a while but, after running into the same problem, I found the
documentation here.
instade of writing this
{
player_id : { type: Number },
player_name : { type: String },
...}
write this:-
{
_id : { type: Number },
_name : { type: String },
_age : { type: Number },
_country : { type: String }
}
hence _id will be players_id
then further write code like this:-
var athlete = new Player();
athlete._id = 1;
athlete._name = "Vicks";
athlete._age = 20;
athlete._country = "UK";
athlete.save(function(err) {
if (err){
console.log("Error saving in PlayerSchema"+ err);
}
});
Related
Below is my mongoose schema
const mongoose = require('mongoose');
const AccountingCostsSchema = mongoose.Schema(
{
// other properties goes here
accountCosts: {
type: mongoose.Decimal128,
set: (v) =>
mongoose.Types.Decimal128.fromString(parseFloat(v).toFixed(4)),
required: true
}
},
{
collection: 'accountingCosts'
}
);
export = mongoose.model('AccountingCosts', AccountingCostsSchema);
Data in MongoDB
accountingCosts collection
Data in Text mode view
{
"_id" : ObjectId("4e1eb38c2bdea2fb81f5e771"),
"accountId" : ObjectId("4e8c1180d85de1704ce12110"),
"accountCosts" : NumberDecimal("200.00"),
}
Data in Text mode view
My query
db.getCollection('accountingCosts').find({'accountId': '4e8c1180d85de1704ce12110'})
Result from query
"accountCosts": {
"$numberDecimal": "123.00"
}
I tried writing a getter function on schema like i have a setter function. But it is not working
get: function(value) {
return value.toString();
}
My expected output is just a plain property with name and value like below
"accountCosts": "123.00"
I am sure you found a solution, but i will also write it here so who will land here would find a solution.
Your idea to using a getter was right, but maybe you forgot to enable it in the schema, so let' s see how using your code.
You have this schema:
var AccountingCostsSchema = new Schema({
accountId: String,
accountCosts: {
type: Schema.Types.Decimal128,
default: 0
}
});
So when you retrieve it you will get:
{
"_id" : "12345",
"accountId" : "123456",
"accountCosts" : {
"$numberDecimal": "123.00"
}
}
Taking in example that you would get accountCosts as number, so something like that:
{
"_id" : "12345",
"accountId" : "123456",
"accountCosts" : 123
}
We would need a getter as you said, so we will need to add a function of this getter in our model file, let' s say after your schema. This could be your getter function:
function getCosts(value) {
if (typeof value !== 'undefined') {
return parseFloat(value.toString());
}
return value;
};
Now, let' s declare this getter in our schema that will become:
var AccountingCostsSchema = new Schema({
accountId: String,
accountCosts: {
type: Schema.Types.Decimal128,
default: 0,
get: getCosts
}
});
Now mongoose will uderstand how you want that value is returned, but we have to specify that we want it uses the getter. So we have to enable it when we want to retieve the value in json format. We can simply add it as this:
var AccountingCostsSchema = new Schema({
accountId: String,
accountCosts: {
type: Schema.Types.Decimal128,
default: 0,
get: getCosts
}
}, {toJSON: {getters: true}});
And so, when you will retrieve it, you will get this:
{
"_id" : "12345",
"accountId" : "123456",
"accountCosts" : 123,
"id" : "12345"
}
But, whoa (this is not my batman glass!) what is that "id" : "12345"?
According to Mongoose documents:
Mongoose assigns each of your schemas an id virtual getter by default which returns the document's _id field cast to a string, or in the case of ObjectIds, its hexString. If you don't want an id getter added to your schema, you may disable it by passing this option at schema construction time.
How we can avoid it? We can just add id: false in our schema that now will be:
var AccountingCostsSchema = new Schema({
accountId: String,
accountCosts: {
type: Schema.Types.Decimal128,
default: 0,
get: getCosts
},
id: false
}, {toJSON: {getters: true}});
And now you will not see it anymore.
Just a little last tip (no, i have not finished):
What happens if you have an array of objects in your schema like this?
var AccountingCostsSchema = new Schema({
accountId: String,
usersAndCosts: [{
username: String,
accountCosts: {
type: Schema.Types.Decimal128,
default: 0,
get: getCosts
}
],
id: false
}, {toJSON: {getters: true}});
Bad news now: you can not keep on using this schema. Good news now: you can fix it easly. You need to create a new schema for usersAndCosts and then reference it in the parent schema. Let' s see it:
var usersAndCostsSchema = new Schema({
username: String,
accountCosts: {
type: Schema.Types.Decimal128,
default: 0,
get: getCosts
},
id: false
}, {toJSON: {getters: true}});
var AccountingCostsSchema = new Schema({
accountId: String,
usersAndCosts: [usersAndCostsSchema],
id: false
}, {toJSON: {getters: true}});
The result will be the same, your accountCosts will be shown as number, no double ids.
Remember that of course this is to enable getters for toJSON but you could need to enable them for toObject too, as you could need to do the same for setters. But we here talked about getters for JSON.
//MONGOOSE SCHEMA OBJECT
var userSchema = new Schema( {
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
tags:[ {name : String, color : String } ],
bookmarks:[{link : String, tags:[ {name : String, color : String } ]}]
} );
module.exports = userSchema; //Export the userSchema
var UserModel = mongoose.model('UserModel', userSchema ); //Create Model
module.exports = UserModel; //Export the Model
//I CAN DELETE AN ITEM FROM BOOKMARKS ARRAY NO PROBLEM USING
UserModel.findByIdAndUpdate(userId ,{$push : {bookmarks: {link : req.body.link, tags : req.body.tags}}}, function(err, user_data) {
PROBLEM!!
How do I delete a tag from the tags Array, within the bookmarks array given users _id, bookmarks _id and the tag _id or name?
//I have tried variations of the following without success
var update = {bookmarks:[{ _id : bookmarkId},
$pull: {tags:[_id : tagid ] }] };
UserModel.findByIdAndUpdate(userId ,update, function(err, user_data) {
AND
UserModel.findOne( {_id : userId}).select({ bookmarks:[ { $elemMatch: {_id : req.params.bookmarkId}}] }).exec(function(err, user_data)
Initially I was using different Models and subdocuments.
var bookmarkSchema = new Schema( {
link : String,
tags:[tagSchema]
});
var tagSchema = new Schema( {
name : String,
color : String
});
var userSchema = new Schema( {
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
tags:[ {name : String, color : String } ],
bookmarks: [bookmarkSchema]
} );
However, I was unable to delete items from the subdocuments using the $pull command like I used above. So I reverted to just using one schema/model.
This is a very important task to be able to complete and I would be greatful for help.
Thanks Muhammad but I could not get either of the 2 methods to work:
1) Nothing happens to DB and the values of the callbacks are:
*numberAffected: 0
*raw: {"updatedExisting":false,"n":0,"connectionId":322,"err":null,"ok":1}
UserModel.update({bookmarks:req.params.bookmarkId},
{ $pull: {"bookmarks.tags" :{_id:req.body._id, name :req.body.name ,color:req.body.color}}}, function(err, numberAffected, raw) {
2) I had to use the lean() function to convert Mongoose Document data format to normal JSON. Otherwise bookmarks.push({link:user.bookmarks[i].link,_id:user.bookmarks[i]._id,tags:tags})
would not combine properly with:
bookmarks.push(user.bookmarks[i]);
on bookmarks[]
However using the lean() functions means I would not be able to save the data to the DB with .save
UserModel.findById(userId).lean(true).exec(function(err, user) {
delete from sub-Document of JSON doc,
UserModel.update({bookmarks:bookmarkId},{$pull: {"bookmarks.tags" :{_id:tagsId,name :name ,color:color}}}, function(err, user_data) {
or
UserModel.findOnd(userId,function(er,user){
var bookmarks= [];
var tags = [];
for (var i = 0;i<user.bookmarks.length;i++){
if (user.bookmarks[i]._id==bookmarkId)
{
for (var j = 0;j<user.bookmarks[i].tags.length;j++){
if (user.bookmarks[i].tags[j]._id==tagid )
{
}else {
tags.push(user.bookmarks[i].tags[j])
}
}
bookmarks.push({link:user.bookmarks[i].link,_id:user.bookmarks[i]._id,tags:tags})
}
else {
bookmarks.push(user.bookmarks[i]);
}
}
})
Using Mongoose version 3.6.4
Say I have a MongoDB document like so:
{
"_id" : "5187b74e66ee9af96c39d3d6",
"profile" : {
"name" : {
"first" : "Joe",
"last" : "Pesci",
"middle" : "Frank"
}
}
}
And I have the following schema for Users:
var UserSchema = new mongoose.Schema({
_id: { type: String },
email: { type: String, required: true, index: { unique: true }},
active: { type: Boolean, required: true, 'default': false },
profile: {
name: {
first: { type: String, required: true },
last: { type: String, required: true },
middle: { type: String }
}
}
created: { type: Date, required: true, 'default': Date.now},
updated: { type: Date, required: true, 'default': Date.now}
);
And I submit a form passing a field named: profile[name][first] with a value of Joseph
and thus I want to update just the user's first name, but leave his last and middle alone, I thought I would just do:
User.update({email: "joe#foo.com"}, req.body, function(err, result){});
But when I do that, it "deletes" the profile.name.last and profile.name.middle properties and I end up with a doc that looks like:
{
"_id" : "5187b74e66ee9af96c39d3d6",
"profile" : {
"name" : {
"first" : "Joseph"
}
}
}
So it's basically overwriting all of profile with req.body.profile, which I guess makes sense. Is there any way around it without having to be more explicit by specifying my fields in the update query instead of req.body?
You are correct, Mongoose converts updates to $set for you. But this doesn't solve your issue. Try it out in the mongodb shell and you'll see the same behavior.
Instead, to update a single deeply nested property you need to specify the full path to the deep property in the $set.
User.update({ email: 'joe#foo.com' }, { 'profile.name.first': 'Joseph' }, callback)
One very easy way to solve this with Moongose 4.1 and the flat package:
var flat = require('flat'),
Schema = mongoose.Schema,
schema = new Schema(
{
name: {
first: {
type: String,
trim: true
},
last: {
type: String,
trim: true
}
}
}
);
schema.pre('findOneAndUpdate', function () {
this._update = flat(this._update);
});
mongoose.model('User', schema);
req.body (for example) can now be:
{
name: {
first: 'updatedFirstName'
}
}
The object will be flattened before the actual query is executed, thus $set will update only the expected properties instead of the entire name object.
I think you are looking for $set
http://docs.mongodb.org/manual/reference/operator/set/
User.update({email: "joe#foo.com"}, { $set : req.body}, function(err, result){});
Try that
Maybe it's a good solution - add option to Model.update, that replace nested objects like:
{field1: 1, fields2: {a: 1, b:2 }} => {'field1': 1, 'field2.a': 1, 'field2.b': 2}
nestedToDotNotation: function(obj, keyPrefix) {
var result;
if (keyPrefix == null) {
keyPrefix = '';
}
result = {};
_.each(obj, function(value, key) {
var nestedObj, result_key;
result_key = keyPrefix + key;
if (!_.isArray(value) && _.isObject(value)) {
result_key += '.';
nestedObj = module.exports.nestedToDotNotation(value, result_key);
return _.extend(result, nestedObj);
} else {
return result[result_key] = value;
}
});
return result;
}
});
need improvements circular reference handling, but this is really useful when working with nested objects
I'm using underscore.js here, but these functions easily can be replaced with other analogs
So I know how to get a single virtual attribute, as stated in the Mongoose docs:
PersonSchema
.virtual('name.full')
.get(function () {
return this.name.first + ' ' + this.name.last;
});
But what if my schema is:
var PersonSchema = new Schema({
name: {
first: String
, last: String
},
arrayAttr: [{
attr1: String,
attr2: String
}]
})
And I want to add a virtual attribute for each nested object in arrayAttr:
PersonSchema.virtual('arrayAttr.full').get(function(){
return attr1+'.'+attr2;
});
Lemme know if I missed something here.
You need to define a separate schema for the elements of attrArray and add the virtual attribute to that schema.
var AttrSchema = new Schema({
attr1: String,
attr2: String
});
AttrSchema.virtual('full').get(function() {
return this.attr1 + '.' + this.attr2;
});
var PersonSchema = new Schema({
name: {
first: String
, last: String
},
arrayAttr: [AttrSchema]
});
Sure, you can define an extra schema, but mongoose is already doing this for you.
It is stored at
PersonSchema.path('arrayAttr').schema
So you can set a virtual by adding it to this schema
PersonSchema.path('arrayAttr').schema.virtual('full').get(function() {
return this.attr1 + '.' + this.attr2
})
If you want a calculated value from all the array elements here's an example:
const schema = new Schema({
name: String,
points: [{
p: { type: Number, required: true },
reason: { type: String, required: true },
date: { type: Date, default: Date.now }
}]
});
schema.virtual('totalPoints').get(function () {
let total = 0;
this.points.forEach(function(e) {
total += e.p;
});
return total;
});
User.create({
name: 'a',
points: [{ p: 1, reason: 'good person' }]
})
User.findOne().then(function(u) {
console.log(u.toJSON({virtuals: true}));
});
Returns to:
{ _id: 596b727fd4249421ba4de474,
__v: 0,
points:
[ { p: 1,
reason: 'good person',
_id: 596b727fd4249421ba4de475,
date: 2017-07-16T14:04:47.634Z,
id: '596b727fd4249421ba4de475' } ],
totalPoints: 1,
id: '596b727fd4249421ba4de474' }
First of all you should write
this.some_attr instead of some_attr
And you can't acces this.attr because there are in arrayAttr. So you can do for example:
this.arrayAttr[0].attr1 + "." + this.arrayAttr[0].attr2
This is not safe because arrayAttr can be empty
My favorite solution to this is to directly reference the nested schema.
PersonSchema.paths.arrayAttr.schema.virtual('full').get(function() {
return this.attr1 + '.' + this.attr2;
});
Something important to also note is that virtuals are not returned by mongoose schemas by default. As such, make sure to set the stringification properties on the nested schemas.
var options = { virtuals: true };
PersonSchema.paths.arrayAttr.schema.set('toJSON', options);
I have FlashcardSchemas and PackageSchemas in my design. One flashcard can belong to different packages and a package can contain different flashcards.
Below you can see a stripped down version of my mongoose schema definitions:
// package-schema.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var PackageSchema = new Schema({
id : ObjectId,
title : { type: String, required: true },
flashcards : [ FlashcardSchema ]
});
var exports = module.exports = mongoose.model('Package', PackageSchema);
// flashcard-schema.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var FlashcardSchema = new Schema({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});
var exports = module.exports = mongoose.model('Flashcard', FlashcardSchema);
As you can see from the comments above, these two schema definitions belong to separate files and reference each other.
I get an exception stating that PackageSchema is not defined, as expected. How can I map a many-to-many relation with mongoose?
I am new to node, mongoDB, and mongoose, but I think the proper way to do this is:
var PackageSchema = new Schema({
id: ObjectId,
title: { type: String, required: true },
flashcards: [ {type : mongoose.Schema.ObjectId, ref : 'Flashcard'} ]
});
var FlashcardSchema = new Schema({
id: ObjectId,
type: { type: String, default: '' },
story: { type: String, default: '' },
packages: [ {type : mongoose.Schema.ObjectId, ref : 'Package'} ]
});
This way, you only store the object reference and not an embedded object.
You are doing it the right way, however the problem is that you have to include PackageSchema in the the flashcard-schema.js, and vice-versa. Otherwise these files have no idea what you are referencing
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
PackageSchema = require('./path/to/package-schema.js')
var FlashcardSchema = new Schema({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});
You could use the Schema.add() method to avoid the forward referencing problem.
This (untested) solution puts the schema in one .js file
models/index.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
// avoid forward referencing
var PackageSchema = new Schema();
var FlashcardSchema = new Schema();
PackageSchema.add({
id : ObjectId,
title : { type: String, required: true },
flashcards : [ FlashcardSchema ]
});
FlashcardSchema.add({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});
// Exports both types
module.exports = {
Package: mongoose.model('Package', PackageSchema),
Flashcard: mongoose.model('Flashcard', FlashcardSchema)
};
You're thinking of this too much like a relational data store. If that's what you want, use MySQL (or another RDBMS)
Failing that, then yes, a third schema could be used, but don't forget it'll still only be the id of each object (no joins, remember) so you'll still have to retrieve each other item in a separate query.
https://www.npmjs.com/package/mongoose-relationship
##Many-To-Many with Multiple paths
var mongoose = require("mongoose"),
Schema = mongoose.Schema,
relationship = require("mongoose-relationship");
var ParentSchema = new Schema({
children:[{ type:Schema.ObjectId, ref:"Child" }]
});
var Parent = mongoose.models("Parent", ParentSchema);
var OtherParentSchema = new Schema({
children:[{ type:Schema.ObjectId, ref:"Child" }]
});
var OtherParent = mongoose.models("OtherParent", OtherParentSchema);
var ChildSchema = new Schema({
parents: [{ type:Schema.ObjectId, ref:"Parent", childPath:"children" }]
otherParents: [{ type:Schema.ObjectId, ref:"OtherParent", childPath:"children" }]
});
ChildSchema.plugin(relationship, { relationshipPathName:['parents', 'otherParents'] });
var Child = mongoose.models("Child", ChildSchema)
var parent = new Parent({});
parent.save();
var otherParent = new OtherParent({});
otherParent.save();
var child = new Child({});
child.parents.push(parent);
child.otherParents.push(otherParent);
child.save() //both parent and otherParent children property will now contain the child's id
child.remove()
This is the problem of cyclic/circular dependency. This is how you make it work in nodejs. For more detail, check out "Cyclic dependencies in CommonJS" at http://exploringjs.com/es6/ch_modules.html#sec_modules-in-javascript
//------ a.js ------
var b = require('b');
function foo() {
b.bar();
}
exports.foo = foo;
//------ b.js ------
var a = require('a'); // (i)
function bar() {
if (Math.random()) {
a.foo(); // (ii)
}
}
exports.bar = bar;