Mongoose casting array to string when using $set - node.js

I have my model:
var QuestionSchema = new Schema({
title: String,
question: String,
answers: [String],
set_id: String
});
And I update like so:
questionModel.update({
_id: id
}, {
$set: {
title: req.body.title,
question: req.body.question,
answers: req.body.answers
}
}, function (err, numAffected) {
});
I've checked req.body.answers and it is an array, however, it seems to get saved in the DB as foo,bar, as in, a string, not an array!
Any ideas?

answers: req.body.answers[0]
Was the eventual workaround, no idea why!? If anyone can shed any light on why it was coming from a form with inputs: name="answers[]" being passed as [[foo, bar]]...

I suspect that because you've used '[String]' instead of 'Array' in your schema definition, then when you go to update the model, the array is cast to a string, rather than being saved as an array. Try the below:
var QuestionSchema = new Schema({
title: String,
question: String,
answers: Array,
set_id: String
});
It also looks like you would only use brackets around a schema type where you are defining meta properties:
var animalSchema = new Schema({
name: String,
type: String,
tags: { type: [String], index: true } // field level
});

Related

How to define an object in NodeJS schema?

I currently have a schema like this:
const postSchema = mongoose.Schema({
title: String,
message: String,
name: String,
creator: String,
tags: [String],
selectedFile: String,
likes: { type: [String], default: [] },
createdAt: {
type: Date,
default: new Date(),
},
})
One of the problem that I anticipate is that as the number of users grow, searching the likes array will become inefficient. Is there a way to store the likes array instead as an Object (key would be userId and value could be true) so that finding someone in the Object would become more efficient.
I am also open to hearing any other ideas that you might have.
Thanks!
I want to suggest populate() for this. From that, you can manage a large no. of user information without a problem. You can create a new schema as likes and add the id of the likes document as an id with the populate. Check the below example.
const likeSchema = mongoose.Schema({
type: [String],
default: [] },
});
const Like = mongoose.model("Like", likeSchema);
Then create the postschema like below.
const postSchema = mongoose.Schema({
title: String,
message: String,
name: String,
creator: String,
tags: [String],
selectedFile: String,
likes: {
type: mongoose.Schema.Types.String,
ref: 'Like',
},
createdAt: {
type: Date,
default: new Date(),
},
})
const Post = mongoose.model("Post", postSchema);
You can easily get all the data inside a likes document by populating when running a query like below.
const posts = await Post.findById({creator_id}).populate("likes");
//Below code will print the data of the first element of the type array of relevant likes document.
console.log(posts.likes.type[0]);
Check the populate and population sections of the mongoose documentation to learn more.

Is there a way to query a document based on a subdocument object ref?

Suppose we have a schema that looks like this:
const RandomSchema = new Schema({
name: String,
randomField: String,
subDoc: {
name: String,
refDoc: {
type: Schema.Types.ObjectId,
ref: 'OtherModel',
required: true,
},
},
}, options);
Our OtherModel has a schema that looks like this:
const OtherModel = new Schema({
name: String,
funFact: String,
}, options);
From the front end of my application I'd like to query the RandomSchema model and return all instances of this model where subDoc.refDoc.funFact === someValue.
Is this possible? I know we have ways to populate those subdocs when return them but it happens only after matching docs have been returned, when in this case we'd need to know more than just the objectId of refDoc.
If multiple collections are involved, this task requires use of aggregation pipeline.

Is it possible to add fields not described in Mongoose model?

For example I have schema like this:
let mongoose = require('mongoose');
let carSchema = new mongoose.Schema({
url: String,
unique: {type: String, index: { unique: true }},
number: String,
title: String,
price: String,
});
module.exports = mongoose.model('Car', carSchema);
When I'm creating new instance is it possible to add extra fields without describing them in the model? For example:
data.bpm = {foo: 'bar'}
new CarModel(data).save(function (err) {
if (err) {
dd(err)
}
})
You can use the strict: false option.
Documentation:
The strict option, (enabled by default), ensures that values passed to our model constructor that were not specified in our schema do not get saved to the db.
Your updated schema will look like this:
let carSchema = new mongoose.Schema({
url: String,
unique: {type: String, index: { unique: true }},
number: String,
title: String,
price: String,
}, { strict: false });
You can use type 'Schema.Types.Mixed' for some field in your schema:
let mongoose = require('mongoose');
let carSchema = new mongoose.Schema({
url: String,
...
data: Schema.Types.Mixed
});
And then use .data field as js object.
You can not since mongoose schema will check if that attribute exists or not, but what you can do is, you can add the following attribute to carSchema:
externalData: Object
And you can set that data to be anything you want.

Add uppercase: true to mongoose embedded document

If I have two schemas, one which will be embedded in the other:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Will embed this in the personSchema below
var addressSchema = new Schema({
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
});
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressSchema
});
module.exports = mongoose.model("Person", personSchema);
I can't seem to get the uppercase: true to work for embedded documents - no error is thrown, but it simply doesn't uppercase the state property. Or any kind of option like that.
I've been searching the Mongoose docs, but maybe I'm just not finding where it mentions that settings these kinds of additional options on subDocuments won't work.
Up until recently, Mongoose would throw an exception if you tried to directly embed one schema within another like you're doing. It looks like it's partially supported now, but apparently not for cases like this.
You can get this to work by using just the definition object from addressSchema instead of the schema itself in the definition of the address field of personSchema.
var addressObject = {
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
};
var addressSchema = new Schema(addressObject);
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressObject
});
Not positive if this is the best way to do it or not, but I added a pre-save hook (per the suggestion of #nbro in the comments) and that seems to be working:
var addressSchema = new Schema({
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
});
addressSchema.pre("save", function (next) {
this.state = this.state.toUpperCase();
next();
});
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressSchema
});
Update #1:
I seem to be able to find lots of cases of people embedding simple schemas without any additional validation (required: true) or alteration (uppercase: true) occurring. While the above solution does work, it seems kind of unnecessary. What I should probably be doing is just putting in the object literal to embed the info:
var personSchema = new Schema({
...
address: {
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
}
});
It seems like the only good reason to use a separate Schema is if you absolutely need the embedded data to have an _id attribute and you don't need to add additional validation or alteration options to any of the properties. If you need an _id, I'm guessing you should probably not be embedding the data, but saving it as a separate object and making a reference.
I'll keep updating this as I discover new information and best practices.
Update #2:
If you want to include validation to the embedded document, such as making the address property required, you're going to have to do it separately, as outlined in this very good blog post about it.

Mongodb type reference node

I am trying reference another object in a model in node,
User = new Schema({
username: {
type: String,
index: {unique: true}
}
});
Idea = new Schema({
Creator: {
type: User
}
});
but I get this error Undefined type at "creator" Did you try nesting Schemas? You can only nest using refs or arrays. I believe I want to use refs, but could not find documentation on it, can some one help me out. Thanks
I found out the answer to my own question here it is.
User = new Schema({
username: {
type: String,
index: {unique: true}
}
});
Idea = new Schema({
Creator: {
type: Schema.ObjectId,
ref: 'User'
}
});
I'd like to add a reply to this question because it's the first result in Google.
No you can't use Nested Schema as the other replies say. But you can still use the same object in different schema.
// Regular JS Object (Not a schema)
var Address = {
address1: String,
address2: String,
city: String,
postalcode: String
};
var Customer = new Schema({
firstname: String,
lastname: String,
address: Address
});
var Store = new Schema({
name: String,
address: Address
});
That way you can modify the Address Object to make the changes available on all your schemas sharing the object.
Here is link to manual # refs.
Tho You can't use refs at schema design level.
I decided to solve a similar problem for my project by making my subdocument a nested type
Foo = new Schema({
name: String,
bar: {
name: String
}
});
Obviously this will not work if you need Bar to be its own model. Perhaps because you reference it as a model in other objects. In my case this was all I needed to do, but the Subdocuments section of the Mongoose guide does not mention it as an option so I am adding to this discussion.

Resources