meteor array $addToSet not adding any items - mongodb

I'm trying to add instruments to a Profiles collection in meteor using $addToSet. The code works in mongo, but will not work in the meteor methods call. I am able to update all other fields without any issues using $set, so I know that this is finding the correct user.
updateInstruments(instruments) {
if (!this.userId) {
throw new Meteor.Error('not-logged-in',
'Must be logged in to update last name.');
}
check(instruments, String);
if (instruments.length === 0) {
throw Meteor.Error('instruments-required', 'Must provide at least one instrument.');
}
let instrArray = instruments.split(',');
instrArray.forEach(function(instrument){
instrument = instrument.trim();
Profiles.update({ userId: this.userId }, { $addToSet: { instruments: instrument } });
});
},
I have even tried:
Profiles.update({ userId: this.userId }, { $addToSet: { instruments: {$each: [instrument] } }});
as well as:
Profiles.update({ userId: this.userId }, { $addToSet: { instruments: [instrument] }});
I have also tried $push and nothing happened there as well. Is there some sort of bug within meteor? Is there some other setting I need to configure to allow the updating of arrays?
UPDATE:
Per request, here's the client code:
updateInstruments() {
if (_.isEmpty(this.data.instruments)) return;
var self = this;
let instruments = this.data.instruments;
this.callMethod('updateInstruments', instruments, (err) => {
if (err) return this.handleError(err);
});
}
Thanks!

I figured out the issue. I forgot that the scope of 'this' changes in the inline 'instrArray.forEach' function, making this.userId 'undefined'. The Profiles collection was unable to find the record. I changed the following code:
let instrArray = instruments.split(',');
instrArray.forEach(function(instrument){
instrument = instrument.trim();
Profiles.update({ userId: this.userId }, { $addToSet: { instruments: instrument } });
});
to:
let userId = this.userId;
let instrArray = instruments.split(',');
instrArray.forEach(function(instrument){
instrument = instrument.trim();
Profiles.update({ userId: userId }, { $addToSet: { instruments: instrument } });
});
Thanks everyone for looking over my code!

Your query seems fine to me. As long as you are doing that on server, you don't need to configure anything. Not a bug in Meteor.

Related

How can I make a subarray unique

I am having an issue with the following
{
artist:"Macy Gray"
song:"I Try'"
station:"PERTHRadio"
timeplay:2020-07-17T10:39:00.000+00:00
__v:0
history:Array
0:"7320564F-76B2-40D0-A0E8-E3917148F567"
1:"7320564F-76B2-40D0-A0E8-E3917148F567"
}
Basically it's adding the same UUID twice in history.
I am using a findOneAndUpdate with $push.
The code I am using
const nowplayingData = {
"station": req.params.stationname,
"song": data[1],
"artist": data[0],
"timeplay":npdate
};
LNowPlaying.findOneAndUpdate(
nowplayingData,
{ $push: { history: [uuid] } },
{ upsert: true },
function(err) {
if (err) {
console.log('ERROR when submitting round');
console.log(err);
}
}
);
Usually when people experience an issue like this It's because the function / route the code is in is being run twice. (Again -usually- this is due to debugging where the debugger is firing an extra call or something of the sort).
Regardless if this happens to you while debugging or in production you can just start using $addToSet instead of push, this will guarantee duplicate values will not be pushed.
LNowPlaying.findOneAndUpdate(
nowplayingData,
{ $addToSet: { history: [uuid] } },
{ upsert: true },
function(err) {
if (err) {
console.log('ERROR when submitting round');
console.log(err);
}
}
);

Mongoose findOneAndUpdate with $addToSet pushes duplicate

I have a schema such as
listSchema = new Schema({
...,
arts: [
{
...,
art: { type: Schema.Types.ObjectId, ref: 'Art', required: true },
note: Number
}
]
})
My goal is to find this document, push an object but without duplicate
The object look like
var art = { art: req.body.art, note: req.body.note }
The code I tried to use is
List.findOneAndUpdate({ _id: listId, user: req.myUser._id },
{ $addToSet: { arts: art} },
(err, list) => {
if (err) {
console.error(err);
return res.status(400).send()
} else {
if (list) {
console.log(list)
return res.status(200).json(list)
} else {
return res.status(404).send()
}
}
})
And yet there are multiple entries with the same Art id in my Arts array.
Also, the documentation isn't clear at all on which method to use to update something. Is this the correct way ? Or should I retrieve and then modify my object and .save() it ?
Found a recent link that came from this
List.findOneAndUpdate({ _id: listId, user: req.user._id, 'arts.art': artId }, { $set: { 'arts.$[elem]': artEntry } }, { arrayFilters: [{ 'elem.art': mongoose.Types.ObjectId(artId) }] })
artworkEntry being my modifications/push.
But the more I'm using Mongoose, the more it feels they want you to use .save() and modify the entries yourself using direct modification.
This might cause some concurrency but they introduced recently a, option to use on the schema { optimisticConcurrency: true } which might solve this problem.

Meteor: How to subscribe to different publications for different templates on the same page?

I have two templates that I'd like to render on the same page. One is a template that lists recent items; the other one lists items that are $text search results.
Data for each template is from a separate subscription. The problem is, minimongo doesn't support $text search, so I can't use $text to limit results from the client once the subscriptions are returned. That's a problem because both subscriptions are mixed together at the client side, so both my search results and recent items results look weird, because they each draw from both subscriptions.
I'm attempting to deal with it by using Iron Router to specify which template subscribes to which publication. However, my code doesn't work.
on the server, the file app.js, two separate publications:
if (Meteor.isServer) {
Meteor.publish("myitems", function () {
return Items.find();
});
Items._ensureIndex({
"itemName": "text",
//"tags" : "text"
});
Meteor.publish("search", function (searchValue) {
if (!this.userId) {
this.ready();
return;
}
return Items.find(
{
createdBy: this.userId,
$text: {$search: searchValue},
retired: {$ne: true}
},
{
fields: {
score: {$meta: "textScore"}
},
sort: {
score: {$meta: "textScore"}
}
}
);
});
}
client side code:
helper for the recent items template:
Template.myitems.helpers(
{
items: function () {
var d = new Date();
var currentUser = Meteor.userId();
return Items.find(
{
createdBy: currentUser,
createdAt: {
$gte: new Date(d.setDate(d.getDate() - 30))
}
},
{
sort: {
createdAt: -1
},
limit: 5
});
}
});
helper for the search results template:
Template.searchResults.helpers({
searchitems: function () {
if (Session.get("searchValue")) {
return Items.find({
}, {
sort: {"score": -1, "itemName": -1},
//limit: 10
});
} else {
//return Items.find({});
}
}
});
}
onCreated subscription for each template, separately:
Template.myitems.onCreated (function () {
Meteor.subscribe('myitems');
});
Template.searchResults.onCreated (function () {
Meteor.subscribe('search');
});
Router controller configuration: yes you'll see that it attempts to subscribe as well, but it fails anyway, so there's no duplicate subscription to "myitems"
itemsController = RouteController.extend({
//waitOn: function() {
// return [
// Meteor.subscribe('myitems')
// ];
//},
//data: function() {
// //return { items : Items.find({}), item_id : this.params._id }
// return {items: Items.find()};
//},
action: function() {
this.render('items');
this.render('searchitems', {to: 'region1'});
this.render('myitems', {
to: 'region3',
waitOn: function() {
return [
Meteor.subscribe('myitems')
];
},
data: function(){
return {items: Items.find()};
}
});
}
});
The above iron router code doesn't attempt to subscribe to the search publication. It attempts to subscribe to the recent items ('myitems') publication, but somehow the returned "items" is empty. The issue is not due to any wrong setting in the publication, because the commented out code works: if it were uncommented, then "items" do get returned and isn't empty, even if I don't use onCreated to subscribe to it.
My questions are:
what's wrong with the above code? I know that the subscription to "myitems" fail from the Iron Router. The subscription to "myitems" succeeds in the "onCreate", but the search results also draws from "myitems", instead of drawing from "searchResults" only.
assuming I can fix the above code, is Iron Router the way to go to solve my original problem: the search results subscription and the recent items subscription need to be separate, although the two templates are to be rendered on the same webpage?

updating existing Mongo collection object in Meteor

I saw the other answers like this, however I think mine is a little more specific.
I have Meteor.user() as Object {_id: "iymu2h9uysCiKFHvc", emails: Array[2], profile: Object, services: Object}
and I'm running a function to set the profile first name and last name here:
thisId = Meteor.userId();
Meteor.users.update({ _id: thisId }, { $set: {
profile: {
first_name: $('#firstName').val(),
last_name: $('#lastName').val()
}
}
});
I also, however, would like to, on a different event, add a a notifications object to the profile.
I tried :
thisId = Meteor.userId();
Meteor.users.update({ _id: thisId }, { $set: {
profile: {
notifications: {
rcptDwnldFile: Session.get('recpt-dwnld-file'),
rcptPaysInv: Session.get('recpt-pays-inv'),
invSentDue: Session.get('inv-sent-due'),
// the rest
}
}
}
});
but that overrides my first_name, last_name entries. I also tried $setOnInstert but i get a update failed: Access denied. Operator $setOnInsert not allowed in a restricted collection. but I thought that profile was writable by the user by default.
use this instead (more info link - see section Set Fields in Embedded Documents):
thisId = Meteor.userId();
Meteor.users.update({ _id: thisId }, { $set: {
'profile.first_name': $('#firstName').val(),
'profile.last_name': $('#lastName').val()
}
});
and
thisId = Meteor.userId();
Meteor.users.update({ _id: thisId }, { $set: {
'profile.notifications.rcptDwnldFile': Session.get('recpt-dwnld-file'),
'profile.notifications.rcptPaysInv': Session.get('recpt-pays-inv'),
'profile.notifications.invSentDue': Session.get('inv-sent-due'),
// the rest
}
});

Populating nested array with ObjectIDs

Trying to populate an array of ObjectID's within my schema. I've looked around at similar answers but it seems everyone is doing it slightly differently and I haven't been able to find a solution myself.
My schema looks like this:
var GameSchema = new Schema({
title: String,
description: String,
location: String,
created_on: { type: Date, default: Date.now },
active: { type: Boolean, default: true },
accepting_players: { type: Boolean, default: true },
players: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
admins: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
});
So far I've been trying to populate it like this, which obviously isn't working
exports.getAdmins = function(req, res) {
Game.findById(req.params.id)
.populate('admins')
.exec(function(err, game) {
return res.json(200, game.admins);
});
};
I hate to add to the list of population questions, but I've looked at many and haven't found a solution. Any help is greatly appreciated!
Edit:
Here's how I am adding admins to the document
// Add admin to game
exports.addAdmin = function(req, res) {
Game.findByIdAndUpdate(
req.params.id,
{ $push: { 'admins': req.params.user_id }},
function(err, game) {
if(err) { return handleError(res, err); }
if(!game) { return res.send(404); }
return res.json(200, game.admins);
});
};
Well I went back to mongoose documentation, and decided to change how I looked up a game by an ID and then populated the response.
Now my working function looks like this:
// Returns admins in a game
exports.getAdmins = function(req, res) {
Game.findById(req.params.id, function(err, game) {
if(err) { return handleError(res, err); }
if(!game) { return res.send(404); }
Game.populate(game, { path: 'admins' }, function(err, game) {
return res.json(200, game);
});
});
};
The issue I was having was that I was trying to call the .populate function directly with the .findById method, but that doesn't work because I found on mongoose's documentation the the populate method need the callback function to work, so I just added that and voila, it returned my User object.
to populate an array, you just have to put model name field after path field like this :
Game.findById(req.params.id)
.populate({path: 'admins', model: 'AdminsModel'})
.exec(function(err, game){...});
it works perfectly on my projects...