In mongoose how to iterate over array of ObjectId? - javascript

I try to refactor from javascript object to mongoose object.
I have two simple models:
Candidate model:
var candidateSchema = new Schema({
name: String,
score: { type: Number, default: 0, min: -2, max: 2 }
});
candidateSchema.methods.updateScore = function updateScore(newScore) {
this.score = newScore;
};
Vote model containing a list of candidate:
var voteSchema = new Schema({
candidates: [
{ type: Schema.Types.ObjectId, ref: 'Candidate' }
]
});
function candidateAlreadyExists(candidates, newCandidate) {
var alreadyExists = false;
candidates.forEach(function (candidate) {
if (newCandidate.name === candidate.name) {
alreadyExists = true;
}
});
return alreadyExists;
}
voteSchema.methods.addCandidate = function addCandidate(newCandidate, callback) {
var candidates = this.candidates;
if (!candidateAlreadyExists(candidates, newCandidate)) {
candidates.push(newCandidate);
}
this.save(callback);
};
I try to add a static method in my voteSchema to add a new candidate, only if it doesn't exist.
I can't iterate over my candidates (candidates.forEach(function (candidate) {) because it's an array of _ids and not an array of javascript objects
Someone have an idea ?

It would be better to use $addToSet to let MongoDB do that for you atomically.
voteSchema.methods.addCandidate = function addCandidate(newCandidate, callback) {
this.candidates.addToSet(newCandidate._id);
this.save(callback);
};

Related

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

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),
};
};

My static method is not running

Here is my recipe.js file...
const express = require('express');
const mongoose = require('mongoose');
const User = require('../model/user');
require('mongoose-currency').loadType(mongoose);
const Currency = mongoose.Types.Currency;
const Schema = mongoose.Schema;
const reviewSchema = require('../model/review');
let recipeSchema = new Schema({
name: {
type: String,
required: true
},
description: {
type: String,
},
steps: {
type: String,
required: true,
},
ingredients: {
type: Array,
default: ['1', '2', '3', '4']
},
category: {
type: String,
required: true,
index: true
},
postedBy: {
type: String,
required: true,
},
reviewsOfRecipe: [reviewSchema],
numberOfRatings: {
type: Number,
default: 0
},
totalAddedRatings: {
type: Number,
default: 0
},
reviewAverage: {
type: Number,
default: 0
},
postersCreationDate: {
type: Number,
index: true
},
likedBy: {
type: Array
},
reviewedBy: {
type: Array
}
});
// Here is my static method
recipeSchema.methods.calculateAverage = function(){
let recipe = this;
if (recipe.numberOfRatings === 0 && recipe.totalAddedRatings === 0){
recipe.reviewAverage = 0;
}
else {
recipe.reviewAverage = recipe.totalAddedRatings / recipe.numberOfRatings
}
};
let Recipe = mongoose.model('Recipe', recipeSchema);
module.exports = Recipe;
In my router file, every time a user submits a review for a recipe, the fields numberOfRatings and totalAddedRatings get incremented. And after they get incremented, my static method calculateAverage should run and update the document.
Here is what it looks like in my code:
Recipe.findOneAndUpdate({_id: recipe._id, postersCreationDate: recipe.postersCreationDate},{$inc: {numberOfRatings: 1, totalAddedRatings: reviewScore}}, {returnNewDocument: true}).then((recipe) => {
recipe.calculateAverage();
});
However, every time a user submits a review, although numberOfRatings and numberOfRatings get incremented accordingly, reviewAverage does not.
I am thinking about setting reviewAverage as a virtual field instead; but I am worried that doing so would make it harder and inefficient to sort the recipes by the highest and lowest review averages.
first some things about your findOneAndUpdate can change. Id should be sufficient to find by and 'returnNewDocument' is not an option. In mongoose it's just 'new'
Recipe.findOneAndUpdate({_id: recipe._id},{$inc: {numberOfRatings: 1, totalAddedRatings: reviewScore}}, {new: true}).then((recipe) => {
recipe.calculateAverage();
});
The problem is that you aren't saving the average to the recipe.
recipeSchema.methods.calculateAverage = function(callback) {
let recipe = this;
if (recipe.numberOfRatings === 0 && recipe.totalAddedRatings === 0) {
recipe.reviewAverage = 0;
}
else {
recipe.reviewAverage = recipe.totalAddedRatings / recipe.numberOfRatings
}
recipe.save(callback);
};

Error Cannot set property of undefined

Hi I have this code in Node.Js, in this I realize a find query with mongoose
router.post('/query',function(req,res,next){
if (req.body){
var result=[];
console.log(req.body.filters);
Pollee.find(req.body.filters)
.select('id age birthday user')
.populate('user','email')
.lean(true)
.exec(function(err,pollees){
if(err) {
console.log(err);
return next(err);
}
for (var i = 0; i < pollees.length; i++){
var query = test(pollees[i]._id);
query.exec(function(err,inters){
if(err)
return console.log(err);
inters.forEach(function(inter){
pollees[i].interaction = inter;
});
});
}
res.json(pollees);
};
})
}
});
function test(id){
var promise = Interaction.find({pollee:id}).select('status other');
return promise;
}
My problem here its in the Interaction.find when I try to pass the results of this query on pollees[i].interaction = inter; the console set me error
Cannot set property pollees[i].interaction = inter; of undefined
Any idea?
The models I used
var interactionSchema = new Schema({
pollee: { type: ObjectId, ref: 'Pollee' },
answers: { type: [ObjectId], ref: 'Answer', autopopulate: true },
status: type: String
});
var PolleeSchema = new Schema({
firstName: String,
lastName: String,
gender: String,
user: { type: ObjectId, ref: 'User', required: true },
interactions: { type: [ObjectId], ref: 'Interaction', autopopulate: true }
});
var userSchema = new Schema({
email: String,
pollee: { type: Schema.Types.ObjectId, ref: 'Pollee', autopopulate: true }
});
Thanks a lot!
I'd say the problem the following: in the for cycle of your code you're calling async method query.exec(). By the time it executes it's callback, for cycle has already finished and value of i === pollees.length. Thus pollees[i] is pointing to non-existent array element (undefined) and you get an error "cannot set property of undefined" when your trying to set it's property interaction.
One of the ways to fix this would be to use .bind:
query.exec(function(i, err,inters){ //i is among the params in your callback
if(err)
return console.log(err);
inters.forEach(function(inter){
pollees[i].interaction = inter;
});
}.bind(null, i)); //you're binding the variable 'i' to the callback
EDIT:
And in order for res.json(pollees); to work (which is a different problem) you should wrap all your callbacks in a Promise. It should probably look like something like this:
var queries = []; //an array of promises
for (var i = 0; i < pollees.length; i++){
queries.push(test(pollees[i]._id)); //add a promise to the array
}
//wait for all promises to resolve
Promise.all(queries).then(function(results) {
results.forEach(function(inter, index){
pollees[index].interaction = inter;
});
res.json(pollees); //return response
});

Return object with subset of its attributes

I've got a flat JavaScript object like this:
{
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
...
a lot more attributes
}
I'd like to create a new object which only has a subset of the attributes of the original object.
Something like
var newObject = oldObject.fields(['id', 'username']);
newObject would be
{
id: 3726492,
username: 'Nicholas'
}
Is there already something like this?
Try this
function pick(data, keys) {
var result = {};
keys.forEach(function (key) {
if (data.hasOwnProperty(key)) {
result[key] = data[key];
}
});
return result;
}
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
}
var newData = pick(data, ['id', 'kind']);
console.log(newData);
In underscorejs or lodash there is method .pick
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var newObject = _.pick(data, 'id', 'username');
console.log(newObject);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
You can use Array.prototype.reduce to reduce one object to another using the list of properties:
function subset(obj, propList) {
return propList.reduce(function(newObj, prop) {
obj.hasOwnProperty(prop) && (newObj[prop] = obj[prop]);
return newObj;
}, {});
}
var obj = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
};
var newObj = subset(obj, ['id', 'username']);
console.log(newObj);
document.getElementById('json').innerText = JSON.stringify(newObj);
<pre id="json"></pre>
Not built-in, but you can sure define a simple function that does the job:
var original = {a:1112, b:434, c:666, d:222};
function fieldSubset(obj, fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(obj.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = obj[fields[i]];
}
}
return subsetClone;
}
fieldSubset(original, ["a", "c"]);
You can also use this in Object.prototype, but be aware that this might happen to conflict with native API in the future versions of JavaScript:
var original = {a:1112, b:434, c:666, d:222};
Object.defineProperty(Object.prototype, "fieldSubset", {
value: function(fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(this.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = this[fields[i]];
}
}
return subsetClone;
},
enumerable: false,
configurable: true}
);
original.fieldSubset(["a", "c"]);
One liner using Array.prototype.reduce. We are also using Object.assign. The idea is to keep extending a blank object with the keys found in the filters array. If you see, the reduce function takes a callback function with arg1,arg2,arg3 params as the first argument and an empty object as the second argument. This object will be cloned and extended with the help of the keys specified in the filters array.
var a = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var filters = ["id","username","permalink"];
var sub = Object.keys(a).reduce((arg1,arg2,arg3)=>{ var res = {}; if(filters.indexOf(arg2)>=0){ res[arg2] = a[arg2]; } return Object.assign(arg1,res);},{})
console.log(sub);
You haven't specifically mentioned what is the type of values behind your object's keys. Your current answers cover the shallow copy and deep copy.
Another alternative would be to create a view of the original object. This would be helpful if you have very large data objects and you do not want them copy in the memory.
function View(obj, properties) {
var view = {};
properties.forEach(function(prop) {
Object.defineProperty(view, prop, {
get: function() {
return obj[prop];
},
set: function(val) {
obj[prop] = val;
},
enumerable: true,
configurable: true
});
});
return view;
}
then with your data you can do:
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
},
view = new View(data, ['id', 'username']);
view.id; // 3736492
view.username; // Nicholas
of course you have to be aware that you can change your original object just by view.id = 'something else'. However it is easily preventable.

How to loop array

I am trying to add an object to JavaScript array and then loop it.But loop is not running
var basicConf = {
RootUrl: "https://api.joltcomm.com/",
username: 'test',
password: 'test',
APPURL:'https://dev.joltcomm.com/secureadmin/',
PROJECTID:'',
uData:'',
iFunc:[],
pushData:function(data){
this.iFunc.push(data);
}
};
function onProjectLoad(fn,params)
{
basicConf.pushData({'function':fn,'parameters':params});
}
onProjectLoad("getData",["user-project/2","showProjects",1]);
$.each(basicConf.iFunc, function( index, value ) {
console.log(value);
var fn=value.function+'(';
$.each(value.parameters, function( i, v ) {
if(i>0)
{
fn+=',';
}
fn+='"'+v+'"';
});
fn+=');';
console.log(fn);
eval(fn);
});
I am not getting into the loop I don’t know why
Well it is hard to push items to an object.
iFunc:{}, <-- object
you want an array if you actually want to add them to an array.
iFunc:[], //<-- That is an array, you can push to the array
If you just want to update the object, than do not use push.
var basicConf = {
RootUrl: "https://api.example.com/",
username: 'test',
password: 'test',
APPURL:'https://dev.example.com/example/',
PROJECTID:'',
uData:'',
iFunc:[],
pushData:function(data){
this.iFunc.push(data);
console.log(this.iFunc)
}
};
function onProjectLoad(fn,params)
{
basicConf.pushData({'function':fn,'parameters':params});
}
onProjectLoad("getData",["user-project/2","showProjects",1]);
push is an array method, you can't 'push' new things into an object as you need to provide a key to store the data against.
addData:function(data){
this.iFunc.someKey = data;
}
indeed you can't push key/val on an object directly but you can set them in an array-way approach:
var basicConf = {
RootUrl: "https://api.example.com/",
username: 'test',
password: 'test',
APPURL:'https://dev.example.com/example/',
PROJECTID:'',
uData:'',
iFunc:{},
pushData:function(data){
for (key in data) {
this.iFunc[key] = data[key];
}
}
};
function onProjectLoad(fn,params)
{
basicConf.pushData({'function':fn,'parameters':params});
}
onProjectLoad("getData",["user-project/2","showProjects",1]);
so if you console.log your basicConf, it will be like:
iFunc: Object
function: "getData"
parameters: Array[3]
0: "user-project/2"
1: "showProjects"
2: 1
I think this is what you need. A method to extend an existing object with additional values.
var basicConf = {
RootUrl: "https://api.example.com/",
username: 'test',
password: 'test',
APPURL: 'https://dev.example.com/example/',
PROJECTID: '',
uData: '',
iFunc: {},
pushData: function(data) {
this.iFunc.push(data);
}
};
function onProjectLoad(fn, params) {
extend(basicConf, {
'function': fn,
'parameters': params
});
}
onProjectLoad("getData", ["user-project/2", "showProjects", 1]);
function extend(dest, src) {
var keys = Object.keys(src);
var i = 0;
while (i < keys.length) {
if (!this.extend || (this.extend && dest[keys[i]] === undefined)) {
dest[keys[i]] = src[keys[i]];
}
i++;
}
return dest;
}

Categories

Resources