Updating multiple value in array in MongoDB - javascript

I receive a jsonObject and want to perfom a Mongo-DB update:
The jsonObject: "tablename":"1","inventar":[{"ean":"802.6180.222"},{"ean":"657.7412.878"}]}
The existing document (shortened):
"tablename": "1",
"accepted": false,
"inventar": [
{
"ean": "802.6180.222",
"accepted": "0"
},
{
"ean": "657.7412.878",
"accepted": "0"
}
],
I need to set the accepted value to "1" for each object in the Array (which is in the invetar-jsonObject.)
The Code:
app.post('/in_accept', function(request,response){
var jsonString=request.body.json;
var jsonObj = JSON.parse(jsonString);
var InUser = jsonObj.in_user;
var InDate = jsonObj.in_date;
var inventar = jsonObj.inventar; //is an Array
var tablename = jsonObj.tablename;
console.log(inventar);
var query = {"tablename": tablename};
var update = {"accepted": true, CODE FOR UPDATING INVENTAR};
var options = {"upsert": false, "new": true};
Move.findOneAndUpdate(query, update, options,
function(err,Move) {
console.log( Move );
});
response.json({"success": true});
});
I know that mongoDB provides the operator "each" but I stuck on the whole syntax.
For each "ean" the accepted value should be set on "1".
Thanks

The only real "sane" way to do this aside of retrieving the object via .findOne() or variant then making modfications in code and calling .save() ( which is not considered "sane" as the concurency issues juyst make that approach "mental' ), is to perform "multiple" updates, or essentially one for each array member you want to change.
Your "best" approach is to gig into the core driver right now and get access to the Bulk Operations API methods:
var input = { "tablename":"1","inventar":[{"ean":"802.6180.222"},{"ean":"657.7412.878"}]},
bulk = Move.collection.initializeOrderedBulkOp();
// Build the statements
input.inventar.forEach(function(inventar) {
bulk.find({
"tablename": input.tablename,
"inventar.ean": inventar.ean
}).updateOne({
"$set": { "inventar.$.accepted": 1 }
});
});
// Then execute
bulk.execute(function(err,result) {
if (!err) {
response.json({ "sucess": true })
} else {
// handle error
}
})
That makes sure that both requests are sent to the server at the same time in a single request and only one response.
Each "query" from the .find() matches an element in the array and returns it's "index" value via the positional $ operator, which is used in the "update" portion of the method to $set the value at the matched index position.

Related

Querying data in mongodb using where clause [duplicate]

I want to perform a query on this collection to determine which documents have any keys in things that match a certain value. Is this possible?
I have a collection of documents like:
{
"things": {
"thing1": "red",
"thing2": "blue",
"thing3": "green"
}
}
EDIT: for conciseness
If you don't know what the keys will be and you need it to be interactive, then you'll need to use the (notoriously performance challenged) $where operator like so (in the shell):
db.test.find({$where: function() {
for (var field in this.settings) {
if (this.settings[field] == "red") return true;
}
return false;
}})
If you have a large collection, this may be too slow for your purposes, but it's your only option if your set of keys is unknown.
MongoDB 3.6 Update
You can now do this without $where by using the $objectToArray aggregation operator:
db.test.aggregate([
// Project things as a key/value array, along with the original doc
{$project: {
array: {$objectToArray: '$things'},
doc: '$$ROOT'
}},
// Match the docs with a field value of 'red'
{$match: {'array.v': 'red'}},
// Re-project the original doc
{$replaceRoot: {newRoot: '$doc'}}
])
I'd suggest a schema change so that you can actually do reasonable queries in MongoDB.
From:
{
"userId": "12347",
"settings": {
"SettingA": "blue",
"SettingB": "blue",
"SettingC": "green"
}
}
to:
{
"userId": "12347",
"settings": [
{ name: "SettingA", value: "blue" },
{ name: "SettingB", value: "blue" },
{ name: "SettingC", value: "green" }
]
}
Then, you could index on "settings.value", and do a query like:
db.settings.ensureIndex({ "settings.value" : 1})
db.settings.find({ "settings.value" : "blue" })
The change really is simple ..., as it moves the setting name and setting value to fully indexable fields, and stores the list of settings as an array.
If you can't change the schema, you could try #JohnnyHK's solution, but be warned that it's basically worst case in terms of performance and it won't work effectively with indexes.
Sadly, none of the previous answers address the fact that mongo can contain nested values in arrays or nested objects.
THIS IS THE CORRECT QUERY:
{$where: function() {
var deepIterate = function (obj, value) {
for (var field in obj) {
if (obj[field] == value){
return true;
}
var found = false;
if ( typeof obj[field] === 'object') {
found = deepIterate(obj[field], value)
if (found) { return true; }
}
}
return false;
};
return deepIterate(this, "573c79aef4ef4b9a9523028f")
}}
Since calling typeof on array or nested object will return 'object' this means that the query will iterate on all nested elements and will iterate through all of them until the key with value will be found.
You can check previous answers with a nested value and the results will be far from desired.
Stringifying the whole object is a hit on performance since it has to iterate through all memory sectors one by one trying to match them. And creates a copy of the object as a string in ram memory (both inefficient since query uses more ram and slow since function context already has a loaded object).
The query itself can work with objectId, string, int and any basic javascript type you wish.

How to make mongo validate documents before insert or update in Vertx?

I'm newbie using Vertx, I'm building a basic Api Rest with Vertx + Mongo using Javascript.
I'm looking for some way to automaticly validate the incoming documents before insert or update (for example, something like schemas in Mongoose).
I got the following:
POST entry point Inserting a new cat
var BodyHandler = require("vertx-web-js/body_handler");
var Router = require("vertx-web-js/router");
var router = Router.router(vertx);
router.post("/cat")
.produces("application/json")
.handler(BodyHandler.create().handle)
.handler(controller.createCat);
Controller's function Inserting a new cat
createCat: function (ctx) {
var response = ctx.response();
var body = ctx.getBodyAsJson() || {};
console.log('inserting cat')
connection.mongoClient.insert("cats", { name: body.name }, function (res, res_err) {
if (res_err == null) {
var id = res;
console.log("Inserted cat with id " + id);
response.putHeader("content-type", "application/json");
response.end(JSON.stringify(id));
} else {
console.log('err')
res_err.printStackTrace();
}
});
}
The problem is that I can insert empty documents resulting documents with only one field: the mongo ID.
TL;DR - I want to know if there is some way to tell Mongo that name field is ALWAYS required.
Solution I used:
mongoClient.runCommand("collMod", { collMod: "cats", validator: { $and: [ {"name": {$type: "string", $exists: true}} ] }, validationLevel: "strict", validationAction: "error" }, function(res, res_err) {
if (res_err) res_err.printStackTrace()
else console.log('The schema of cats collection has been updated')
});
In mongodb there is a concept of validation with query filters for collection: https://docs.mongodb.com/manual/core/schema-validation/#query-expressions
If the "name" field is always required, you can create your collection like this:
db.createCollection("cats", {
validator: {
$and: [ {"name": {$type: "string", $exists: true}} ]
})
you can create your own schema in a separate json file, then create your own validation method when the context is provided by ur router.
I assume different endpoint has different validation.
you can also create a middleware using gateleen
you have plenty of options, mongoose its benefits but the whole point of using mongodb is not creating a schema. Therefore, u can assume that the request should have specific mandatory fields to validate.

Aggregating an object in AngularJS

So, I'm experimenting with AngularJS, and, as an exercise, figured I would make a simple application using the Steam API. I have made a simple Spring Boot Rest service, which provides a reverse proxy service for the Steam API, in such a way that certain calls can be forwarded. At this time there are two actions:
/user/ provides a list of steam id's.
/user/:id/games provides the output of the following api:
http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=MY_STEAM_KEY&steamid=STEAM_ID&format=json
which returns an answer in the following format:
{
"response":{
"game_count":3,
"games":[
{
"appid":70000,
"playtime_forever":0
},
{
"appid":550,
"playtime_forever":0
},
{
"appid":274900,
"playtime_forever":0
}
]
}
}
What I want to achieve is to extract the games array from this json object, and append it to the correct user. And I want to do this for all users. I have achieved something close to what I want using the $resource object, by defining the following factories:
angular.module("SAM.Resources", [])
.factory("UserList", ["$resource",
function ($resource) {
return $resource('/user');
}])
.factory("GamesList", ["$resource",
function ($resource) {
return $resource('/user/:id/games', {
id: '#id'
});
}
]);
And then in my controller use the following:
UserList.query(function(response){
$scope.users = response ? response : [];
for(index=0; index < $scope.users.length; ++index){
user = $scope.users[index];
$scope.users[index].games = GamesList.get({id:user.id});
}
});
This is close to what I want, however, it returns something of the format:
{
"id": "76561198119953061",
"name": "Yuri",
"games": {
"response": {
"game_count": 3,
"games": [
{
"appid": 70000,
"playtime_forever": 0
},
{
"appid": 550,
"playtime_forever": 0
},
{
"appid": 274900,
"playtime_forever": 0
}
]
}
}
}
And I don't want the games.response.games construction. I have tried to change it to:
$scope.users[index].games = GamesList.get({id:user.id}).response.games;
which fails, seems logical, as it is a promise, and doesn't immediately contain the response object.
I've also tried to use something like
GamesList.get({id:user.id}), function(response){
angular.extend(user, response);
});
Which does indeed append the response to the user object, only the user object is always the last value in the array by the time the promise resolves.
So basically my question comes down to: How can I extend my User object with the Games list?
You need to change your code around a bit:
UserList.query(function(response){
$scope.users = response ? response : [];
for(index=0; index < $scope.users.length; ++index){
user = $scope.users[index];
(function(index, id){
GamesList.get({id: id}, function(response){ // Get the games from the response
$scope.users[index].games = response.response.games;
}));
})(index, user.id)
}
});
In the for loop, user keeps changing value. By the time the first GameList.get has a value returned, your loop will be at the last user already.
Wrapping that in an IIFE separates those variables in a separate scope.
for(index=0; index < $scope.users.length; ++index){
user = $scope.users[index];
$scope.users[index].games = GamesList.get({id:user.id}, function(response){
angular.extend(user, response);
}));
}
When you do that, the user variable will change at every step. But the anonymous callback will be executed later. So only the last user is used.
You can fix that by using an anonymous function as a scope with forEach :
$scope.users.forEach(function(user) {
$scope.users[index].games = GamesList.get({id:user.id}, function(response){
angular.extend(user, response);
}));
});
If you want to avoid the user.games.response.games, you need to merge the objects in a different way.
$scope.users.forEach(function(user) {
$scope.users[index].games = GamesList.get({id:user.id}, function(response){
user.games = response.games;
user.games_count = response.games_count;
}));
});

MongoDB updated object with item remove not saving

I'm using Angular Fullstack for an web app.
I'm posting my data by $http.post() my object:
{ title: "Some title", tags: ["tag1", "tag2", "tag3"] }
When I edit my object and try to $http.put() for example:
{ title: "Some title", tags: ["tag1"] }
In console I get HTTP PUT 200 but when I refresh the page I still recive the object with all 3 tags.
This is how I save in the MongoDB:
exports.update = function(req, res) {
if (req.body._id) {
delete req.body._id;
}
Question.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(saveUpdates(req.body))
.then(responseWithResult(res))
.catch(handleError(res));
};
function saveUpdates(updates) {
return function(entity) {
var data = _.merge(entity.toJSON(), updates);
var updated = _.extend(entity, data);
return updated.saveAsync()
.spread(function(updated) {
return updated;
});
};
}
Can someone explain how to save the object with removed items?
What I'm doing wrong?
This is pretty bad practice to use things like _.merge or _.extend in client ( meaning your nodejs client to database and not browser ) code after retrieving from the database. Also notably _.merge is the problem here as it is not going to "take away" things, but rather "augment" what is already there with the information you have provided. Not what you want here, but there is also a better way.
You should simply using "atomic operators" like $set to do this instead:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$set": { "tags": req.body.tags } },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
You also really should be targeting your endpoints and not having a "generic" object write. So the obove would be specically targeted at "PUT" for related "tags" only and not touch other fields in the object.
If you really must throw a whole object at it and expect an update from all the content, then use a helper to fix the update statement correctly:
function dotNotate(obj,target,prefix) {
target = target || {},
prefix = prefix || "";
Object.keys(obj).forEach(function(key) {
if ( typeof(obj[key]) === "object" ) {
dotNotate(obj[key],target,prefix + key + ".");
} else {
return target[prefix + key] = obj[key];
}
});
return target;
}
var update = { "$set": dotNotate(req.body) };
Question.findByIdAndUpdateAsync(
req.params.id,
update,
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
Which will correctly structure not matter what the object you throw at it.
Though in this case then probably just directly is good enough:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$set": req.body },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
There are other approaches with atomic operators that you could also fit into your logic for handling. But it is best considered that you do these per element, being at least root document properties and things like arrays treated separately as a child.
All the atomic operations interact with the document "in the database" and "as is at modification". Pulling data from the database, modifiying it, then saving back offers no such guarnatees that the data has not already been changed and that you just may be overwriting other changes already comitted.
I truth your "browser client" should have been aware that the "tags" array had the other two entries and then your "modify request" should simply be to $pull the entries to be removed from the array, like so:
Question.findByIdAndUpdateAsync(
req.params.id,
{ "$pull": { "tags": { "$in": ["tag2", "tag3"] } } },
{ "new": true }
)
.then(function(result) {
// deal with returned result
});
And then, "regardless" of the current state of the document on the server when modified, those changes would be the only ones made. So if something else modified at added "tag4", and the client had yet to get the noficiation of such a change before the modification was sent, then the return response would include that as well and everything would be in sync.
Learn the update modifiers of MongoDB, as they will serve you well.

Mongo query doesn't include my filter clause

I am building a mongo query in node as such:
query = {"status":{$ne:"Complete"}}, {"processId":1, "_id":0};
then I execute the query:
return this.engine.dbcollectionName.find(query).toArray(function(err, result){...
If i evaluate "conditions", the result is 'status=$ne:"Complete"'...however, my projection is missing.
Am I missing something about how to represent json in javascript variable? It is acting like I didn't include anything after the comma.
Another way to say it, in mongo CLI I want:
db.collection.find( {"status":{$ne:"Complete"}}, {"processId":1, "_id":0})
whereas within node, i'm getting equivalent of (notice no projection):
db.collection.find( {"status":{$ne:"Complete"}})
Any help appreciated! Thanks
Your query and projection must be separate object parameters to find:
var query = {"status":{$ne:"Complete"}};
var projection = {"processId":1, "_id":0};
this.engine.dbcollectionName.find(query, projection).toArray(function(err, result){...
New to JavaScript I see. If you want to construct an array of arguments then you need an actual array, and with the way method signatures work here you will also need the JavaScript .apply() method:
var async = require('async'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient;
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var query = [{ "status": { "$ne": "Complete" } }, { "processId": 1, "_id": 0 } ];
db.collection('collectionname',function(err,coll) {
coll.find.apply(coll,query).toArray(function(err,docs) {
if (err) throw err;
console.log( JSON.stringify( docs, undefined, 2 ) );
});
});
});
Otherwise you are separating arguments like
coll.find.(
{ "status": { "$ne": "Complete" } },
{ "processId": 1, "_id": 0 }
).toArray(function(err,docs) {
So that each item is passed to the function individually.
.apply() lets you pass in list variables or arrays of arguments to your function. It often requires a context as the first argument, which in this case is the "collection" object that the .find() method belongs to.

Categories

Resources