Properly retrieve and display data from Mongoose / MongoDB .find method - node.js

I am working on a command line app in Node.js that involves retrieving results from a database (DB) using Mongoose. One important function I created just is not working no matter how hard I try and how many tutorials I go through.
The main purpose of the function is to look and see if the database (a simple word bank) has an entry containing given phrase / word.
Here is the full code setup for the particular function:
const mongoose = require('mongoose'),
mongoSettings = {
useNewUrlParser: true,
useUnifiedTopology: true
},
wordSchema = new mongoose.Schema(
{ item: String }
);
/**
* Looks for a given word or phrase in database.
* Will eventually return a boolean.
*
* #param {string} word2LookUp Word or phrase to search for
* #param {string} lang Which language list to search in [english (default), spanish, or french]
*/
const inWordBank = (word2LookUp,lang="english") => {
mongoose.connect('mongodb://localhost:27017/word-bank', mongoSettings);
mongoose.connection
.once("open",()=>{
console.log(`// ----- Searching for: "${word2LookUp}"...`);
const Word = mongoose.model('Word', wordSchema,lang);
Word.find({item: word2LookUp}, function (err, words) {
console.log(words); // <---------[ WHERE I'M HAVING TROUBLE ]
});
mongoose.connection.close();
})
.on("error", error => {
console.log(">>> Error: ",error)
mongoose.connection.close();
});
}
module.exports = {
inWordBank: inWordBank
}
This is how I am trying to execute the function on my app.js file:
const wordBank = require("./wordBank");
wordBank.alreadyRegistered("fem");
All I get in the console is:
// ----- Searching for: "fem"...
undefined
undefined
undefined
Some additional tidbits:
There's 3 documents in the particular DB in question that contain "fem" in their "item" section. That's probably why there is undefined 3 times for some odd reason.
The DB works fine when I write to it so I know it's not a connection issue
There are no other errors generated.
In my package.json file, my dependencies look like this:
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^6.2.2"
},
"dependencies": {
"mongoose": "^5.7.10"
}
Is there an important piece of the puzzle I'm simply missing? Or do I have to rewrite this from square 1?

Right now you are searching for the exact value of word2Lookup. To search for documents who's item contains a string, you have to use regex.
So for wordBank.alreadyRegistered("fem"); try using wordBank.alreadyRegistered(/fem/g);.
Or, at the top of inWordBank() you can take the passed string and make a regex out of it like so: word2Lookup = new RegExp(word2Lookup, 'g');. Just put that at the top of your inWordBank() function.
Edit: There is another issue. Since the value is returning undefined, there is actually an error being thrown by MongoDB. Do console.log(err); inside the find callback and that will tell you the error.

As #chirspytoes says it is important to verify if there is no error, you can do it as follows:
Word.find({item: word2LookUp}, function (err, words) {
if(err) {
console.error(err);
return;
}
console.log(words);
});
The second thing to consider is that the results obtained with the find() method come as an array.
Example:
[
{
"item": "fem..."
},
{
"item": "...fem"
},
{
"item": "fem"
},
]

Related

Optional parameters on sequelize query

Good morning.
I'm quite new to NodeJS / sequelize world and I'm currently facing a problem while trying to display a dashboard on screen.
This dashboard has three filters: two dates (period), client name, and employee name. The user can select none, one, two, or all the filters and my database needs to work accordingly.
That being said, my problem is with Sequelize because I don't know how to treat this problem of parameters not being "always" there.
I've seen this question:
Sequelize optional where clause parameters?
but this answer doesn't work anymore. I also tried another way of building the where clause, but I failed on it as well (mainly due to sequelize operators).
The last thing I tried was to make a single query with all parameters included but try to find some value (or flag) that would make sequelize ignore the parameter, for the case when the parameter was no there*, but it looks like Sequelize doesn't have anything like that.
* I've read a question here that has an answer saying that {} would do the trick but I tried that as well but didn't work.
In summary: I need to make a query that can "change" over time, for example:
Foo.findAll({
where: {
id : 1,
}
});
Foo.findAll({
where: {
id {
[Op.in] : [1,2,3,4,5]
},
name: "palmeiira",
}
});
Do you know a way of doing it without the need of using a lot if / switch statements?
I'm currently using Sequelize v. 5.5.1.
Update
I tried doing as suggested by #Anatoly and created a function to build the parameters. It was something like that. (I tried a "smaller" version just to test)
async function test() {
const where = {};
where[Op.and] = [];
where[Op.eq].push({
id: {
[Op.in]: [1,2,3]
}
});
return where;
}
I setted the return value to a const:
const query = await test()
And tried console.log(query)
The result was: { [Symbol(and)]: [ { id: [Object] } ] }, which made me believe that the problem was parsing the Op part so i tried using 'Op.and' and 'Op.in' to avoid that and it solved this problem, but led to another on sequelize that said Invalid value
Do you have any idea where is my error ?
P.S.: #Anatoly very nice idea you gave me on original answer. Thank you very much.
If these three conditions should work together then you can use Op.and with an array of conditions:
const where = {}
if (datesFilter || clientNameFilter || employeenameFilter) {
where[Op.and] = []
if (datesFilter) {
where[Op.and].push({
dateField: {
[Op.between]: [datesFilter.start, datesFilter.finish]
}
})
}
if (clientNameFilter) {
where[Op.and].push({
name: {
[Op.iLike]: `%${clientNameFilter.value}%`
}
})
}
if (employeenameFilter) {
where[Op.and].push({
employeeName: {
[Op.iLike]: `%${employeenameFilter.value}%`
}
})
}
}
const dashboardItems = await DashboardItem.findAll({ where }, {
// some options here
})
If the conditions should work as alternatives then just replace Op.and with Op.or

Node.js Testing with Mongoose. unique gets ignored

I'm having a little trouble with an integration test for my mongoose application. The problem is, that my unique setting gets constantly ignored. The Schema looks more or less like this (so no fancy stuff in there)
const RealmSchema:Schema = new mongoose.Schema({
Title : {
type : String,
required : true,
unique : true
},
SchemaVersion : {
type : String,
default : SchemaVersion,
enum: [ SchemaVersion ]
}
}, {
timestamps : {
createdAt : "Created",
updatedAt : "Updated"
}
});
It looks like basically all the rules set in the schema are beeing ignored. I can pass in a Number/Boolean where string was required. The only thing that is working is fields that have not been declared in the schema won't be saved to the db
First probable cause:
I have the feeling, that it might have to do with the way I test. I have multiple integration tests. After each one my database gets dropped (so I have the same condition for every test and precondition the database in that test).
Is is possible that the reason is my indices beeing droped with the database and not beeing reinitiated when the next text creates database and collection again? And if this is the case, how could I make sure that after every test I get an empty database that still respects all my schema settings?
Second probable cause:
I'm using TypeScript in this project. Maybe there is something wrong in defining the Schema and the Model. This is what i do.
1. Create the Schema (code from above)
2. Create an Interface for the model (where IRealmM extends the Interface for the use in mongoose)
import { SpecificAttributeSelect } from "../classes/class.specificAttribute.Select";
import { SpecificAttributeText } from "../classes/class.specificAttribute.Text";
import { Document } from "mongoose";
interface IRealm{
Title : String;
Attributes : (SpecificAttributeSelect | SpecificAttributeText)[];
}
interface IRealmM extends IRealm, Document {
}
export { IRealm, IRealmM }
3. Create the model
import { RealmSchema } from '../schemas/schema.Realm';
import { Model } from 'mongoose';
import { IRealmM } from '../interfaces/interface.realm';
// Apply Authentication Plugin and create Model
const RealmModel:Model<IRealmM> = mongoose.model('realm', RealmSchema);
// Export the Model
export { RealmModel }
Unique options is not a validator. Check out this link from Mongoose docs.
OK i finally figured it out. The key issue is described here
Mongoose Unique index not working!
Solstice333 states in his answer that ensureIndex is deprecated (a warning I have been getting for some time now, I thought it was still working though)
After adding .createIndexes() to the model leaving me with the following code it works (at least as far as I'm not testing. More on that after the code)
// Apply Authentication Plugin and create Model
const RealmModel:Model<IRealmM> = mongoose.model('realm', RealmSchema);
RealmModel.createIndexes();
Now the problem with this will be that the indexes are beeing set when you're connection is first established, but not if you drop the database in your process (which at least for me occurs after every integration test)
So in my tests the resetDatabase function will look like this to make sure all the indexes are set
const resetDatabase = done => {
if(mongoose.connection.readyState === 1){
mongoose.connection.db.dropDatabase( async () => {
await resetIndexes(mongoose.models);
done();
});
} else {
mongoose.connection.once('open', () => {
mongoose.connection.db.dropDatabase( async () => {
await resetIndexes(mongoose.models);
done();
});
});
}
};
const resetIndexes = async (Models:Object) => {
let indexesReset: any[] = [];
for(let key in Models){
indexesReset.push(Models[key].createIndexes());
}
Promise.all(indexesReset).then( () => {
return true;
});
}

Mongoose - Modle.update() updates wrong document - Cast Error

I need some help to clear some things up.
I have a Model:
var Event = new Schema({
event_code: String
, segments: [Segment]
});
The creation of new documents work very well like perfect. When it comes to update certain documents I ran into some troubles.
When I do this (code below): = it only updates the first document, even if the id does not match
function edit_event (id, new_name, callback) {
Event.update(id, {$set:{event_code: new_name}}, function(err, doc) {
if (err) throw err;
callback();
});
}
When I do this (code below): = it gives me an Error (see below)
function edit_event (id, new_name, callback) {
Event.findByIdAndUpdate(id, {$set:{event_code: new_name}}, function(err, doc) {
if (err) throw err;
callback();
});
}
Error when using findByIdAndUpdate: Cast to ObjectId failed for value ""58fdbde31bff83141b376508"" at path "_id" for model "Event"
Please, i'm desperate :! :/
UPDATE
I figured out that the id that i'm trying to pass get stored with "" around it, so when i am looking for document with matching ID it puts an extra pair of "" around it so it ends up like ""id""
UPDATE 2
When I am listing all my documents, it returns:
{ _id: 58fdbde31bff83141b376508,
event_code: 'TestABC',
__v: 0,
segments: [] }
Then when i store the id in an HTML form it adds extra pair of "" around it ... that's not the case with event_code. Why is that ?
Looks like you performed unneeded JSON.stringify and that's why you get quotes around the id. Calling JSON.parse should solve the issue:
Event.findByIdAndUpdate(JSON.parse(id), {$set:{event_code: new_name}}, ...

Express js Mongoose alternative to MySQL % Wildcard

I've been reading up and tried a few different code snippets that others have had success with, but I can't seem to get it to work.
What I'd like is for users to search using only part of the term i.e pe for 'peter'. I'd like to have a wildcard on the search term.
My code so far, which isn't working:
router.get('/:callsign', function(req,res){
var search = req.params.callsign;
var term = escape(search);
term = term.toUpperCase();
if(search=="*" || search==""){
res.redirect("/");
}
User.find({'callsign' : new RegExp('^'+term+'$', "i") }, function(err, callsign){
if(err)
{
console.log('No user found'+err);
req.flash('message','Sorry, something went wrong. Try again.');
res.render('callSearchResults'),{
message: req.flash('message'),
title: 'Sorry, no results'
}
}
if(callsign){
console.log('Callsign:'+callsign+term);
res.render('callSearchResults',{
call: callsign,
title: 'You searched for '+search,
query: term
});
}else{
console.log('No entries found'+search);
}
});
});
Also, 'callsign' callback is constantly true - even when there are no results!
You are using an RegExp for this search. Literal ^ mean that pattern must be at the beggining of string, and $ that at the end. If you want just to match part you don't need to add them, so example below:
new RegExp(term, "i")
Also there is a good mechanism of full-text search build-in mongo. You can read about them from official docs.
About queries in mongoose, when there is now object and checking in callback. The returned object is Array of documents, and in JS empty array is casted to true statement. Instead check lenght array, like this:
if(callsign.length > 0) {
// Logic
} else {
// Nothing found
}

Updating values in mongodb

I have a mongodb model called User, which has a mixed schema type variable called "inventory" (contains all the items a user contains). I would like to loop through all the users, and change the name of each item in their inventory. In particular, I would like to convert strings in the format of "10_alex_magician" or "3_maia_princess" to "alex_magician" and "maia_princess" respectively. The string conversion is relatively straightforward, and I'm using x.split('').slice(1).join('') to accomplish the conversion.
Where I'm having trouble is even when console.log shows that the conversion has been applied, it doesn't seem to be updating correctly to mongodb, yet no error message is being thrown. Does anyone know how to solve this?
Node.js function
//function to change old naming of items "10_alex_magician" to "alex_magician"
function modifyUser() {
User.find({}, function(err, results) {
_.map(results, function(result) {
var regex = /^\d+_[A-Za-z]+_[A-Za-z]+$/
for (var i = 0, len = result.inventory.length; i < len; i++) {
if(regex.test(result.inventory[i].itemName)) {
result.inventory[i].itemName = result.inventory[i].itemName.split('_').slice(1).join('_');
result.save(function(err, r) {
if(err) console.log(err);
//logging r shows that the text has been correctly updated
console.log(r)
});
}
}
})
})
}
Format of inventory variable
"inventory": [
{
"type": "sticker",
"numberOwned": 2,
"itemName": "1_alex_magician"
},
{
"type": "sticker",
"numberOwned": 1,
"itemName": "10_alex_scuba"
}
],
Mongoose only has automatic change detection for top-level properties and you are modifying a nested property, so mongoose doesn't know anything changed. Use markModified to tell mongoose you are mucking with inventory.
result.inventory[i].itemName = result.inventory[i].itemName.split('_').slice(1).join('_');
result.markModified('inventory');
result.save(....)
For efficiency, you may want to consider both .lean() and .stream() for this type of query and just do your updates with findByIdAndUpdate, passing just the updated inventory property.

Resources