Sails ORM doesn't understand numeric values? - javascript

Well this should work, everything (when going through it) step by step works. Except the actual writing to the database. In my sails application I have a route to the following function:
add: function (req, res) {
let params = req.allParams();
let idx = params.event_id;
let name = params.name;
let sport = params.sport;
let location = params.location;
let idNr = Number(idx);
let sportNr = Number(sport);
let locationNr = Number(location);
let dat = {
name:name,
id:idNr,
sport:sportNr,
location:locationNr
};
console.log(dat);
Event.create(dat)
.then(function (res){ return res.ok(); })
.catch(function (err){
console.log(err);
return res.send(err);
});
}
Really a simplistic function right? Yet on my "good" data it fails:
{
"error": "E_VALIDATION",
"status": 400,
"summary": "2 attributes are invalid",
"model": "Event",
"invalidAttributes": {
"location": [
{
"rule": "number",
"message": "Value should be a number (instead of \"3\", which is a string)"
}
],
"sport": [
{
"rule": "number",
"message": "Value should be a number (instead of \"2\", which is a string)"
}
]
}
}
How can this happen? I cleary am using dat which takes a numeric variation of the "sport" and "location". - Logging even shows that the dat is filled as expected with numeric values.
So it makes no sense at all that this fails; it shouldn't.
EDIT, the model is defined as:
module.exports = {
attributes: {
id: {
type: 'number',
required: true,
unique: true,
},
name: {
type: 'string',
required: true
},
location: {
type: 'number',
required: true,
},
sport: {
type: 'number',
required: true
},
},
};

This issue appears to be a limitation of sails support for
type:'number'
From the official sails documentation on model attributes. The following attribute types are supported:
string
text
integer
float
date
datetime
boolean
binary
array
json
mediumtext
longtext
objectid
As an alternative to using the javascript
Number()
function, you could consider using
parseInt()
for known integer values or
parsefloat()
for known floating point numbers.

Related

How to insert ref objectId in nodejs?

Main Model
{
name: {
type: String,
trim: true,
required: true
},
carModels: [{
type: ObjectId,
ref: 'CarModel'
}]
}
Second Model
{
name: {
type: String,
trim: true,
required: true
},
carModels: [
{
type: ObjectId,
ref: 'CarModel'
}
]
},
Third Model
{
name: {
type: String,
trim: true,
required: true
}
},
Here i am trying to insert the data like this
{
"name": "test",
"phoneNumber": "0123456789",
"email": "m#m.com",
"carMakes": [{
"name": "BMW",
"carModels": [{
"_id": "some id"
}]
}]
}
and it giving me error like
carMakes.0: Cast to [ObjectId] failed for value
here is the create function
export const create = async data => {
const result = await Booking(data).save();
return result;
};
Can anyone tell what I am missing here ..i am learning nodejs
i think the problem is with the _id that you're passing to carModel and since you set the type to ObjectId it has to be in a valid format "either 12 byte binary string, or a 24 hex byte string" and "some id" is not the valid one if you're sending that.
you can check if your id is valid with isValidObjectId() function.
or you can easily generate an ObjectId:
var mongoose = require('mongoose');
var id = new mongoose.Types.ObjectId();

Unable to update Mongoose Nested Array

I've spent a day going through Mongoose documentation and posts on here and while I'm almost certain I have the code as prescribed in all those solved posts I simply cannot get my code to work. :(
The schema is relatively complex but by no means as complex as I've seen:
const hrUserSchema = new mongoose.Schema({
username: String,
displayName: String,
school: String,
leaveAuthoriser: String,
holidayLeft: Number,
holidayAllowed: Number,
discretionaryDays: Number,
emailAddress: String,
termTimeOnly: Boolean,
noOfAdditionalContractedDays: Number,
noOfAdditionalContractedDaysLeft: Number,
teachingStaff: Boolean,
employStartDate: String,
notes: String,
loginAttempts: Number,
lineManages: [{
staffName: String
}],
bookedHoliday: [{
dateEntered: String,
startDate: String,
endDate: String,
daysTaken: Number,
status: String,
approvedBy: String,
leaveType: String,
declineReason: String
}],
additionalContractedDays: [{
acdDateEntered: String,
acdStartDate: String,
acdEndDate: String,
acdDaysTaken: Number,
acdStatus: String,
acdApprovedBy: String,
acdDeclineReason: String
}],
perfMan: [{
year: Number,
selfReview: {
sref1: String,
sref2: String,
sref3: String,
sref4: String,
sref5: String,
status: String,
dateStarted: String,
dateCompleted: String,
signedOff: Boolean
},
stage1: {
objectives : [
{
objective: String,
objectiveLinkToSchoolTeam: String,
objectiveProgress: Boolean
}
],
personalDevelopment: String,
resourcesTraining: String,
appraiserSignOff: Boolean,
appraiseeSignOff: Boolean
},
stage2: {
feedback: String,
appraiserSignOff: Boolean,
appraiseeSignOff: Boolean
},
stage3: {
feedback: String,
appraiserSignOff: Boolean,
appraiseeSignOff: Boolean
}
}]
});
Basically I want to update perfMan.stage1.objectives.objectiveProgress, example of what data in that might look like is:
"perfMan" : [
{
"_id" : ObjectId("60cb502631dcea3eaaae6853"),
"selfReview" : {
"sref1" : "I have no strength",
"sref2" : "No developments",
"sref3" : "None I'm brill",
"sref4" : "The department has no aims",
"signedOff" : true
},
"stage1" : {
"objectives" : [
{
"_id" : ObjectId("60cb502631dcea3eaaae6854"),
"objective" : "Objective is pants",
"objectiveLinkToSchoolTeam" : "I hate objectives!",
"objectiveProgress" : false
},
{
"_id" : ObjectId("60cb502631dcea3eaaae6855"),
"objective" : "My second Objectoves",
"objectiveLinkToSchoolTeam" : "My Second reasons",
"objectiveProgress" : false
}
],
"personalDevelopment" : "My personal Development",
"resourcesTraining" : "My Resources"
},
"stage2" : {
"feedback" : "Keep working HARD",
"appraiserSignOff" : true
},
"stage3" : {
"feedback" : "Yoy've done really well",
"appraiserSignOff" : true
},
"year" : NumberInt(2021)
}
]
I've read about and tried to implement this by using arrayFilters which to me seems to exactly what I want, I've checked my Mongoose version (hosted) and it's 4.4.6 so I'm easily running above 3.6 which I think is what's needed.
My current code looks like this, I've confirmed the find is getting the right data:
HRUser.findOneAndUpdate(
{"username": username},
{"$set": {"perfMan.$[perfMan].stage1.objectives.$[objectives].objectiveProgress" : true}},
{"arrayFilters" : [{ "perfMan._id": ObjectId("" + perfmanID + "") },{ "objectives._id": ObjectId("" + objectiveID + "") }]}
),
function (err, response) {
console.log(err)
console.log(response);
if (!err) {
res.send("Successfully updated staff member details.");
} else {
res.send(err);
}
};
If somebody could spot my obviously glaring error I would be for ever grateful!
Thanks
After not looking at a screen all weekend and reading through the documentation for the billionth time I decided to change tack! I can't see any limitations on the number of arrayFilters, or for that matter objectID use within them, and I'm 99% certain both object IDs are correct but, after changing the code to the below, basically using a mix of a positional operator $ based on the search of the same first objectID AND an arrayFilter element it's now working! Code below:
HRUser.findOneAndUpdate({
perfMan: {
$elemMatch: {
_id: ObjectId("" + perfManID + "")
}
}
},
{"$set": {"perfMan.$.stage1.objectives.$[objectives].objectiveProgress" : false}},
{"arrayFilters" : [{ "objectives._id": ObjectId("" + objectiveID + "") }]},
function (err, response) {
console.log(err)
console.log(response);
});

use mongooseModel.find() on schema that has a Schema.Types.Mixed property

I have trouble using postModel.find() query in a schema that defined as Schema.Types.Mixed.
this is a sample of my schema
const PostSchema = new mongoose.Schema({
//.....
address: {
type: String,
required: true,
},
postDetails: {
type: Schema.Types.Mixed,
required: true,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
},
//.....
});
this is a sample document stored in db
{
//.....
"state": "Lakes State",
"address": "some address",
"postDetails": {
"type": "Cages",
"condition": "Used"
},
//......
}
it is giving me an empty array if I use this
const queryObject = {
postDetails: {
type: 'Cages',
},
};
return this.postModel.find(queryObject);
but it gives the desired results if I include all the properties like this
const queryObject = {
postDetails: {
type: 'Cages',
condition: 'Used',
},
};
return this.postModel.find(queryObject);
How do i get all matching posts that have postDetails.type = 'Cages' ? without knowing all available properties inside postDetails
there are some similar questions about this here. but most of them are using arrays instead of an object
You can use dot notation for querying embedded documents
postModel.find({
"postDetails.type": "Cages"
});

Information isn't being passed to an array via Mongoose, can't work out why

Apologies if this has been answered before, I have checked other answers and can't work it out from those.
I have a set of information that I would like placed into an array named "teamDetails". Here is the relevant /post item from server.js:
app.post('/create', (req, res) => {
console.log('Post command received');
console.log(req.body);
console.log(req.body.data.teamDetails[0]);
//We need to push the variable below, 'teamDetails', as an object into an array of the same name
var teamDetailsObj = {
// Modified for Postman
"teamName": req.body.data.teamDetails[0].teamName,
"teamNameShort": req.body.data.teamDetails[0].teamNameShort,
"teamfounded": req.body.data.teamDetails[0].teamFounded,
"teamHome": req.body.data.teamDetails[0].teamHome
};
console.log(teamDetails);
var newTeam = new Team({
"data.added": new Date(),
"data.entry": req.body.data.entry
});
newTeam.save().then((doc) => {
console.log("This is newTeam.data: " + newTeam.data);
console.log("This is teamDetailsObj: " + teamDetailsObj);
newTeam.data.teamDetails.push(teamDetailsObj);
var teamId = doc.id;
res.render('success.hbs', {teamId});
console.log("Team Added - " + teamId);
}, (e) => {
res.status(400).send(e);
});
});
Here is my team.js model:
var mongoose = require('mongoose');
var ObjectID = mongoose.Schema.Types.ObjectId;
var Mixed = mongoose.Schema.Types.Mixed;
var Schema = mongoose.Schema;
var Team = mongoose.model('Team', {
data: {
entry: {
type: String,
default: "USER.INPUT"
},
added: {
type: Date,
default: Date.Now
},
teamDetails: [
{
teamName: {
type: String,
trim: true,
required: true,
default: "First Team"
},
teamNameShort: {
type: String,
trim: true,
uppercase: true,
maxlength: 3,
required: true
},
teamFounded: {
type: Number,
maxlength: 4
},
teamHomeCity: {
type: String
}
}
]
}
});
module.exports = {Team};
Lastly, the sample data I'm trying to inject via Postman:
{
"data": {
"entry": "Postman.Entry",
"teamDetails": [
{
"teamName": "Test Teamname",
"teamNameShort": "TTN",
"teamFounded": "1986",
"teamHome": "London",
"players": [
{
"player1Name": "Test Player 1",
"player1Position": "Forward",
"player1Nationality": "GBR"
},
{
"player2Name": "Test Player 2",
"player2Position": "Defender",
"player2Nationality": "UKR"
},
{
"player3Name": "Test Player 3",
"player3Position": "Goaltender",
"player3Nationality": "IRL",
"captain": true
}
],
"coachingStaff": {
"headCoach": "Derp McHerpson",
"teamManager": "Plarp McFlarplarp"
}
}
]
}
}
(Disregard the players section, it's another kettle of fish)
As a result of using my code above, the resulting entry for teamDetails is just an empty array. I just can't get my code to push the teamDetailsObj into it.
Any help anyone can provide is appreciated.
It looks like you add teamObjDetails AFTER saving it with newTeam.save().then( ... )
I'm not a lot familiar with Mongoose but I don't see how could the team details could be present if not added before saving.
Let me know if it changes something !
A. G

MongoDB queries optimisation

I wish to retrieve several information from my User model that looks like this:
var userSchema = new mongoose.Schema({
email: { type: String, unique: true, lowercase: true },
password: String,
created_at: Date,
updated_at: Date,
genre : { type: String, enum: ['Teacher', 'Student', 'Guest'] },
role : { type: String, enum: ['user', 'admin'], default: 'user' },
active : { type: Boolean, default: false },
profile: {
name : { type: String, default: '' },
headline : { type: String, default: '' },
description : { type: String, default: '' },
gender : { type: String, default: '' },
ethnicity : { type: String, default: '' },
age : { type: String, default: '' }
},
contacts : {
email : { type: String, default: '' },
phone : { type: String, default: '' },
website : { type: String, default: '' }
},
location : {
formattedAddress : { type: String, default: '' },
country : { type: String, default: '' },
countryCode : { type: String, default: '' },
state : { type: String, default: '' },
city : { type: String, default: '' },
postcode : { type: String, default: '' },
lat : { type: String, default: '' },
lng : { type: String, default: '' }
}
});
In Homepage I have a filter for location where you can browse Users from Country or City.
All the fields contains also the number of users in there:
United Kingdom
All Cities (300)
London (150)
Liverpool (80)
Manchester (70)
France
All Cities (50)
Paris (30)
Lille (20)
Nederland
All Cities (10)
Amsterdam (10)
Etc...
This in the Homepage, then I have also the Students and Teachers pages where I wish to have information only about how many teachers there are in those Countries and Cities...
What I'm trying to do is to create a query to MongoDB to retrieve all these information with a single query.
At the moment the query looks like this:
User.aggregate([
{
$group: {
_id: { city: '$location.city', country: '$location.country', genre: '$genre' },
count: { $sum: 1 }
}
},
{
$group: {
_id: '$_id.country',
count: { $sum: '$count' },
cities: {
$push: {
city: '$_id.city',
count: '$count'
}
},
genres: {
$push: {
genre: '$_id.genre',
count: '$count'
}
}
}
}
], function(err, results) {
if (err) return next();
res.json({
res: results
});
});
The problem is that I don't know how to get all the information I need.
I don't know how to get the length of the total users in every Country.
I have the users length for each Country.
I have the users length for each city.
I don't know how to get the same but for specific genre.
Is it possible to have all these information with a single query in Mongo?
Otherwise:
Creating few promises with 2, 3 different requests to Mongo like this:
getSomething
.then(getSomethingElse)
.then(getSomethingElseAgain)
.done
I'm sure it would be easier storing every time specified data but: is it good for performance when there are more than 5000 / 10000 users in the DB?
Sorry but I'm still in the process of learning and I think these things are crucial to understand MongoDB performance / optimisation.
Thanks
What you want is a "faceted search" result where you hold the statistics about the matched terms in the current result set. Subsequently, while there are products that "appear" to do all the work in a single response, you have to consider that most generic storage engines are going to need multiple operations.
With MongoDB you can use two queries to get the results themselves and another to get the facet information. This would give similar results to the faceted results available from dedicated search engine products like Solr or ElasticSearch.
But in order to do this effectively, you want to include this in your document in a way it can be used effectively. A very effective form for what you want is using an array of tokenized data:
{
"otherData": "something",
"facets": [
"country:UK",
"city:London-UK",
"genre:Student"
]
}
So "factets" is a single field in your document and not in multiple locations. This makes it very easy to index and query. Then you can effectively aggregate across your results and get the totals for each facet:
User.aggregate(
[
{ "$unwind": "$facets" },
{ "$group": {
"_id": "$facets",
"count": { "$sum": 1 }
}}
],
function(err,results) {
}
);
Or more ideally with some criteria in $match:
User.aggregate(
[
{ "$match": { "facets": { "$in": ["genre:student"] } } },
{ "$unwind": "$facets" },
{ "$group": {
"_id": "$facets",
"count": { "$sum": 1 }
}}
],
function(err,results) {
}
);
Ultimately giving a response like:
{ "_id": "country:FR", "count": 50 },
{ "_id": "country:UK", "count": 300 },
{ "_id": "city:London-UK", "count": 150 },
{ "_id": "genre:Student": "count": 500 }
Such a structure is easy to traverse and inspect for things like the discrete "country" and the "city" that belongs to a "country" as that data is just separated consistently by a hyphen "-".
Trying to mash up documents within arrays is a bad idea. There is a BSON size limit of 16MB to be respected also, from which mashing together results ( especially if you are trying to keep document content ) is most certainly going to end up being exceeded in the response.
For something as simple as then getting the "overall count" of results from such a query, then just sum up the elements of a particular facet type. Or just issue your same query arguments to a .count() operation:
User.count({ "facets": { "$in": ["genre:Student"] } },function(err,count) {
});
As said here, particularly when implementing "paging" of results, then the roles of getting "Result Count", "Facet Counts" and the actual "Page of Results" are all delegated to "separate" queries to the server.
There is nothing wrong with submitting each of those queries to the server in parallel and then combining a structure to feed to your template or application looking much like the faceted search result from one of the search engine products that offers this kind of response.
Concluding
So put something in your document to mark the facets in a single place. An array of tokenized strings works well for this purpose. It also works well with query forms such as $in and $all for either "or" or "and" conditions on facet selection combinations.
Don't try and mash results or nest additions just to match some perceived hierarchical structure, but rather traverse the results received and use simple patterns in the tokens. It's very simple to
Run paged queries for the content as separate queries to either facets or overall counts. Trying to push all content in arrays and then limit out just to get counts does not make sense. The same would apply to a RDBMS solution to do the same thing, where paging result counts and the current page are separate query operations.
There is more information written on the MongoDB Blog about Faceted Search with MongoDB that also explains some other options. There are also articles on integration with external search solutions using mongoconnector or other approaches.

Categories

Resources