update sub-document in meteor mongo - mongodb

I am working on a query that need to update a subdocument in meteor mongo. My code looks like this:
Cases.update(
{"_id":doc._id,"notes._id": doc.noteid},
{$set:{
'notes.$': {
'note': doc.note,
'updatedBy': currentUser,
'updatedAt': date
}
}});
this does work, however it does remove other fields that are not in the update such as "createdAt" and "date". I went as far as doing this but i get the same result:
Cases.update(
{"_id":doc._id,"notes._id": doc.noteid},
{$set:{
'notes.$': {
'note': doc.note,
'updatedBy': currentUser,
'updatedAt': date,
'createdBy':doc.createdBy,
'date': doc.date,
'_id':doc.noteid
}
}});
the notes.$._id, date and createdBy fields are removed in the opertion. I am using aldeed simple schema and collection 2 as packages of the autoForm package. This update is however been done using a generic form with a Meteor.call. I am using meteor 1.2.1. I know that collection 2 has the removeEmptyString setting turned on by default that is why I tried doing it the second way but it still does not work. Can some one please help?

Your $set is saying, "Assign the notes.$ object to this object I'm giving you, and delete whatever is currently there."
You want to do something like:
Cases.update( {"_id":doc._id, "notes._id": doc.noteid},
{
$set:{
'notes.$.note': doc.note,
'notes.$.updatedBy': currentUser,
'notes.$.updatedAt': date
}
});
My $set is saying, "Assign these three particular fields of the notes.$ object to the values I'm giving you, and don't touch anything else."

Related

How to find and return a specific field from a Mongo collection?

Although I think it is a general question, I could not find a solution that matches my needs.
I have 2 Mongo collections. The 'users' collection and the second one 'dbInfos'.
Now, I have a template called 'Infos' and want the already existing fields in the Mongo collections to be presented to the user in input fields in case there is data in the collection. When no data is provided in the database yet, it should be empty.
So here is my code, which works fine until I want to capture the fields from the second collection.
Template.Infos.onRendered(function() {
$('#txtName').val(Meteor.user().profile.name);
$('#txtEmail').val(Meteor.user().emails[0].address);
});
These 2 work great.
But I don´t know how to query the infos from the collection 'dbInfos', which is not the 'users' collection. Obviously Meteor.user().country does not work, because it is not in the 'users' collection. Maybe a find({}) query? However, I don´t know how to write it.
$('#txtCountry').val( ***query function***);
Regarding the structure of 'dbInfos': Every object has an _id which is equal to the userId plus more fields like country, city etc...
{
"_id": "12345",
"country": "countryX",
"city": "cityY"
}
Additionally, how can I guarantee that nothing is presented, when the field in the collection is empty? Or is this automatic, because it will just return an empty field?
Edit
I now tried this:
dbInfos.find({},{'country': 1, '_id': 0})
I think this is the correct syntax to retrieve the country field and suppress the output of the _id field. But I only get [object Object] as a return.
you're missing the idea of a foreign key. each item in a collection needs a unique key, assigned by mongo (usually). so the key of your country info being the same as the userId is not correct, but you're close. instead, you can reference the userId like this:
{
"_id": "abc123",
"userId": "12345",
"country": "countryX",
"city": "cityY"
}
here, "abc123" is unique to that collection and assigned by mongo, and "12345" is the _id of some record in Meteor.users.
so you can find it like this (this would be on the client, and you would have already subscribed to DBInfos collection):
let userId = Meteor.userId();
let matchingInfos = DBInfos.find({userId: userId});
the first userId is the name of the field in the collection, the second is the local variable that came from the logged in user.
update:
ok, i think i see where you're getting tripped it. there's a difference between find() and findOne().
find() returns a cursor, and that might be where you're getting your [object object]. findOne() returns an actual object.
for both, the first argument is a filter, and the second argument is an options field. e.g.
let cursor = DBInfos.find({
userId: Meteor.userId()
},
{
fields: {
country: 1
}
});
this is going to:
find all records that belong to the logged in user
make only the country and _id fields available
make that data available in the form of a cursor
the cursor allows you to iterate over the results, but it is not a JSON object of your results. a cursor is handy if you want to use "{{#each}}" in the HTML, for example.
if you simply change the find() to a findOne():
let result = DBInfos.findOne({ /** and the rest **/
... now you actually have a JSON result object.
you can also do a combination of find/fetch, which works like a findOne():
let result = DBInfos.find({
userId: Meteor.userId()
},
{
fields: {
country: 1
}
}).fetch();
with that result, you can now get country:
let country = result.country;
btw, you don't need to use the options to get country. i've been assuming all this code is on the client (might be a bad assumption). so this will work to get the country as well:
let result = DBInfos.findOne({userId: Meteor.userId()});
let country = result.country;
what's going on here? it's just like above, but the result JSON might have more fields in it than just country and _id. (it depends on what was published).
i'll typically use the options field when doing a find() on the server, to limit what's being published to the client. on the client, if you just need to grab the country field, you don't really need to specify the options in that way.
in that options, you can also do things like sort the results. that can be handy on the client when you're going to iterate on a cursor and you want the results displayed in a certain order.
does all that make sense? is that what was tripping you up?

$ne and $nin not working on Meteor Backend

I've noticed that wherever I have a publication definition involving $ne or $nin, it has no effect. For example, I define a publication as
Meteor.publish('testing', function() {
return Courses.find({name: {$ne: 'Course A'}})
});
I have made sure that I have a course with name "Course A", however, when I subscribe like this on the client:
MeteorObservable.subscribe("testing").subscribe(() => {
console.log(Courses.find().fetch())
});
I get every single course in the database (including the one with name "Course A"). In case it's relevant, my definition for Courses is: export const Courses = new MongoObservable.Collection<Course>('courses');.
Is my selector wrong, or is it a bug with Meteor? When I run the same selector on the client side, everything works as expected.
Did u check if you disabled/remove autopublish package? You can check it inside .meteor/packages file. If yes, then remove it so meteor respect your publications.
Let me know if this solves your problem. Thanks!
Update 1:
Try this:
MeteorObservable.subscribe("testing").subscribe(() => {
console.log(Courses.find({name: {$ne: 'Course A'}}).fetch());
});

Cannot remove on mongodb using mongoose?

Hi im trying to simply remove a document from a collection using mongoose but for some strange reason I cannot get it to work.
Here is the code:
function deleteUserevent()
{console.log('in delete User Event');
models.Userevent.remove({ _id: "5214f4050acb53fe31000004"}, function(err) {
if (!err){
console.log('deleted user event!');
}
else {
console.log('error');
}
});
}
Can anyone help me out on my syntax? I know the _id is stored as new ObjectId("5214f4050acb53fe31000004") but I have tried this with no joy?
Thanks.
In MongoDB, the "_id" field of documents is of type ObjectId, as you mentioned. This is not equal to a String, so running the query
db.userevent.remove({ _id: "5214f4050acb53fe31000004"});
will not match anything, and will not remove anything. Instead, you must search for a document where the _id field is an ObjectId with that value:
db.userevents.remove({ _id: ObjectId("5214f4050acb53fe31000004")});
In mongoose, you can use the findByIdAndRemove command to remove a document with a specific _id. This command takes either an ObjectId or a String as an argument, so
query = Userevent.findByIdAndRemove("5214f4050acb53fe31000004");
should work just fine.
Just add exec() after query.
It should work like this:
await models.Userevent.findByIdAndDelete("5214f4050acb53fe31000004").exec()

Update meteor collection without removing or overriding existing fields

I don't know why but if i try to update an existing field using the $set method, any existing fields are replaced in the same context.
For example. Say i have an existing collection with the following fields.
Name of collection: Ticket
{profile: {name: "Test", placement: 1}, requestor: _id}
When i attempt to add/update fields to this collection like this:
var ticket = Meteor.tickets.findOne({_id: ticketID});
if(ticket){
Meteor.users.update(ticket, {
$set: profile: {name: "Test2", new_fields: "value"}
});
}
The collection gets updated and the name field changes but placement is removed and no longer there. This is also true if i remove the name field. How do we properly update a meteor collection without having to keep passing the same structure over and over?
Just do this:
$set: {"profile.name": "Test2", "profile.new_fields": "value"}
I.e. You were replacing the whole hash. Instead you can update the fields within the hash.
if the field you want to change have a unique index, you can modify that particular field to what you want without destroying the remaining information in the field.
db.artists.find()
{"_id":ObjectId("1"),"name":"A1","media_id":["m1","m2" ]}
{"_id":ObjectId("2"),"name":"A2","media_id":["m2","m3"]}
{"_id":ObjectId("3"),"name":"A3","media_id":["m3","m1","m2"]}
db.artists.ensureIndex({"name":1})
db.artists.update(
{name:"A1"},
{$set: { name:"A4"}},
{ upsert: true }
)
b.artists.find()
{"_id":ObjectId("1"),"name":"A4","media_id":["m1","m2" ]}
{"_id":ObjectId("2"),"name":"A2","media_id":["m2","m3"]}
{"_id":ObjectId("3"),"name":"A3","media_id":["m3","m1","m2"]}
I am myself quite new in MongoDB but this worked pretty well for me.

mongodb: upserting: only set value if document is being inserted

Considering a simple mongo document structure:
{ _id, firstTime, lastTime }
The client needs to insert a document with a known ID, or update an existing document. The 'lastTime' should always be set to some latest time. For the 'firstTime', if a document is being inserted, then the 'firstTime' should be set to current time. However, if the document is already created, then 'firstTime' remain unchanged. I would like to do it purely with upserts (to avoid look ups).
I've crawled the http://www.mongodb.org/display/DOCS/Updating, but I just don't see how that particular operation can be done.
I don't believe this is something unreasonable, there are $push and $addToSet operations that effectively do that on array fields, just nothing that would do the same on simple fields. It's like there should be something like $setIf operation.
I ran into the exact same problem and there was no simple solution for <2.4 however since 2.4 the $setOnInsert operator let's you do exactly that.
db.collection.update( <query>,
{ $setOnInsert: { "firstTime": <TIMESTAMP> } },
{ upsert: true }
)
See the 2.4 release notes of setOnInsert for more info.
I ran into a very similar problem when attempting to upsert documents based on existing content--maybe this solution will work for you also:
Try removing the _id attribute from your record and only use it in the query portion of your update (you'll have to translate from pymongo speak...)
myid = doc.get('_id')
del doc['_id']
mycollection.update({'_id':myid}, {'$set':doc}, upsert=True)
If you will trigger the following code 2 subsequent times, it will first set both firstVisit and lastVisit on document insert (and will return upsertedId in the response) and on the second it will only update lastVisit (and will return modifiedCount: 1).
Tested with Mongo 4.0.5 though I believe should be working with older versions.
db.collection.updateOne(
{_id: 1},
{
$set: {
lastVisit: Date.now()
},
$setOnInsert: {
firstVisit: Date.now()
}
},
{ upsert: true }
);
There's no way to do this with just one upsert. You'd have to do it as 2 operations - first try to insert the document, if it already exists the insert will fail due to duplicate key violation on the _id index. Then you do an update operation to set the lastTime to now.