Mongoose Schema method: Error - model method is not a function - javascript

I have two Mongoose model schemas as follows. The LabReport model contains an array of the referenced SoilLab model. There is a static method in the SoilLab model that I was using to select which fields to display when LabReport is retrieved.
//LabReport.js
var mongoose = require("mongoose");
var SoilLab = mongoose.model("SoilLab");
var LabReportSchema = new mongoose.Schema(
{
labFarm: { type: mongoose.Schema.Types.ObjectId, ref: "Farm" },
testName: { type: String },
soilLabs: [{ type: mongoose.Schema.Types.ObjectId, ref: "SoilLab" }],
},
{ timestamps: true, usePushEach: true }
);
LabReportSchema.methods.toLabToJSON = function () {
return {
labReport_id: this._id,
testName: this.testName,
soilLabs: this.soilLabs.SoilToLabJSON(),
};
};
mongoose.model("LabReport", LabReportSchema);
//SoilLab.js
var mongoose = require("mongoose");
var SoilLabSchema = new mongoose.Schema(
{
description: { type: String },
sampleDate: { type: Date },
source: { type: String },
},
{ timestamps: true, usePushEach: true }
);
SoilLabSchema.methods.SoilToLabJSON = function () {
return {
description: this.description,
sampleDate: this.sampleDate,
source: this.source,
};
};
mongoose.model("SoilLab", SoilLabSchema);
When I try to retrieve the LabReport, I get "this.soilLabs.SoilToLabJSON is not a function". This is how I'm trying to retrieve LabReport.
//labReports.js
...
return Promise.all([
LabReport.find()
.populate("soilLabs")
.exec(),
LabReport.count(query).exec(),
req.payload ? User.findById(req.payload.id) : null,
]).then(function (results) {
var labReports = results[0];
var labReportsCount = results[1];
var user = results[2];
return res.json({
labReports: labReports.map(function (labReport) {
return labReport.toLabToJSON(user); //This cant find SoilToLabJSON
}),
If I remove the .SoilToLabJSON in LabReport.js and just call this.soilLabs, it works but outputs all of the soilLabs data which will become an issue when I have the model completed with more data. I have dug into statics vs methods a little and tried changing it to statics but it didn't work.
I get the soilLabs to populate but not sure why the .SoilToLabJSON method is inaccessible at this point. Do I need to find() or populate the soilLab differently? Is the method incorrect?

labReport.toLabToJSON is passing an array and that was causing the error for me. I simply edited the LabReport.js to the following to take the array and map it to SoilToLabJSON properly.
myTestSoilLabOutput = function (soilLabs) {
var test = soilLabs.map(function (soilLab) {
return soilLab.SoilToLabJSON();
});
return test;
Changed the LabReportSchema.methods.toLabToJSON to:
LabReportSchema.methods.toLabToJSON = function () {
return {
labReport_id: this._id,
testName: this.testName,
soilLabs: myTestSoilLabOutput(this.soilLabs),
};
};

Related

Mongoose.model('model_name') return empty object when function is required in the mongoose model schema file

I apologize if the title of the question is misleading, because I am not too sure how to explain this. I have 2 files, matchedTransaction.js and player.js.
sharedApi/player.js
const MatchedTransactionModel = require('../models/matchedTransaction');
// #1: If I try to console log here, the output will be an empty object "{}"
console.log(MatchedTransactionModel);
async function banPlayer(userId) {
// ...
// Because MatchedTransactionModel is an empty object,
// the code below will crash with the following error:
// "MatchedTransactionModel.findOne is not a function"
const pendingMatchedTransaction = await MatchedTransactionModel.findOne({
$and: [
{
$or: [
{ reserverAccountId: `${account._id}` },
{ sellerAccountId: `${account._id}` },
],
},
{
$or: [
{ status: 'pendingReserverPayment' },
{ status: 'pendingSellerConfirmation' },
],
},
],
});
// ...
}
module.exports = {
banPlayer,
};
models/matchedTransaction.js
const mongoose = require('mongoose');
const { banPlayer } = require('../sharedApi/player');
const MatchedTransactionSchema = new mongoose.Schema([
{
createdDate: {
type: Date,
required: true,
},
// ...
},
]);
MatchedTransactionSchema.post('init', async function postInit() {
// ...
await banPlayer(userId);
});
const MatchedTransactionModel = mongoose.model('matchedTransactions', MatchedTransactionSchema);
module.exports = MatchedTransactionModel;
Notice that in player.js when I tried to console.log the required MatchedTransactionModel, it returns an empty object. However, if I made the following changes to matchedTransaction.js:
models/matchedTransaction.js
// Instead of requiring banPlayer outside, I only require it when it is used
// const { banPlayer } = require('../sharedApi/player');
MatchedTransactionSchema.post('init', async function postInit() {
// ...
const { banPlayer } = require('../sharedApi/player');
await banPlayer(userId);
});
// ...
The output of the previously mentioned console.log will be a non-empty object, and MatchedTransactionModel.findOne is working as expected.
Why does that happen?
The only problem I see with your code is that when you define the schema on matchedTransaction.js, you passed an array which I think is problematic and does not make sense. You must pass an object there:
const MatchedTransactionSchema = new mongoose.Schema({
createdDate: {
type: Date,
required: true,
},
// ...
});

How to update existing object with additional data

The project is created with nodejs and mongoose. What I am trying to do is to update the existing model with addition data (which is a comment, in that case).
This is the model and its methods:
const bugSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
date: {
type: String,
required: true
},
time: {
type: String,
required: true
},
assignedTo: {
type: String,
required: true
},
assignedBy: {
type: String,
required: true
},
status: {
type: String,
required: true
},
priority: {
type: String,
required: true
},
comments: {
comment:[
{
user:{
type: String,
required: true
},
content: {
type: String,
required: true
}
}
]
}
});
bugSchema.methods.addComment = function(comment){
const username = comment.user;
const content = comment.content;
console.log(comment);
const updatedComments = [...this.comments];
updatedComments.push({
user : username,
content: content
});
this.comments = updatedComments;
return this.save();
};
The controller, which is passing the information from the form:
exports.postComment = (req,res,next) =>{
const bugId = req.body.bugID;
const name = req.session.user.fullName;
const content = req.body.content;
const prod = {name, content};
Bug.findById(bugId).then(bug =>{
return bug.addComment(prod);
})
.then(result =>{
console.log(result);
});
};
I am getting a following error:
(node:3508) UnhandledPromiseRejectionWarning: TypeError: this.comments is not iterable
(node:3508) UnhandledPromiseRejectionWarning: TypeError: this.comments is not iterable
The error indicate you're trying to iterable a type of data which does NOT has that capability.
You can check that printing the type:
console.log(typeof this.comments)
Or even, priting the whole object:
console.log(this.comments)
as you can see, in both cases you're getting an object, not a list (how you spect)
So you can do 2 things:
1- Iterable a list
this.comments is an object but into that object you have the list you want, so just use the list instead.
bugSchema.methods.addComment = function(comment){
const username = comment.user;
const content = comment.content;
console.log(comment);
//const updatedComments = [...this.comments];
const updatedComments = [...this.comments.comment];
updatedComments.push({
user : username,
content: content
});
this.comments = updatedComments;
return this.save();
};
Or you can modify your schema making the comments a list instead of an object
2- comments as list in schema
Define the comments attribute as a list
const bugSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
...
...,
comments:[
{
user:{
type: String,
required: true
},
content: {
type: String,
required: true
}
}
]
});
And then, try to iterable it as how you been doing
bugSchema.methods.addComment = function(comment){
const username = comment.user;
const content = comment.content;
console.log(comment);
const updatedComments = [...this.comments];
updatedComments.push({
user : username,
content: content
});
this.comments = updatedComments;
return this.save();
};
I am not sure but comments is an object and not an array so you can't push using [...this.comments] and I think it is the comment you want to push?
const updatedComments = [...this.comment];
updatedComments.push({
user : username,
content: content
});
this.comment = updatedComments;
From your schema comments is not an array. you are trying to spread an object into an array. const updatedComments = [...this.comments]; also push works on array.
try to modify your schema definitions by declaring the commentSchema outside the bugSchema.
const commentSchema = new Schema({
user:{
type: String,
required: true
},
content: {
type: String,
required: true
}
})
const bugSchema = new Schema({
comments: {
type: [commentSchema]
}
})
Bug.findByIdAndUpdate(bugId, {$push: {comments: newComment}})
Don't use findByIdAndUpdate Mongoose method, you better use save
it is written here https://mongoosejs.com/docs/tutorials/findoneandupdate.html
The findOneAndUpdate() function in Mongoose has a wide variety of use cases. You should use save() to update documents where possible, but there are some cases where you need to use findOneAndUpdate(). In this tutorial, you'll see how to use findOneAndUpdate(), and learn when you need to use it.
Below a router example
router.put('/items', (req, res) => {
if (!req.body._id || !req.body.title) {
return res.status(501).send({ message: 'Missing parameters, or incorrect parameters' });
}
return itemModel.findOne({ _id: req.body._id }, (err, item) => {
if (err) {
return res.status(500).send({
message: err
});
}
item.title = req.body.title; // <------------- You rewrite what was before stored on title attribute
return item.save((err, item) => { // <------------- You save it, this is not gonna create a new one, except if it doesn't exist already
if (err) {
return res.status(400).send({
message: 'Failed to update item'
});
} else {
return res.status(200).send({
message: 'Item update succesfully',
data: item
});
}
});
});
});

Add data to returned model sequelize

I have a sequelize model object that is working like expected.
const part = sequalize.define("part", {
id: {type:Sequalize.INTEGER, primaryKey: true, autoIncrement: true},
part_number: {type : Sequalize.STRING(12), unique: true, allowNull: false},
description: {type : Sequalize.STRING(255), allowNull: false}, true}
})
But I want to run an additional function when this model is called for example in a query.
I have this function now:
part.prototype.getTotalStock = function () {
let _this = this;
return new Promise(function(resolve, reject) {
part.findOne({
where: {id: _this.id},
include: [{
model: sequalize.models.location,
}]
}).then(result => {
if (result) {
var totalStock = 0;
let stockOnLocation = result.dataValues.locations
stockOnLocation.forEach(function (entry) {
totalStock += entry.stock.amount
});
_this.setDataValue('totalStock', totalStock);
return resolve(_this)
} else {
return resolve(0)
}
}).catch(err => {
return reject(err)
});
})
}
What i'm doing is that I do a query so I have 1 part object. After that I can call:
queryResult.getTotalStock().then(result => {
//data here in result
})
That is working, its retrieving the stock, calculating it, and it is adding the data to self. Now my question, is it possible to append the result of getTotalStock automatically when the model is being used? So that I don't have to call getTotalStock on the returned object?

Custom validator, cb is not a function

Problem with a custom validator in node.js, using mongoose. I'm trying to check if a query exists in headerLog prior to inserting it.
My code is below:
var mongoose = require('mongoose'); //layer above mongodb
var Schema = mongoose.Schema;
var headerLogSchema = new Schema({
query: { type: String, required: true, unique: true, validate: {
validator: function(v, cb) {
HeaderLog.find({query: v}, function(err, documents){
cb(documents.length == 0);
});
},
message: 'Header already exists in log, didnt save this one.'
}
}
})
var HeaderLog = mongoose.model('headerLog', headerLogSchema);
module.exports = HeaderLog;
The error: TypeError: cb is not a function.
I'm calling this function like so:
function logHeader(query) {
var newHeaderLog = new HeaderLog({
query: query
})
newHeaderLog.save(function(err) {
if (err) {
console.log(err);
}
else {
console.log('New header logged');
}
});
}
What am I doing wrong?
As the reference states, asynchronous validators should either have isAsync flag:
validate: {
isAsync: true,
validator: function(v, cb) { ... }
}
Or return a promise. Since the validator already uses another model, and Mongoose models are promise-based, it makes sense to use existing promise:
validator: function(v) {
return HeaderLog.find({query: v}).then(documents => !documents.length);
}
countDocuments is a better alternative to find for cases when only documents count is needed.
If you look at the async validator example here in the doc, it looks like you have to pass the option isAsync: true in order to tell mongoose that you are using an async validator and thus it should pass a callback to it.
var headerLogSchema = new Schema({
query: {
type: String,
required: true,
unique: true,
validate: {
isAsync: true, // <======= add this
validator: function(v, cb) {
HeaderLog.find({query: v}, function(err, documents){
cb(documents.length == 0);
});
},
message: 'Header already exists in log, didnt save this one.'
}
}
})

model.find({}).populate('place').populate('location') does not return place and location

I have document with the following structure on MongoDb,
I am using Mongoose version ^4.8.1 with my node application. I have created 3 schema models for the above document which are as follows,
Event.js
var mongoose = require('mongoose');
var eventSchema = new mongoose.Schema({
description: {
type: String
},
end_time: {
type: Date
},
start_time: {
type: Date
},
name: {
type: String
},
place: {
type: mongoose.Schema.Types.ObjectId,
ref: 'place'
}
});
eventSchema.index({name: 'text'},{'place.location.country':"text"});
var Event = mongoose.model('events', eventSchema);
module.exports= Event;
Place.js
var mongoose = require('mongoose');
var placeSchema = new mongoose.Schema({
name: {
type: String
},
location: {
type: mongoose.Schema.Types.ObjectId,
ref: 'location'
}
});
var Place = mongoose.model('place', placeSchema);
module.exports= Place;
Location.js
var mongoose = require('mongoose');
var locationSchema = new mongoose.Schema({
city: {
type: String
},
latitude: {
type: String
},
country: {
type: String
},
located_in: {
type: String
},
state: {
type: String
},
street: {
type: String
},
zip: {
type: String
},
});
var Location = mongoose.model('location', locationSchema);
module.exports= Location;
Common handler to access /query database,
dbHandler.js
querandpoplulate : function(model,condition,options)
{
return new Promise(function(resolve, reject) {
options = options||{};
console.log("model is" + model);
model.find({}).populate('place').populate('location').exec(function(error, data) {
if (error)
console.log(error);
reject(error);
console.log(data);
resolve(data);
})
})
}
Here is how i query,
dbHelper.querandpoplulate(mongoose.model('events'), {$text: {$search: searchString},'place.location.country': countryString},function(error,data){
callback(data);
});
Question: it does not return the result set with the place and location , it returns null in place field.
It looks to me like your documents are saved as embedded documents, but not as referenced documents.
To fetch such documents, you don't need to do any population. Simple find query should work for you.
Try this:
model.find({}).exec(function(error, data) {
if (error)
console.log(error);
reject(error);
console.log(data);
resolve(data);
})
As you are not saving the data in the mongoDB but only retrieving it. you need to define the schema that matches with the document structure.
As discussed with you, i think you need to change the Schema, and combine all 3 schemas in one file (Event.js).
var mongoose = require('mongoose');
var locationSchema = new mongoose.Schema({
city: {
type: String
},
//add other fields here
});
var placeSchema = new mongoose.Schema({
name: {
type: String
},
location: locationSchema
});
var eventSchema = new mongoose.Schema({
description: {
type: String
},
//add other fields here too
place: placeSchema
});
eventSchema.index({name: 'text'},{'place.location.country':"text"});
var Event = mongoose.model('events', eventSchema);
module.exports= Event;

Categories

Resources