Limit Mongodb field conditionally in Meteor publication - mongodb

I have this Meteor publication of all the posts in the database.
I would like to return the owner field of the post only if the isAnonymous field of the post is true (i.e. if the post is anonymous, don't publish the owner?
How do I make this condition in Meteor/Mongodb? The script that I've got here never returns the owner.
Meteor.publish('posts', function(id) {
var posts = Posts.find({},{fields: {owner: 0}},{sort: {created_at: -1}});
return posts;
});
I tried something like this, but it doesn't work
Meteor.publish('posts', function(id) {
var posts = Posts.find({},{fields: {owner: { $cond: [ { $eq: [ "isAnonymous", 1 ] }, 0, 1 ] }}},{sort: {created_at: -1}});
return posts;
});

What you seem to want here is something like this:
Meteor.publish('posts',function(args) {
var self = this;
Posts.find().fetch().forEach(function(post) {
if ( post.hasOwnProperty("isAnonymous") ) {
if ( post.isAnonymous ) {
delete post.owner;
delete post.isAnonymous;
}
}
self.added("postsFiltered",post._id,post);
});
self.ready();
});
Then basically define your collection within the client at least as:
Posts = new Meteor.Collection("postsFiltered");
And then after you subscribe, the client will only see the data without the "privacy" information as it is always published that way.

If you don't want to return a field in some cases only, you can define a transform function for your collection. Something like this:
Posts = new Mongo.Collection('posts', {
transform: function(doc) {
if (-- condition for not returning owner is true --) {
delete doc.owner;
}
}
});
If you are ok with returning it, but don't want to display it in some cases, you can do this in the template like so:
{{#each posts }}
...
{{#if isOwnerKnown owner }}
{{ owner }}
{{/if }}
...
{{/foreach}}
Template.people.helpers({
isOwnerKnown: function(owner){
return owner != 'Anonymous';
}
...
});

Related

How can I return the element I'm looking for inside a nested array?

I have a database like this:
[
{
"universe":"comics",
"saga":[
{
"name":"x-men",
"characters":[
{
"character":"wolverine",
"picture":"618035022351.png"
},
{
"character":"cyclops",
"picture":"618035022352.png"
}
]
}
]
},
{
"universe":"dc",
"saga":[
{
"name":"spiderman",
"characters":[
{
"character":"venom",
"picture":"618035022353.png"
}
]
}
]
}
]
and with this code I manage to update one of the objects in my array. specifically the object where character: wolverine
db.mydb.findOneAndUpdate({
"universe": "comics",
"saga.name": "x-men",
"saga.characters.character": "wolverine"
}, {
$set: {
"saga.$[].characters.$[].character": "lobezno",
"saga.$[].characters.$[].picture": "618035022354.png",
}
}, {
new: false
}
)
it returns all my document, I need ONLY the document matched
I would like to return the object that I have updated without having to make more queries to the database.
Note
I have been told that my code does not work well as it should, apparently my query to update this bad, I would like to know how to fix it and get the object that matches these search criteria.
In other words how can I get this output:
{
"character":"wolverine",
"picture":"618035022351.png"
}
in a single query using filters
{
"universe": "comics",
"saga.name": "x-men",
"saga.characters.character": "wolverine"
}
My MongoDB knowledge prevents me from correcting this.
Use the shell method findAndModify to suit your needs.
But you cannot use the positional character $ more than once while projecting in MongoDb, so you may have to keep track of it yourself at client-side.
Use arrayFilters to update deeply nested sub-document, instead of positional all operator $[].
Below is a working query -
var query = {
universe: 'comics'
};
var update = {
$set: {
'saga.$[outer].characters.$[inner].character': 'lobezno',
'saga.$[outer].characters.$[inner].picture': '618035022354.png',
}
};
var fields = {
'saga.characters': 1
};
var updateFilter = {
arrayFilters: [
{
'outer.name': 'x-men'
},
{
'inner.character': 'wolverine'
}
]
};
db.collection.findAndModify({
query,
update,
fields,
arrayFilters: updateFilter.arrayFilters
new: true
});
If I understand your question correctly, your updating is working as expected and your issue is that it returns the whole document and you don't want to query the database to just to return these two fields.
Why don't you just extract the fields from the document returned from your update? You are not going to the database when doing that.
var extractElementFromResult = null;
if(result != null) {
extractElementFromResult = result.saga
.filter(item => item.name == "x-men")[0]
.characters
.filter(item => item.character == "wolverine")[0];
}

Mongoose remove one object from array of array

I have Mongoose schema like this:
{
......
project: [
{
Name: String,
Criteria:[
{
criteriaName:String,
}
]
}
]
......
}
And I want to remove one of the objects of criteria array which is in the array of project based on the object id
I tried the code following
criteria.findOneAndUpdate({
"_id": uid,
},{ $pull: { "project.Criteria": { _id: cid } } }, (err) => {
......
}
However this cannot work, it said "Cannot use the part (Criteria) of (project.Criteria) to traverse the element"
Do you need to do it in one query to the database? If not, the following solution may work for you:
criteria.findOne({ _id: uid })
.then((obj) => {
// Filter out the criteria you wanted to remove
obj.project.Criteria = obj.project.Criteria.filter(c => c._id !== cid);
// Save the updated object to the database
return obj.save();
})
.then((updatedObj) => {
// This is the updated object
})
.catch((err) => {
// Handle error
});
Sorry if the .then/.catch is confusing. I can rewrite with callbacks if necessary, but I think this looks a lot cleaner. Hope this helps!

Meteor/Mongo - add/update element in sub array dynamically

So I have found quite few related posts on SO on how to update a field in a sub array, such as this one here
What I want to achieve is basically the same thing, but updating a field in a subarray dynamically, instead of just calling the field name in the query.
Now I also found how to do that straight in the main object, but cant seem to do it in the sub array.
Code to insert dynamically in sub-object:
_.each(data.data, function(val, key) {
var obj = {};
obj['general.'+key] = val;
insert = 0 || (Documents.update(
{ _id: data._id },
{ $set: obj}
));
});
Here is the tree of what I am trying to do:
Documents: {
_id: '123123'
...
smallRoom:
[
_id: '456456'
name: 'name1'
description: 'description1'
],
[
...
]
}
Here is my code:
// insert a new object in smallRoom, with only the _id so far
var newID = new Mongo.ObjectID;
var createId = {_id: newID._str};
Documents.update({_id: data._id},{$push:{smallRooms: createId}})
And the part to insert the other fields:
_.each(data.data, function(val, key) {
var obj = {};
obj['simpleRoom.$'+key] = val;
console.log(Documents.update(
{
_id: data._id, <<== the document id that I want to update
smallRoom: {
$elemMatch:{
_id : newID._str, <<== the smallRoom id that I want to update
}
}
},
{
$set: obj
}
));
});
Ok, having said that, I understand I can insert the whole object straight away, not having to push every single field.
But I guess this question is more like, how does it work if smallRoom had 50 fields, and I want to update 3 random fields ? (So I would NEED to use the _each loop as I wouldnt know in advance which field to update, and would not want to replace the whole object)
I'm not sure I 100% understand your question, but I think the answer to what you are asking is to use the $ symbol.
Example:
Documents.update(
{
_id: data._id, smallRoom._id: newID._str
},
{
$set: { smallroom.$.name: 'new name' }
}
);
You are finding the document that matches the _id: data._id, then finding the object in the array smallRoom that has an _id equal to newId._str. Then you are using the $ sign to tell Mongo to update that object's name key.
Hope that helps

Sorting users using date from separate collection

I'm trying to get return a list of users based on their accountActiveDate which is stored in a separate collection. If I console.log candidateUserIDByDate it returns the users in the correct order however when I return Meteor.users.find using the var candidateUserIDByDate its not sorted.
Path: sort.js
let sortCandidatesByDate = CandidateAccountStatus.find({}, {sort: {accountActiveDate: 1}}).fetch();
let candidateUserIDByDate = _.map(sortCandidatesByDate, function(obj) {
return obj.candidateUserId;
});
return Meteor.users.find({$and: [{_id: { $ne: Meteor.userId() }}, {_id: { $in: candidateUserIDByDate }}]});
I think a (probably hackish) solution would be, returning the CandidateAccountStatus and inside the loop, using another helper to return the correct user like this:
Template helpers:
status: function(){
//you might want to do {$ne: {Meteor.userId()}} for the correct field
//in your selector if you don't want currentUser status
return CandidateAccountStatus.find({}, {sort: {accountActiveDate: 1}})
},
statusUser: function(){
//change correctField to how you save the userId to
//your CandidateAccountStatus schema
return Meteor.users.findOne({_id: this.correctField})
}
HTML
{{#each status}}
{{#with statusUser}}
<!-- You have the user object here -->
{{_id}} <!-- gives the userId -->
{{/with}}
{{/each}}

Publish cursor with simplified array data

I need to publish a simplified version of posts to users. Each post includes a 'likes' array which includes all the users who liked/disliked that post, e.g:
[
{
_id: user_who_liked,
liked: 1 // or -1 for disliked
},
..
]
I'm trying to send a simplified version to the user who subscribes an array which just includes his/her like(s):
Meteor.publish('posts', function (cat) {
var _self = this;
return Songs.find({ category: cat, postedAt: { $gte: Date.now() - 3600000 } }).forEach(function (post, index) {
if (_self.userId === post.likes[index]._id) {
// INCLUDE
} else
// REMOVE
return post;
})
});
I know I could change the structure, including the 'likes' data within each user, but the posts are usually designed to be short-lived, to it's better to keep that data within each post.
You need to use this particular syntax to find posts having a likes field containing an array that contains at least one embedded document that contains the field by with the value this.userId.
Meteor.publish("posts", function (cat) {
return Songs.find({
category: cat,
postedAt: { $gte: Date.now() - 3600000 },
"likes._id":this.userId
},{
fields:{
likes:0
}
});
});
http://docs.mongodb.org/manual/tutorial/query-documents/#match-an-array-element
EDIT : answer was previously using $elemMatch which is unnecessary because we only need to filter on one field.