updating collection with embedded objects in meteor - mongodb

I tried to add data from a JSON response in my collection in meteor. Unfortunately all the things I found on Stack overflow didn't work. In some cases I got an error back like:
Exception in callback of async function: MongoError: Cannot update 'plugins' and 'plugins' at the same time
and if it didn't return an error my object in my collection stayed empty like:
"plugins": {}
The code below is what resulted in an empty object, I hope someone here can help my out.
My collection consist of a field which has an object with multiple objects in it. I want them to be like this
{
"_id" : "id",
"name" : "DemoSite",
"url" : "http://someurl.com",
"createdAt" : ISODate("2016-02-10T17:22:15.011+0000"),
"plugins" :
"Akismet": {
"pluginName" : "Akismet",
"currentPluginVersion" : "3.1.7",
"newPluginVersion" : null,
"lastChecked" : "Thu Feb 18 2016 15:54:02 GMT+0100 (CET)"
},
"Random Plugin": {
"pluginName" : "Random Plugin",
"currentPluginVersion" : "0.1.0",
"newPluginVersion" : null,
"lastChecked" : "Thu Feb 18 2016 15:54:02 GMT+0100 (CET)"
}
}
If my HTTP.call has no error it will do this:
var pluginData = result.data.plugins;
var dbPush = {};
if(pluginData != []){
for (var key in pluginData){
var obj = pluginData[key];
var objKey = obj.Name;
dbPush[objKey] = {
'pluginName': objKey,
'currentPluginVersion': obj.Version,
'newPluginVersion': null,
'lastChecked': new Date()
};
Items.update({_id: item._id}, {$set: {plugins: dbPush}});
}
}
Also my Simple schema has this in it:
Items.attachSchema(new SimpleSchema({
name: {
type: String,
label: "Item name",
min: 4,
max: 100
},
url: {
type: String,
label: "Item url",
autoform: {
afFieldInput: {
type: "url"
}
}
},
createdAt: {
type: Date,
optional: true,
autoValue: function() {
if (this.isInsert) {
return new Date();
} else {
this.unset(); // Prevent user from supplying their own value
}
},
autoform: {
afFieldInput: {
type: "hidden"
}
}
},
plugins: {
type: Object,
optional: true
}
}));
edit
I tried to predefine all the object and then update the collection
dbPush["test"] = {
'pluginName': "test",
'currentPluginVersion': "1.0.0",
'newPluginVersion': null,
'lastChecked': new Date()
};
Items.update({_id: item._id}, {$set: {plugins: dbPush}});

I'm pretty sure this line of code:
Items.update({_id: item._id}, {$set: {plugins: dbPush}});
Is your issue. You're looping through each plugin and updating the exact same MongoDB plugin property on the exact same item for each plugin. I think you want to build the object up beforehand and only insert once:
var pluginData = result.data.plugins;
var dbPush = {};
if(pluginData != []){
for (var key in pluginData){
var obj = pluginData[key];
var objKey = obj.Name;
dbPush[objKey] = {
'pluginName': objKey,
'currentPluginVersion': obj.Version,
'newPluginVersion': null,
'lastChecked': new Date()
};
}
Items.update({_id: item._id}, {$set: {plugins: dbPush}});
}
Right now, you're looping through some amount of times and slowly building that dbPush object.

I found a fix.
It's not a great solution but Simple Schema doesn't allows me to have custom objects in my collection. All that's needed to get this to work with my collection is the blackbox option found in the documentation here.
plugins: {
type: Object,
optional: true,
blackbox: true
}
This will skip validation on the plugins object so it will store the data in the collection.
Thanks for all the help!

Related

How to update with mongoose

I have this record
{
"_id" : ObjectId("5dfdff479ad032cbbc673507"),
"selection" : [
{
"highlights" : "test",
"comment" : "CHANGE THIS",
"el" : "body:nth-child(2)>div:nth-child(2)#root>div.App>p:nth-child(1)"
},
{
"highlights" : "Barrett’s lyrical prose opens with a clever and tender solution",
"comment" : "",
"el" : "body:nth-child(2)>div:nth-child(2)#root>div.App>p:nth-child(2)"
}
],
"category" : [],
"status" : "",
"url" : "http://localhost:3000/theone",
"title" : "React App test",
"__v" : 4
}
And I want to update the comment. I have tried to use update and findOneAndUpdate and nothing is working. Here is my attempt
WebHighlight.findOneAndUpdate(
{
_id: req.params.highlight,
"selection.highlights": "test"
},
{ "selection.$.comment": "yourValue" }
);
That req.params.highlight is the id (I even hardcoded it)
I also tried this
WebHighlight.findById(req.params.highlight, (err, book) => {
var test = [...book.selection];
test[0].comment = "somethibf"
book.save();
res.json(book);
});
And nothing is working.
This is the model
const webhighlightsModel = new Schema({
selection: { type: Array, default: "" },
category: { type: Array, default: [] },
title: { type: String },
url: { type: String },
status: { type: String, default: "" }
});
Actually your code seems to work, but findOneAndUpdate returns the old document if you don't give {new: true} option.
I think for this reason, you think the update wasn't successfull, but if you check your collection, you will see the update.
WebHighlight.findOneAndUpdate(
{
_id: req.params.highlight,
"selection.highlights": "test"
},
{ "selection.$.comment": "yourValue" },
{ new: true }
)
.then(doc => res.send(doc))
.catch(err => res.status(500).send(err));
Also I think it would be better if selection had a sub schema like this:
const mongoose = require("mongoose");
const schema = new mongoose.Schema({
selection: [
new mongoose.Schema({
highlights: String,
comment: String,
el: String
})
],
category: { type: Array, default: [] },
title: { type: String },
url: { type: String },
status: { type: String, default: "" }
});
module.exports = mongoose.model("WebHighlight", schema);
So with this every selection would an _id field, and it would be better to update with this _id.
You should use the $set operator to update existing values:
WebHighlight.findOneAndUpdate(
{
_id: req.params.highlight,
"selection.highlights": "test"
},
{ '$set': { "selection.$.comment": "yourValue" } }
);

Mongoose query not showing subdocument

I can't get mongoose to show subdocument when running find() while it displays perfectly well in mongodb shell.
Subdocument should be embedded based on my schema, not objectId referenced, so I shouldn't be running any black magic voodoo to get my data to show up.
const UserSchema = new mongoose.Schema({
username: String;
xp: Number;
//etc.
});
const RoomSchema = new mongoose.Schema({
timestamp: { type: Date, default: Date.now },
status: { type: String, enum: ["pending", "ongoing", "completed"]},
players: {
type: [{
points: { type: Number, default: 0 },
position: String,
user: UserSchema
}],
maxlength:2
}
});
After adding a new room with:
let room = new Room(coreObj);
room.players.push({
points: 0,
position: 'blue',
user: userObj //where userObj is a result of running findById on User model
});
It displays nicely in mongo shell, when running db.rooms.find({}).pretty() I can see that full document has been added. However, when running on mongoose model:
Room.find({}).exec((err,rooms)=>{
console.log(rooms[0].toJSON());
});
I don't see user subdocument, moreover I cannot see user field entirely! What seems to be the problem?
logged json from mongoose model:
{
"status": "pending",
"_id": "5cf5a25c050db208641a2076",
"timestamp": "2019-06-03T22:42:36.946Z",
"players": [
{
"points": 0,
"_id": "5cf5a25c050db208641a2077",
"position": "blue"
}
],
"__v": 0
}
json from mongo shell:
{
"_id" : ObjectId("5cf5a25c050db208641a2076"),
"status" : "pending",
"timestamp" : ISODate("2019-06-03T22:42:36.946Z"),
"players" : [
{
"points" : 0,
"_id" : ObjectId("5cf5a25c050db208641a2077"),
"position" : "blue",
"user" : {
"xp" : 0,
"_id" : ObjectId("5cf2da91a45db837b8061270"),
"username" : "bogdan_zvonko",
"__v" : 0
}
}
],
"__v" : 0
}
Keeping best practice in mind, I think it would be more appropriate to reference the UserSchema in the RoomSchema. Something like:
...
user: {
type: Schema.Types.ObjectId,
ref: 'UserSchema'
}
Then you would store the user._id in that field.
This way, if the user is modified, your RoomSchema is always referencing the correct information. You could then get the user using Mongoose's populate
I'm not entirely sure why you can't see the sub-sub-document, but this code example printed it correctly for me. Example was originally posted in https://mongoosejs.com/docs/subdocs.html but modified slightly to contain sub-sub-document so it looks similar to your code:
var grandChildSchema = new mongoose.Schema({ name: 'string' });
var childSchema = new mongoose.Schema({ name: 'string', grandChild: grandChildSchema });
var parentSchema = new mongoose.Schema({ children: [childSchema] });
var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({
children: [
{ name: 'Matt', grandChild: {name: 'Matt Jr'} },
{ name: 'Sarah', grandChild: {name: 'Sarah Jr'} }
]
})
parent.save(function() {
Parent.find().exec(function(err, res) {
console.log(JSON.stringify(res[0]))
mongoose.connection.close()
})
});
Executing this code resulted in:
{
"_id": "5cf7096408b1f54151ef907c",
"children": [
{
"_id": "5cf7096408b1f54151ef907f",
"name": "Matt",
"grandChild": {
"_id": "5cf7096408b1f54151ef9080",
"name": "Matt Jr"
}
},
{
"_id": "5cf7096408b1f54151ef907d",
"name": "Sarah",
"grandChild": {
"_id": "5cf7096408b1f54151ef907e",
"name": "Sarah Jr"
}
}
],
"__v": 0
}
This was tested using Mongoose 5.5.12.
Note that I was using JSON.stringify() to print the document instead of using Mongoose's toJSON().
I just met a very similar problem, i think i got it.
the whole point is in model which you use:
const RoomSchema = new mongoose.Schema({
...
players: {
type: [{
...
user: UserSchema
...
but then you make
room.players.push({
points: 0,
position: 'blue',
user: userObj //where userObj is a result of running findById on User model
});
so you are missing the "type" subfield, so your doc is not compliant with your RoomSchema and mongoose do not show the parts which does not fit schema.

[mongodb]How to save a document and automatically increase a field of it?

I need to save or insert a new record into my mongo database. I hope the field "userid" of it can automatically increase by 1. Is it possible to do it in mongodb?
Schema
generalUserApplication: {
userid: Number, // 1
lora: [
{},
{}
]
}
Here's the way from the mongo tutorial.
You need to create new collection counters in your db.
function getNextSequence(db, name, callback) {
db.collection("counters").findAndModify( { _id: name }, null, { $inc: { seq: 1 } }, function(err, result){
if(err) callback(err, result);
callback(err, result.value.seq);
} );
}
Then, you can use getNextSequence() as following, when you insert a new row.
getNextSequence(db, "user_id", function(err, result){
if(!err){
db.collection('users').insert({
"_id": result,
// ...
});
}
});
I have use another package named 'mongoose-sequence-plugin' to generate sequence which is auto-incremented by 1.
Here I am posting code that I have tried for same problem. Hope it will help.
Schema :
var mongoose = require('mongoose');
var sequenceGenerator = require('mongoose-sequence-plugin');
var Schema = mongoose.Schema;
var ProjectSchema = new Schema({
Name : { type : String, required: true },
Description : { type : String, required: true },
DetailAddress : { type : String, required: true },
ShortAddress : { type : String, required: true },
Latitude : { type : Number, required: true },
Longitude : { type : Number, required: true },
PriceMin : { type : Number, required: true, index : true },
PriceMax : { type : Number, required: true, index: true },
Area : { type : Number, required: true },
City : { type : String, required: true }
});
ProjectSchema.plugin(sequenceGenerator, {
field: 'project_id',
startAt: '10000001',
prefix: 'PROJ',
maxSaveRetries: 2
});
module.exports = mongoose.model('Project', ProjectSchema);
This will create projects collection with parameter project_id starting from PROJ10000001 and on wards. If you delete last inserted record then this package reads the current last entry of field project_id and next id is assigned by incrementing current id.

Mongodb pull from array attached to simple schema

I have the following collection attached to aldeed:simple schema
Posts = new Mongo.Collection("posts");
Posts.attachSchema(new SimpleSchema({
samplePost:{
type:String,
max:500
},
createdAt:{
type: Date,
autoValue: function(){
return new Date()
}
},
"comments.$.reply":{
type:String
},
"comments.$.commentId":{
type: String,
autoValue: function(){
var tempCommentId = new Meteor.Colletion.ObjectID();
return tempCommentId.str;
}
},
"comments.$.commentCreatedAt": {
type: Date,
optional: true,
autoValue: function(){
return new Date()
}
},
});
The actual document looks like the following:
{
"_id": "aaa",
"samplePost": "Hello world!",
"comments": [
{
"reply": "Goodbye",
"commentId": "bbb",
"createdAt": "2016-06-19T19:06:17.931Z"
},
{
"reply": "Good morning",
"commentId": "ccc"
"createdAt": "2016-06-19T19:05:17.931Z"
},
]
}
Now im trying to remove only the 2nd comment with commentId:"ccc" from the document with $pull
"click #delete-comment": function(event, template){
var tempCommentId = $(event.target).parent().find('#commentIdPass').text(); //commentId is collected from HTML view
Posts.update(
{_id: template.data._id}, //_id is collected from the url param
{$pull:{
comments: {
commentId: tempCommentId
}}
});
},
and this is not working. I have narrowed down the problem to
"comments.$.commentCreatedAt": {
type: Date,
optional: true,
autoValue: function(){
return new Date()
}
},
schema. If i remove this schema, i can delete the comment.
So, why is this causing a problem of pulling the whole comment item from the array. Any ideas? Any workarounds?
Try This Query:
db.getCollection('Mytest').update({"_id":"aaa"},{"$pull":{"comments":{"commentId":"ccc"}}});
Found the problem. As I have suspected attached schema was the culprit. I needed to set a conditional for inserting and updating a schema with autovalue method. The schema should look as following:
"comments.$.commentCreatedAt": {
type: Date,
autoValue: function () {
if (this.isInsert) {
return new Date;
}
}
},

Correct way to return from mongo to datatable

I'm using mongoose and returning documents from a collection to be displayed using datatables. I'm having some issues though. The client-side code is
var table = $('#dataTables-example').DataTable( {
"bProcessing" : true,
"bServerSide" : true,
"ajax" : {
"url" : "/mongo/get/datatable",
"dataSrc": ""
},
"columnDefs": [
{
"data": null,
"defaultContent": "<button id='removeProduct'>Remove</button>",
"targets": -1
}
],
"aoColumns" : [
{ "mData" : "name" },
{ "mData" : "price" },
{ "mData" : "category" },
{ "mData" : "description" },
{ "mData" : "image" },
{ "mData" : "promoted" },
{ "mData" : null}
]
});
Then this handled on the server-side using the following
db.once('open', function callback ()
{
debug('Connection has successfully opened');
productSchema = mongoose.Schema({
name: String,
price: String,
category: String,
description: String,
image: String,
promoted: Boolean
});
Product = mongoose.model('Product', productSchema, 'products');
});
exports.getDataForDataTable = function (request, response) {
Product.dataTable(request.query, function (err, data) {
debug(data);
response.send(data);
});
};
If I use the above code the datatable fails to display the documents, claiming no matching records found BUT it does correctly display the number of docs Showing 1 to 2 of 2 entries. If I change the server side code to response with data.data instead of data, the documents are correctly populated in the table BUT the number of records is no longer found, instead saying Showing 0 to 0 of 0 entries (filtered from NaN total entries)
exports.getDataForDataTable = function (request, response) {
Product.dataTable(request.query, function (err, data) {
debug(data);
response.send(data.data);
});
The actual data being returned when querying mongo is
{ draw: '1', recordsTotal: 2, recordsFiltered: 2, data: [ { _id: 5515274643e0bf403be58fd1, name: 'camera', price: '2500', category: 'electronics', description: 'lovely', image: 'some image', promoted: true }, { _id: 551541c2e710d65547c6db15, name: 'computer', price: '10000', category: 'electronics', description: 'nice', image: 'iamge', promoted: true } ] }
The third parameter in mongoose.model sets the collection name which is pluralized and lowercased automatically so it has no effect in this case.
Assuming your Product variable has been declared early on and global, try this:
products = mongoose.model('products', productSchema);
Product = require('mongoose').model('products');
Did you try to remove the dataSrc field in the DataTable configuration:
"ajax" : {
"url" : "/mongo/get/datatable",
},