nodejs mongoose model / relations questions - javascript

I recently discovered nodejs + mongodb (via mongoosejs).
I am coding a small app for my raspberry to display a scoreboard on a big led pannel.
I actually have 2 problems
To configure the scoreboard for a match I need :
2 teams : each teams has n players ( 1 to .... )
A time limit
A goal limit
the goals scored by each team
Here is my models files :
var MatchSchema = new Schema({
teams: [{ type: Schema.Types.ObjectId, ref: 'Team' }],
goals_left: Number,
goals_right: Number,
time_limit: Number,
goal_limit: Number
});
var TeamSchema = new Schema({
name: {
type: String,
required: [true, "Team Name is required"],
},
players: [{ type: Schema.Types.ObjectId, ref: 'Player' }]
});
var PlayerSchema = new Schema({
name: {
type: String,
required: [true, "Player Name is required"],
}
});
Question 1 :
Create team : I have no problem to insert in my Teams collection an array of player object ( I use checkboxes to select the players of the team )
Create match : I can't manage to insert an array of 2 team objects in my match collection, here is the form and the controller action :
Form
div.row.form-group
each i in [0,1]
div.col
label(for='teams-'+i) #{ i===0 ? 'Left' : 'Right' } Team
select.form-control(type='select' id='teams-'+i placeholder='Select TEAM'+i name='teams['+i+']' required='false' data-target='/team/new')
option(value=null) Select #{ i===0 ? 'Left' : 'Right' } Team
for team in all_teams
option(value=team._id) #{team.name}
Create match code :
// Handle Match create on POST.
exports.match_create_post = [
(req, res, next) => {
if(!(req.body.teams instanceof Array)){
if(typeof req.body.teams==='undefined') {
req.body.teams=[];
}else{
match_teams=new Array(req.body.teams);
}
}
next();
},
(req, res, next) => {
const errors = validationResult(req);
// Create a team object with escaped and trimmed data.
var match = new Match(
{
//teams: [ "5a92f691d5038fd56c648664", "5a92fa14b4d7f5d665da9ef4"],
teams: match_teams,
time_limit: req.body.time_limit,
goal_limit: req.body.goal_limit,
status: req.body.status
}
);
if (!errors.isEmpty()) {
res.render('match_form', { title: 'Create Match', match: match, errors: errors.array()});
return;
} else {
match.save(function (err) {
if (err) { return next(err); }
res.redirect(match.url);
});
}
}
];
The weird thing is I do the same thing for creating a team and it works perfeclly...
I can not understand why req.body.teams is undefined
Question 2 :
If I manually insert the 2 teams in my teams fields like :
teams: [ "5a92f691d5038fd56c648664", "5a92fa14b4d7f5d665da9ef4"]
When I want to display match detail :
Controller Query :
exports.match_detail = function(req, res, next) {
var id = mongoose.Types.ObjectId(req.params.id);
async.parallel({
match: function(callback) {
Match.findById(id)
.populate('teams')
.populate('teams.players')
.exec(callback);
},
}, function(err, results) {
if (err) { return next(err); }
if (results.match==null) {
var err = new Error('Match not found');
err.status = 404;
return next(err);
}
// Successful, so render
res.render('match', {
title: 'match Detail',
match: results.match
} );
});
};
The team names are correctly displayed but for player details I only can access the _id, not the name
Any help would be appreciated :-)
Thank you
Clément

Related

How to display search result with mongoose pagination

I have Training Collection with lots of data. All function works except that on search result the latest entry results appear correctly, but the old results don't appear on front except they appear on the pages based on their position. Helper and course are populated from another schema. The reason I used the if condition is because the helper and course are populated. I tried searching for similar problem on stack overflow and other website and tried to query it as 'helper.first_name': { $regex: reg_ex } but it did not work for me that way. Here is the pseudo code below
P.s I am using pagination. I need help please
req.checkBody('search').notEmpty().withMessage('Please enter a query'); // check empty entry
const reg_ex = new RegExp(req.body.search, 'i'); // regular expression
const page = parseInt(req.query.page);
const limit = 20;
const skipIndex = (page - 1) * limit;
try {
Training
.find()
.populate([{
path: 'helper',
model: Helper
},
{
path: 'course',
model: Course
},
{
path: 'user',
select: '-password'
}
])
.sort({
_id: -1
})
.limit(limit)
.skip(skipIndex)
.exec(function (err, doc) {
if (err) {
return next(err)
}
const arryPush = [];
doc.forEach(data => {
if (
(data.batch.match(reg_ex) != null) ||
(data.helper.first_name.match(reg_ex) != null) ||
(data.helper.last_name.match(reg_ex) != null) ||
(data.helper.phone.match(reg_ex) != null) ||
(data.helper.email.match(reg_ex) != null) ||
(data.course[0].title.en.match(reg_ex) != null)
) {
arryPush.push({
_id: data._id,
course: [{
title: {
en: data.course[0].title.en,
am: data.course[0].title.am
}
}],
is_rejected: data.is_rejected,
approved: data.approved,
training_completion_status: data.training_completion_status,
training_time: data.training_time,
weekdays: data.weekdays,
payment_option: data.payment_option,
batch: data.batch,
created_at: data.created_at,
helper: {
first_name: data.helper.first_name,
last_name: data.helper.last_name,
phone: data.helper.phone,
email: data.helper.email
}
})
}
});
res.status(200).json(arryPush)
});
} catch (e) {
res.status(500).json({
message: "Error Occured" + e
});
}

Mongoose - CastError Cast to string failed for value "Object"

I have Mongoose CastError issue. I made a nodeJs API. At the specific route, it returns data appended with some other data. I saw many fixes available here but my scenario is different.
Here is my model and the problem occurs at fields property.
const deviceSchema = new Schema({
device_id: { type: String, required: true },
user_id: { type: Schema.Types.ObjectId, ref: 'User', require: true },
location_latitude: { type: String, default: '0' },
location_longitude: { type: String, default: '0' },
fields: [{ type: String }],
field_id: { type: Schema.Types.ObjectId, ref: 'Field', required: true },
timestamp: {
type: Date,
default: Date.now,
},
});
and my controller is
exports.getAllDevices = async (req, res) => {
try {
let devices = await Device.find({})
.sort({
timestamp: 'desc',
})
.populate('user_id', ['name']);
// Let us get the last value of each field
for (let i = 0; i < devices.length; i++) {
for (let j = 0; j < devices[i].fields.length; j++) {
if (devices[i].fields[j] !== null && devices[i].fields[j] !== '') {
await influx
.query(
`select last(${devices[i].fields[j]}), ${devices[i].fields[j]} from mqtt_consumer where topic = '${devices[i].device_id}'`
)
.then((results) => {
************** Problem occurs here **************
if (results.length > 0) {
devices[i].fields[j] = {
name: devices[i].fields[j],
last: results[0].last,
};
} else {
devices[i].fields[j] = {
name: devices[i].fields[j],
last: 0,
};
}
************** Problem occurs here **************
});
}
}
}
// Return the results
res.status(200).json({
status: 'Success',
length: devices.length,
data: devices,
});
} catch (err) {
console.log(err);
res.status(500).json({
error: err,
});
}
};
It actually gets data from InfluxDB and appends it to fields property which was fetched from MongoDB as mentioned in my model. But it refused to append and CastError occurs.
After addition, it will look like this
I can't resolve this error after trying so many fixes. I don't know where I'm wrong. Please suggest to me some solution for this.
I can see you are not using devices variable as Mongoose Document. devices is an array of Documents.
I would like to suggest you to use lean() function to convert from Document to plain JavaScript object like
let devices = await Device.find({})
.sort({
timestamp: 'desc',
})
.populate('user_id', ['name'])
.lean();

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

Sails.js query db by foreign key

I'm wondering how to make a query by foreign key using the default Waterline model.
I have two models Post and Category - Post has a foreign key category. I need to make a query like so:
Post.find({
where: {
category: query
}
}).exec(function (err, data) {});
In this case query is a string so the results returned should be Posts containing searched category.
What is the best way to do this?
Note: Current example does not work
Your model should be
// Post
module.exports = {
attributes: {
name: {
type: 'string'
},
category: {
model: 'category'
}
}
};
// Category
module.exports = {
attributes: {
name: {
type: 'string'
},
post: {
collection: 'Post',
via: 'category'
}
}
};
Then query from category would be
Category
.find()
.where({ name: query })
.populateAll()
.exec(function (error, categories) {
var catArr = [];
if (categories.length) {
categories.map(function (item) {
catArr.push(item.id);
});
}
Post.find().where({ category: catArr }).exec(function (error, posts) {
// do stuff
});
});
Or simply you can query it from post by
Post
.find()
.where({ category: categoryId })
.populateAll()
.exec(function (error, posts) {
// posts is all post with category that defined
});
Make sure that you know categoryId if you want to query it from post. I usually use categoryId is string and slugify from name, so I can query category by it's name and make sure that category name (and also ID of course) is unique.
Figured how to implement this using the category id:
Category.find().where({ name: query }).exec(function (error, categories) {
var catArr = [];
if (categories.length) {
categories.map(function (item) {
catArr.push(item.id);
});
}
Post.find().where({ category: catArr }).exec(function (error, posts) {
// do stuff
});
});
Also had to add attributes in the models like so:
// Post
module.exports = {
attributes: {
name: {
type: 'string'
},
category: {
model: 'category'
}
}
};
// Category
module.exports = {
attributes: {
name: {
type: 'string'
},
post: {
model: 'post'
}
}
};

Dynamically building MongoDB queries in NodeJS

I receive a POST argument that looks like this:
sort:
[
{ field: 'name', dir: 'asc', compare: '' },
{ field: 'org', dir: 'asc', compare: '' }
]
}
and I need to create a MongoDB query based on that, so it should look like:
db.collection("my_collection").find( ... ).sort({'name': 'asc', 'org': 'asc'}).toArray(...);
Anyways, keep in mind that more fields could be passed. Also, it could happen that none of those fields is passed, meaning that the query won't have .sort().
My question: How can I create dynamically a query with Node's MongoDB driver? Is there a query builder or something similar?
I've found that most cases are unique regarding passed data, so building query objects varies from project to project.
So first ideas was to create middleware for express (in my case), that would parse query arguments into objects that are valid for query.
mongo-native can use as chained options to cursor, as well as in object:
Chained:
items.find({ type: 'location' }).sort({ title: 1 }).limit(42).toArray(function(err, data) {
// ...
});
Non-chained:
items.find({ type: 'location' }, { sort: { title: 1 }, limit: 42 }).toArray(function(err, data) {
// ...
});
As you can see Non-Chained can accept everything as object, while chained returns cursor after every method and can be reused. So generally you have two options:
For Chained:
var cursor = items.find({ type: 'location' });
if (sort) {
cursor.sort(sort);
}
cursor.toArray(function(err, data) {
// ...
});
For Non-Chained:
var options = { };
if (sort) {
options.sort = sort;
}
items.find({ type: 'location' }, options).toArray(function(err, data) {
// ...
});
It is important to remember that any data from query have to be validated and parsed properly. As well if you are developing API (for example), and will decide to change the way sorting arguments are passed or will want to add new way, then making middleware (in express.js) for parsing this data - is the way to go.
Example for pagination:
function pagination(options) {
return function(req, res, next) {
var limit = options.limit ? options.limit : 0;
var skip = 0;
if (req.query.limit) {
var tmp = parseInt(req.query.limit);
if (tmp != NaN) {
limit = tmp;
}
}
if (req.query.skip) {
var tmp = parseInt(req.query.skip);
if (tmp != NaN && tmp > 0) {
skip = tmp;
}
}
if (options.max) {
limit = Math.min(limit, options.max);
}
if (options.min) {
limit = Math.max(limit, options.min);
}
req.pagination = {
limit: limit,
skip: skip
};
next();
}
}
Usage:
app.get('/items', pagination({
limit: 8, // by default will return up to 8 items
min: 1, // minimum 1
max: 64 // maximum 64
}), function(req, res, next) {
var options = {
limit: req.pagination.limit,
skip: req.pagination.limit
};
items.find({ }, options).toArray(function(err, data) {
if (!err) {
res.json(data);
} else {
next(err);
}
});
});
And url examples:
http://example.com/items
http://example.com/items?skip=64
http://example.com/items?skip=256&limit=32
So it is the way to develop well flexible framework, which does not creates any rules of how things have to be coded as well as solving your challenge.

Categories

Resources