Express js Mongoose alternative to MySQL % Wildcard - node.js

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
}

Related

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

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"
},
]

MongoDB and Mongoose - Creating custom, autoindexed secondary IDs?

I initialize my DB in the usual way:
mongoose.connect(`mongodb://uname:pword#127.0.0.1:port/dbname?authSource=admin`, {useNewUrlParser: true, autoIndex: false});
And I have a Schema, something like:
var materialSchema = new Schema({
bookID: {type: String, required: true},
active: Boolean,
name: {type: String, required: true},
stockLength: {type: Number, required: true}
});
module.exports = mongoose.model('material', materialSchema);
When I create a new material and add it to the database, it is automatically assigned the usual _id - which is a behaviour I want to maintain. BUT, I'd also like for bookID to be a unique, auto-incrementing index. This is for physical shelf storage, and not for queries or anything like that.
I'd like for bookID to increment in the following way:
A-001
A-002
A-003
...
A-098
A-099
A-100
B-001
...
B-100
...
Z-001
...
Z-100
In case the pattern above isn't clear, the pattern starts at A-001 and ultimately ends at Z-100. Each letter goes from 001 through 100 before moving to the next letter. Each new collection entry is just the next ID in the pattern. It is unlikely that the end will ever be reached, but we'll cross that bridge when we get there.
I've only ever used the default _id for indexing, and can't figure out how to make this pattern.
Thanks for any insight!
Edit #1
The best solution I've come up with so far is to have a separate .txt file with all of the IDs listed in order. As each new object is created, pop (... shift) the next ID off the top of the file. This might also have the added benefit of easily adding additional IDs at a later date. This will probably be the approach I take, but I'm still interested in the mongoose solution requested above.
Edit #2
So I think the solution I'm going to use is a little different. Basically, findOne sorted by bookID descending. Then use the value returned to set the next.
Material.findOne()
.sort({bookID : -1})
.exec((err, mat) => {
if(err) {
// Send error
}else if(!mat) {
// First bookID
}else {
// Indexes exist...
let nextId = getNextID(mat.bookID);
// ...
}
});
Still easy to modify getNextID() to add new/different IDs in the future (if/when "Z100" is reached)
Thanks again!
Ok, so to expand a little bit on Edit #2, I've come up with the following solution.
Within the model (schema) file, we add a schema pre() middleware, that executes when .save() is called, before the save occurs:
// An arrow function will not work on this guy, if you want to use the "this" keyword
materialSchema.pre('save', function(next) {
this.model('material').findOne() // Don't forget the .model(...) bit!
.sort({bookID : -1}) // All I need is the highest (i.e. most recent) bookID
.select('bookID') // Ditto above (not really necessary)
.exec((err, result) => {
if(err) {
return next(err); // Oopsies, an error!
}else if(!result) {
this.bookID = 'A-001'; // The case when collection is empty
}else {
this.bookID = getNextID(result.bookID); // Otherwise, increment ID
}
next(); // Don't forget this sucker! This is how you save
});
});
And that's about it! It isn't an in-built solution direct from Mongoose, but it works a treat.
Just for completeness, the getNextID function looks like:
function getNextID(curID) {
let letter = curID.split('-')[0];
let number = parseInt(curID.split('-')[1]);
if(number >= 100) { // Increase the letter and reset the number
letter = String.fromCharCode(letter.charCodeAt(0) + 1)
number = '001';
}else { // Only increase the number
number = ('' + (number + 1)).padStart(3, '0'); // Makes sure the numbers are always 3 digits long
}
return `${letter}-${number}`;
}
This'll do just dandy for now. Until we get to Z100. But I'll cross that bridge if/when it comes. No big deal at all.
And you don't need to do anything special to use it. Just save a new doc as normal, and it automatically fires:
new Material({
// New material properties
}).save((err, mat) => {
// Handle errors and returns ...
});

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}}, ...

How to define a sort function in Mongoose

I'm developing a small NodeJS web app using Mongoose to access my MongoDB database. A simplified schema of my collection is given below:
var MySchema = mongoose.Schema({
content: { type: String },
location: {
lat: { type: Number },
lng: { type: Number },
},
modifierValue: { type: Number }
});
Unfortunately, I'm not able to sort the retrieved data from the server the way it is more convenient for me. I wish to sort my results according to their distance from a given position (location) but taking into account a modifier function with a modifierValue that is also considered as an input.
What I intend to do is written below. However, this sort of sort functionality seems to not exist.
MySchema.find({})
.sort( modifierFunction(location,this.location,this.modifierValue) )
.limit(20) // I only want the 20 "closest" documents
.exec(callback)
The mondifierFunction returns a Double.
So far, I've studied the possibility of using mongoose's $near function, but this doesn't seem to sort, not allow for a modifier function.
Since I'm fairly new to node.js and mongoose, I may be taking a completely wrong approach to my problem, so I'm open to complete redesigns of my programming logic.
Thank you in advance,
You might have found an answer to this already given the question date, but I'll answer anyway.
For more advanced sorting algorithms you can do the sorting in the exec callback. For example
MySchema.find({})
.limit(20)
.exec(function(err, instances) {
let sorted = mySort(instances); // Sorting here
// Boilerplate output that has nothing to do with the sorting.
let response = { };
if (err) {
response = handleError(err);
} else {
response.status = HttpStatus.OK;
response.message = sorted;
}
res.status(response.status).json(response.message);
})
mySort() has the found array from the query execution as input and the sorted array as output. It could for instance be something like this
function mySort (array) {
array.sort(function (a, b) {
let distanceA = Math.sqrt(a.location.lat**2 + a.location.lng**2);
let distanceB = Math.sqrt(b.location.lat**2 + b.location.lng**2);
if (distanceA < distanceB) {
return -1;
} else if (distanceA > distanceB) {
return 1;
} else {
return 0;
}
})
return array;
}
This sorting algorithm is just an illustration of how sorting could be done. You would of course have to write the proper algorithm yourself. Remember that the result of the query is an array that you can manipulate as you want. array.sort() is your friend. You can information about it here.

Not able to create index using mongoose

I am building a search feature in a Node.js application which uses Mongoose, creating a full-text search index in Mongo:
postSchema = mongoose.Schema({
xx : String
.......
}).index({
'xx':'text'
});
Post = mongoose.model('Post',postSchema);
And execute the search as follows:
find = {'$text':{'$search':query}};
findScore = {'score':{'$meta':'textScore'}};
sort = {'score': {'$meta':'textScore'} };
Post.find(find, findScore).sort(sort).exec(function (err, data) {
if (err){
logging.log(err);
} else {
logging.log(data);
}
});
When I run the program, I get the following error:
'Unable to execute query: error processing query \n planner returned error: need exactly one text index for $text query'
Any help would be appreciated.
A collection can have at most one text index. You need to check it any of the text index is previously created.

Resources