Mongoose model deleteOne() only works with hard coded string - node.js

I have a strange error with mongoose deleteOne() function. Today I wanted to work on my project and got an error while deleting an item from a collection. It simply doesn't delete the document until I use a hardcoded parameter for the options object like this:
const { deletedCount } = await Model.deleteOne({symbol: 'hardcoded'})
// results in deletedCount = 1
But if I try to use a dynamic string like:
const test = 'dynamic'
const { deletedCount } = await Model.deleteOne({symbol: test})
// results in deletedCount = 0
It does no longer delete the document from my collection. The strange thing is yesterday it worked fine and deleted the item.
I tried one other thing I read regarding errors with deleteOne():
const { deletedCount } = await Model.deleteOne({symbol: JSON.stringifiy(symbol)})
But this doesn't work, too.
Does anyone have an idea what's going wrong?

I always default to using ids whenever possible to make sure there's no mistake in the data I am targeting with a given operation.
So in this case that would mean using findByIdAndDelete() instead.
If I don't know the id of the document I'm trying to delete, then only I'd use findOneAndDelete() or deleteOne(), as you have, with something other than an id to identify the document I'm looking for.
Are you certain that the key-value pair you're passing to the function exists in your database?

Problem solved. I accidentally added an additional space character at the end of the string. This is very strange because the error was there since the beginning of my project and yesterday it worked.
So for everyone who might have a similar problem:
I have a ejs template file where I render a html element like this:
<div id="<%= symbol %> ">
Then in my event handler for requesting the server to deleting one item from my list I use the id attribute as a request parameter in the body. In the route handler this parameter is passed to mongoose:
const { symbol } = req.body
const { deletedCount } = await Model.deleteOne({ symbol })
As I mentioned. In the template file after the last ejs seperator there is an addional space character that caused the error. I spotted this issue by making a copy of the monoogse query and than logged it to the console. There I could see the wrong condition parameter.

Related

Different variable name case convention in one application

This is a really trivial problem. I am just curious on how to deal with this in a "professional" manner.
I am trying to stick to variable naming convention. For NodeJs I am doing camelCasing. For database, I am using PostgreSQL and using underscore_casing.
Now the problem arises when I query data from PostgreSQL. I'll get a user object with following format,
{user_id: 1, account_type : "Admin"}
I can pass this object directly to server side-render and will have to use underscore casing to access account_type. Of course, I can manually create a new user JSON object with property userId and accountType but that is unnecessary work.
Is it possible to follow variable naming convention for both language and avoid having mixed variable names casing in some files? What is a good way to stay organized?
The are two good ways to approach this issue. The simplest one - do no conversion, use the exact database names. And the second one is to camel-case columns automatically.
Either way, you should always follow the underscore notation for all PostgreSQL declarations, as it will give you the option to activate camel-casing in your app at a later time, if it becomes necessary. Never use camel-case inside the database, or you will end up in a lot of pain later.
If you want the best of both worlds, follow the underscore notation for all PostgreSQL declarations, and convert to camel-case as you read data.
Below is an example of how to do it properly with pg-promise, copied from event receive example:
// Example below shows the fastest way to camelize column names:
const options = {
receive(e) {
camelizeColumns(e.data);
}
};
function camelizeColumns(data) {
const template = data[0];
for (var prop in template) {
const camel = pgp.utils.camelize(prop);
if (!(camel in template)) {
for (var i = 0; i < data.length; i++) {
const d = data[i];
d[camel] = d[prop];
delete d[prop];
}
}
}
}
Also see the following article: Pg-promise and case sensitivity in column names.
UPDATE
The code above has been updated for use of pg-promise v11 or later.
I've struggled with this too, and I've concluded that there's really no way to avoid this kind of ugliness unless you rewrite the objects that come from the database. Fortunately, that's not too difficult in Javascript:
const fromDBtoJS = (obj) => {
// declare a variable to hold the result
const result = {};
// iterate over the keys on the object
Object.keys(obj).forEach((key) => {
// adjust the key
const newKey = key.replace(/_[a-z]/g, (x) => x[1].toUpperCase());
// add the value from the old object with the new key
result[newKey] = obj[key];
});
// return the result
return result;
};
Here's a JSFiddle. The "replace" code above was found here
If you wanted to use classes for models in your application, you could incorporate this code into the constructor or database load method so it's all handled more-or-less automatically.

Firebase node.js remove item from dictionary

I can successfully add and fetch values from my firebase database.
firebase.initializeApp(config);
// Get a reference to the database service
var database = firebase.database();
var ref = database.ref();
var refer = database.ref('todo');
I have a todo list in my database, I wanted use an array but from what I understand firebase doesnt prefer arrays.
So I just add values to my todo list like
function handleAddTodoIntent(intent, session, response) {
refer.push(todoSlot.value);
}
and it works
But when I try to remove the items , it seems it doesnt do anything
refer.remove(); removes entire todo dictionary and refer.child(todoSlot.value).remove(); seems not deleting the value from database.
What is the correct way of doing this?
Your mistake is refer.child(todoSlot.value).
You are referencing the wrong path. child() should take the name of the field, not a value.
When you push a new child, you can get its key by following:
var todoSlotKey = refer.push(todoSlot.value);
And if you just want to remove the pushed todoSlot.value, you can simply do: refer.child(todoSlotKey).remove();

MongoDB update object and remove properties?

I have been searching for hours, but I cannot find anything about this.
Situation:
Backend, existing of NodeJS + Express + Mongoose (+ MongoDB ofcourse).
Frontend retrieves object from the Backend.
Frontend makes some changes (adds/updates/removes some attributes).
Now I use mongoose: PersonModel.findByIdAndUpdate(id, updatedPersonObject);
Result: added properties are added. Updated properties are updated. Removed properties... are still there!
Now I've been searching for an elegant way to solve this, but the best I could come up with is something like:
var properties = Object.keys(PersonModel.schema.paths);
for (var i = 0, len = properties.length; i < len; i++) {
// explicitly remove values that are not in the update
var property = properties[i];
if (typeof(updatedPersonObject[property]) === 'undefined') {
// Mongoose does not like it if I remove the _id property
if (property !== '_id') {
oldPersonDocument[property] = undefined;
}
}
}
oldPersonDocument.save(function() {
PersonModel.findByIdAndUpdate(id, updatedPersonObject);
});
(I did not even include trivial code to fetch the old document).
I have to write this for every Object I want to update. I find it hard to believe that this is the best way to handle this. Any suggestions anyone?
Edit:
Another workaround I found: to unset a value in MongoDB you have to set it to undefined.
If I set this value in the frontend, it is lost in the REST-call. So I set it to null in the frontend, and then in the backend I convert all null-values to undefined.
Still ugly though. There must be a better way.
You could use replaceOne() if you want to know how many documents matched your filter condition and how many were changed (I believe it only changes one document, so this may not be useful to know). Docs: https://mongoosejs.com/docs/api/model.html#model_Model.replaceOne
Or you could use findOneAndReplace if you want to see the document. I don't know if it is the old doc or the new doc that is passed to the callback; the docs say Finds a matching document, replaces it with the provided doc, and passes the returned doc to the callback., but you could test that on your own. Docs: https://mongoosejs.com/docs/api.html#model_Model.findOneAndReplace
So, instead of:
PersonModel.findByIdAndUpdate(id, updatedPersonObject);, you could do:
PersonModel.replaceOne({ _id: id }, updatedPersonObject);
As long as you have all the properties you want on the object you will use to replace the old doc, you should be good to go.
Also really struggling with this but I don't think your solution is too bad. Our setup is frontend -> update function backend -> sanitize users input -> save in db. For the sanitization part, we use a helper function where we integrate your approach.
private static patchModel(dbDocToUpdate: IModel, dataFromUser: Record<string, any>): IModel {
const sanitized = {};
const properties = Object.keys(PersonModel.schema.paths);
for (const key of properties) {
if (key in dbDocToUpdate) {
sanitized[key] = data[key];
}
}
Object.assign(dbDocToUpdate, sanitized);
return dbDocToUpdate;
}
That works smoothly and sets the values to undefined. Hence, they get removed from the document in the db.
The only problem that remains for us is that we wanted to allow partial updates. With that solution that's not possible and you always have to send everything to the backend.
EDIT
Another workaround we found is setting the property to an empty string in the frontend. Mongo then also removes the property in the database

Inserting a variable as a key in MongoDB

I am retrieving JSON data from an API and this is a short example of it:
{"hatenames":
{"id":6239,
"name":"hatenames",
"stat1":659,
"stat2":30,
"stat3":1414693
}
}
I am trying to insert it in the MongoDB (using MongoClient) but it won't let me put just the object directly or use a variable as a field name. If I put the var username it will just output it as username in the database field. This is what I would like to work:
collection.insert({object}
collection.insert({username:object[username]}
but it doesn't and I've been stuck on this for the past few hours. The only resolution I found was to set it and then update the field name afterwards, but that just seems lame to have to do every single time, is there no elegant or easy option that I am somehow missing?
First of all, being a programmer, you should forget about "it does not work" phrase. You should describe how it does not work with exact error messages you encounter. Not to your problem.
Just because I can easily do
db.coll.insert({"hatenames":
{"id":6239,
"name":"hatenames",
"stat1":659,
"stat2":30,
"stat3":1414693
}
})
or var a = {"hatenames":{"id":6239, "name":"hatenames", "stat1":659, "stat2":30, "stat3":1414693}}; and db.coll.insert(a),
I think that the problem is that your object is not really an object, but a string. So I suspect that you have a string returned to you back from that API. Something like '{"hatenames":{...}' and this caused a problem when you try to save it or access the properties. So try to convert it to JSON.
Try doing this:
MongoClient.connect('mongodb://127.0.0.1:27017/db_name', function(err, db) {
if(err) throw err;
someCollection = db.collection('some_collection');
});
someCollection.insert({
name: hatenames['name']
});
EDIT
For Dynamic approach, I would suggest you to play aroung this function:
Object.keys(hatenames)
this function will return keys in array.
EDIT 2
I have founded a link: Insert json file into mongodb using a variable
See, if that helps.

MODX - Making multiple snippet calls on a page returning differeny output

I've created a snippet that pulls data from a databse table and displays it in tabular format. The snippet takes an id as parameter, and this is added to the sql query.
My problem is that if I've got more than 1 snippet call (sometimes need the tabular data for different id's displayed on a page) on the same page, all table data is the same as the last database call that's been made by the last snippet.
What do I need to do to kinda not cache the snippet database calls and have them all display their own content?
I've tried setting the page to no cache-able. Also used the [! !] brackets for the snippet calls, and even used the function_exists() method, but none of them helped.
Please can someone help me?
thanks
Try this at the end of the snippet:
mysql_connect('host', 'user', 'pass');
mysql_select_db('db_name');
You need to specify the connection parameters ofcourse.
It would help to answer if you can post your snippet. I do this with multiple calls on the page without issue, so there is either something wrong inside the snippet, or you need to output to unique placeholder names.
You have encountered a glitch of ModX, and it took me a long time to solve. ModX does a lot of caching by using hashing and apparently, when multiple connections are made from within one page divided over multiple snippets, this erratic behaviour can be seen. This is most likely very unwanted behaviour, it can be solved easily but gives you terrible headache otherways.
One sympton is that $modx->getObject($classname, $id)returns null (often).
The solution is very simple:
either use a static class with a single db instance, or
use $modx->setPlaceholder($instance, $tag);, or a combination.
My solution has been:
class dt__xpdo {
private function __construct() {}
public function __destruct() {
$this->close();
}
static public function db($modx = null) {
if ($modx->getPlaceholder('dt_xpdo') == '') {
$dt_user = 'xxxxxxxxx';
$dt_pw = 'xxxxxxxxx';
$dt_host = 'localhost';
$dt_dbname = 'xxxxxxxxx';
$dt_port = '3306';
$dt_dsn = "mysql:host=$dt_host;dbname=$dt_dbname;port=$dt_port;charset=utf8";
$dt_xpdo = new xPDO($dt_dsn, $dt_user, $dt_pw);
$dt_xpdo->setPackage('mymodel', MODX_CORE_PATH.'components/mymodel/'.'model/', '');
//$modx->log(modX::LOG_LEVEL_DEBUG, 'mymodel.config.php');
//$modx->log(modX::LOG_LEVEL_DEBUG, 'Could not addPackage for mymodel!');
$modx->setPlaceholder('dt_xpdo', $dt_xpdo);
}
return $modx->getPlaceholder('dt_xpdo');
}
}
Now you can use in your code:
require_once 'above.php';
and use something like
$xpdo = dt__xpdo::db($modx);
and continue flawlessly!

Resources