Insert a document with mongoose without initialize the model with empty attributes - node.js

I want to insert a document in my database from a website form. I have a model created with mongoose and I want to save in the database only the attributes that contains data and I don't want to save empty attributes.
This is my model:
const localizationSchema = new Schema({
name: { type: String, required: true },
spins: [{ type: String }],
spinsForChild: [{ type: String }],
parent: { id: String, name: String },
localizationType: { type: String },
count: { type: Number, default: 0 },
countries: [{ id: String, name: String, cities: [{ id: String, name: String }] }]
});
const Localization = mongoose.model('Localization', localizationSchema);
When I try to save a new document, it creates in the database all attributes although I don't send it on my query.
Localization.create({
name: body.name,
localizationType: body.localizationType,
"parent.id": parent.id,
"parent.name": parent.name,
spins: spins,
spinsForChild: spinsForChild
}, function(err) {
if (err) return handleError(err);
res.redirect('/localizations');
});
This code, for example, inserts in the DB an empty array called "countries".
I tried to use strict: false in the model declaration but it didn't works.

You could use this answer.
But, thing you try to implement seems to be anti-pattern and can cause errors when you will try to use array update operators with undefined array. So, use it carefully.
Good luck!

Related

How to inset multiple documents in mongoose with mongoose required validation?

I wanted to insert multiple documents in my MongoDB collection. I was able to do that by using Model.collection.insert function but when I insert those data it skip/bypass required validation.
I've tried Model.collection.insert([{data: '1'}, {data: '2'}, {type: '3'}]) but this way it's skip or bypass the validation. I want data field required and I used in my Schema that as required. But that's not working.
There is my schema that required a field.
export const SubjectSchema = new mongoose.Schema({
title: { type: String, required: [true, "title field required"] },
groups_id: { type: String },
class_id: { type: String },
meta: { type: Object }
},
{ timestamps: true })
Here is my function
async createSubject(body) {
let result = SubjectSchema.collection.insert(body)
return result
}
I want multiple data to be stored and in each record, title field should be required
Model.insertMany([{data: '1'}, {data: '2'}, {type: '3'}])
you can find the insertMany ref here
however, you can also db.collection.validate()

Aggregate Data into Array MongoDB, Mongoose, Node

Ok, here is what I am trying to do. I have a user schema which has a team key and an array as its value. For example:
user = {
team : Array
}
The values of the array are strings (team names). I have a team schema that has the team information, such as roster, state, etc. Here is my question. If my user has more than one team name in the array, how then would I aggregate all the data to one variable and then pass it to my view. I thought I could just use a for loop, but the view seems to render before the aggregation is done. Here is what I tried:
// if there is there is more than one team name in the user's team array
if( data[0].teams.length > 1 ){
for(var i = 0; i < data[0].teams.length; i++){
// for each team name in the user array
// find the corresponding team and add that data to the array called team
Team.find( { team : data[0].teams[i] }, function(err, data){
if(err){
throw err;
}
team.push(data);
});
}
// render the view
res.render('coach', {
user: req.user,
fname: self.fname,
lname: self.lname,
email: self.email,
phone: self.phone,
address: self.address,
state: self.state,
teams : [team]
});
}
Edit (added schemas)
User schema ( look in teams array, find corresponding teams, return data from team schema based on the name in the user schema teams array )
var userSchema = new Schema({
username : { type: String, required: true, index: { unique: true } },
fname : { type: String, required: true },
lname : { type: String, required: true },
password : { type: String, required: true },
type : { type: String, required: true },
phone : String,
address : String,
state: String,
conference: String,
email : { type: String, required: true, index: { unique : true } },
teams: Array,
});
Team Schema:
var teamsSchema = new Schema({
coach: String,
state: String,
conference: String,
team: String,
roster: [{fname: String, lname: String, number: String}]
});
Now that you have posted your schema It is clear what you are trying to do and also clear how to show your various approaches to do this much better.
The first and basic case is that you seem to have an array of "teams" containing the "String" value for the "team name" presumably stored on your user object. This appears to work for you and does have the advantage of having those names accessible as you retrieve the user.
The thing is that you are iterating over the results and issuing a .find() for every element in the array, which is not the most efficient way. You can basically use the $in operator with your query and existing data and get around what you probably tried to do in merging the full "team data" to the user object by calling .toObject() to transform the mongoose document to an plain JavaScript object:
User.findById( req.user, function(err,user) {
var data = user.toObject();
Team.find({ "team": "$in": data.teams }, function(err,teams) {
data.teams = teams;
res.render( 'coach', data );
});
});
That is the simple approach, but really there are features available to mongoose that will sort of do this for you if you change your schema a little to reference the data in the Team model. So you can then just fill in using .populate():
var userSchema = new Schema({
username : { type: String, required: true, index: { unique: true } },
fname : { type: String, required: true },
lname : { type: String, required: true },
password : { type: String, required: true },
type : { type: String, required: true },
phone : String,
address : String,
state: String,
conference: String,
email : { type: String, required: true, index: { unique : true } },
teams: [{ type: ObjectId, ref: "Team" }]
});
Now that stores just the references _id value and where to get it from. So now your query becomes this:
User.findById( req.user).populate("teams").exec(function(err,user) {
res.render("coach", user);
});
So what this does is basically the equivalent of the first example but using the _id values and in a little cleaner way, and the user object now has pulled in all of the data from the Team schema matching the list of _id values stored in your user array.
Of course you don't have the same immediate access to just the team name as you did before, but you can work around this by selecting the fields to populate with something like this:
User.findById( req.user).populate("teams","team").exec(function(err,user) {
res.render("coach", user);
});
So that would only pull in the "team" field from each object and is much the same as what you originally had as a result, albeit with actually two queries (under the hood).
Finally if you can live with the concept of a little data replication, then the most efficient way is to simply embed the data. While there is duplicated data being stored,the reading is the most efficient as it is a single query. If you don't need to regularly update that data, this may be worth consideration:
var userSchema = new Schema({
username : { type: String, required: true, index: { unique: true } },
fname : { type: String, required: true },
lname : { type: String, required: true },
password : { type: String, required: true },
type : { type: String, required: true },
phone : String,
address : String,
state: String,
conference: String,
email : { type: String, required: true, index: { unique : true } },
teams: [teamSchema]
});
So with that data embedded you only need to retrieve the user and the "teams" data would already be there:
User.findById( req.user, function(err,user) {
res.render("coach", user);
});
So those are a few approaches to take. All show that the looping of the array is not required, and that the number of queries you are actually issuing can be greatly reduced and even down to 1 if you can live with that.
Firstly, the find operations are asynchronous and although you are waiting for a response before pushing to the team array you are not waiting for all responses before responding with res.render(). Something like below should show what I mean:
// if there is there is more than one team name in the user's team array
if( data[0].teams ){
var teamArr = [];
var responses = 0;
for(var i=0; i<data[0].teams.length; i++){
// for each team name in the user array
// find the corresponding team and add that data to the array called team
Team.find( { team : data[0].teams[i] }, function(err, team){
if(err){
throw err;
}
responses ++;
teamArr.push(team);
// if all teams have been pushed then call render
if (responses == data[0].teams.length - 1) {
// render the view
res.render('coach', {
user: req.user,
fname: self.fname,
lname: self.lname,
email: self.email,
phone: self.phone,
address: self.address,
state: self.state,
teams : teamArr
});
}
});
}
}
Edit: I changed some naming to avoid potential naming clashes (two data variables for example)
Secondly, another option if you change your document structure, given you are using mongoose, if you stored references to the team document objects in your array instead of name strings you could use the mongoose populate method and populate the array before returning the whole document.
Update: Based on Schema provided you could use populate as follows:
var userSchema = new Schema({
username : { type: String, required: true, index: { unique: true } },
....
email : { type: String, required: true, index: { unique : true } },
teams: [{
type: Schema.Types.ObjectId, ref: 'teams'
}],
});
And then populate the document before returning it avoiding the for loop:
User.findOne({_id: userId})
.populate('teams')
.exec(function (err, user) {
if (err) return handleError(err);
res.render('coach', {
user: filterUser(user);
});
});
where user.teams now contains a populated array of teams and filterUser() returns a user object with only the properties you require;

Mongoose Populate - array

can someone please help me with population of this schema? I need to populate array of Staff by their userId.
var PlaceSchema = new Schema ({
name: { type: String, required: true, trim: true },
permalink: { type: String },
country: { type: String, required: true },
...long story :D...
staff: [staffSchema],
admins: [adminSchema],
masterPlace:{ type: Boolean },
images: []
});
var staffSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account' },
role: { type: Number }
});
var adminSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account'}
})
var Places = mongoose.model('Places', PlaceSchema);
I tried to use this query, but without success.
Places.findOne({'_id' : placeId}).populate('staff.userId').exec(function(err, doc){
console.log(doc);
});
Polpulation is intended as a method for "pulling in" information from the related models in the collection. So rather than specifying a related field "directly", instead reference the related fields so the document appears to have all of those sub-documents embedded in the response:
Places.findOne({'_id' : placeId}).populate('staff','_id')
.exec(function(err, doc){
console.log(doc);
});
The second argument just returns the field that you want. So it "filters" the response.
There is more information on populate in the documentation.

Mongoose Relationship Populate Doesn't Return results

var SecuritySchema = new Mongoose.Schema({
_bids: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'BuyOrder'
}],
_asks: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'SellOrder'
}]
});
var OrdersSchema = new Mongoose.Schema({
_security: {
type: Mongoose.Schema.Types.ObjectId,
ref: 'Security'
},
price: {
type: Number,
required: true
},
quantity: {
type: Number,
required: true
}
});
// declare seat covers here too
var models = {
Security: Mongoose.model('Security', SecuritySchema),
BuyOrder: Mongoose.model('BuyOrder', OrdersSchema),
SellOrder: Mongoose.model('SellOrder', OrdersSchema)
};
return models;
And than when I save a new BuyOrder for example:
// I put the 'id' of the security: order.__security = security._id on the client-side
var order = new models.BuyOrder(req.body.order);
order.save(function(err) {
if (err) return console.log(err);
});
And attempt to re-retrieve the associated security:
models.Security.findById(req.params.id).populate({
path: '_bids'
}).exec(function(err, security) {
// the '_bids' array is empty.
});
I think this is some sort of naming issue, but I'm not sure, I've seen examples here and on the moongoose website that use Number as the Id type: http://mongoosejs.com/docs/populate.html
The ref field should use the singular model name
Also, just do:
models.Security.findById(req.params.id).populate('_bids').exec(...
My main suspicion given your snippet at the moment is your req.body.order has _security as a string instead of an array containing a string.
Also, you don't need an id property. Mongodb itself will automatically do the _id as a real BSON ObjectId, and mongoose will add id as a string representation of the same value, so don't worry about that.
While I don't understand your schema (and the circular nature of it?), this code works:
var order = new models.BuyOrder({ price: 100, quantity: 5});
order.save(function(err, orderDoc) {
var security = new models.Security();
security._bids.push(orderDoc);
security.save(function(err, doc) {
models.Security.findById({ _id: doc._id })
.populate("_bids").exec(function(err, security) {
console.log(security);
});
});
});
It:
creates a BuyOrder
saves it
creates a Security
adds to the array of _bids the new orderDoc's _id
saves it
searches for the match and populates
Note that there's not an automatic method for adding the document to the array of _bids, so I've done that manually.
Results:
{ _id: 5224e73af7c90a2017000002,
__v: 0,
_asks: [],
_bids: [ { price: 100,
quantity: 5,
_id: 5224e72ef7c90a2017000001, __v: 0 } ] }

mongoose sub populate not works

here is my schema :
var sourcesSchema = {
title: String,
name: String,
url: String,
description: String,
category: Array,
rating: Number,
source_pages: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'source_page',
}]
}
var sourcePageschema = {
uname: String,
source_name: String,
page_address: String,
driver_name: String,
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'products' //Edit: I'd put the schema. Silly me.
}
}
var productsSchema = {
title: String,
uname: String,
descriptin: String,
images: Array,
currency: String,
last_update_time: Number,
last_process_time: Number,
meta_data: {},
tags: Array,
min_price: Number,
max_price: Number,
prices: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'prices' //Edit: I'd put the schema. Silly me.
}]
}
this code works and populate the source_pages successfully :
_sources.find().populate('source_pages').exec(function (err,sources) {
res.json(200, sources);
});
but if I want to populate the product too :
_sources.find().populate('source_pages').populate('source_pages.product').exec(function (err,sources) {
res.json(200, sources);
})
this error :
TypeError: Cannot call method 'path' of undefined
at search (/home/sina/rhino2/node_modules/mongoose/lib/model.js:2088:28)
at search (/home/sina/rhino2/node_modules/mongoose/lib/model.js:2107:22)
at Function._getSchema (/home/sina/rhino2/node_modules/mongoose/lib/model.js:2114:5)
at populate (/home/sina/rhino2/node_modules/mongoose/lib/model.js:1719:22)
at Function.Model.populate (/home/sina/rhino2/node_modules/mongoose/lib/model.js:1702:5)
at cb (/home/sina/rhino2/node_modules/mongoose/lib/query.js:1690:11)
at /home/sina/rhino2/node_modules/mongoose/lib/utils.js:414:16
at /home/sina/rhino2/node_modules/mongoose/node_modules/mongodb/lib/mongodb/cursor.js:158:16
at commandHandler (/home/sina/rhino2/node_modules/mongoose/node_modules/mongodb/lib/mongodb/cursor.js:643:16)
at null. (/home/sina/rhino2/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1641:20)
I was just hunting down the same problem, and I believe what you are looking for is this Mongoose: deep population (populate a populated field).
Basically, you are not able to do what you are trying to do, unless you do it in your callback function and then insert it in your return. I was trying to avoid that, but at the moment it seems like the only option. The other option, if you plan on doing a lot of this type of stuff, is to look into using a Relational DB.

Resources