Add array object to minimongo - mongodb

I have a chat app, that is using Ionic 2 and Meteor with MongoDB. It works perfectly.
However, everything is stored in the MongoDB on the server, so each time a user wants to view their messages, they need to be connected to the Meteor/Mongo Server running in the cloud. Also, if one user deletes their chat, it will delete the chat on the MongoDB, and the corresponding other user will also have their chat deleted.
I would like similar functionality as WhatsApp where the messages are held locally on the device (I am using SQLite), and only new messages are held in the cloud until both users download them.
Currently my app iterates over a Mongo.Cursor<Chat> object. It also observes this object (this.chats.observe({changed: (newChat, oldChat) => this.disposeChat(oldChat), removed: (chat) => this.disposeChat(chat)});).
I get chat data from SQLlite that I have stored locally (Array<Chat>).
Question
Is it possible to add the SQLite data (Array<Chat>) to the Mongo.Cursor<Chat>? When I do so, I want to just add to minimongo and not MongoDB on the server.
Thanks
UPDATE
Asp per advise below, I do the following:
let promise: Promise<Mongo.Cursor<Chat>> = new Promise<Mongo.Cursor<Chat>>(resolve => {
this.subscribe('chats', this.senderId, registeredIds, () => {
let chats: Mongo.Cursor<Chat> = Chats.find(
{ memberIds: { $in: registeredIds } },
{
sort: { lastMessageCreatedAt: -1 },
transform: this.transformChat.bind(this),
fields: { memberIds: 1, lastMessageCreatedAt: 1 }
}
);
this.localChatCollection = new Mongo.Collection<Chat>(null);
console.log(this.localChatCollection);
chats.forEach(function (chat: Chat) {
console.log('findChats(): add chat to collection: ' + chat);
this.localChatCollection.insert(chat);
});
Will update if it works.
UPDATE
When I do the following, it inserts the chat object:
let promise: Promise<Mongo.Collection<Chat>> = this.findChats();
promise.then((data: Mongo.Collection<Chat>) => {
let localChatCollection: Mongo.Collection<Chat> = new Mongo.Collection<Chat>(null);
data.find().forEach(function (chat: Chat) {
console.log('==> ' + chat);
localChatCollection.insert(chat);
});
However, if I define the localChatCollection globally, it does not insert the chat object. There are no errors but the process just stops on the insert line.
private localChatCollection: Mongo.Collection<Chat> = new Mongo.Collection<Chat>(null);
....
this.localChatCollection.insert(chat);
Any ideas how I can get this to insert into a globally defined collection?

Is it possible to add the SQLite data (Array) to the Mongo.Cursor? When I do so, I want to just add to minimongo and not MongoDB on the server.
Meteor itself knows nothing about SQLite, but it sounds like you have that part of it working.
To just add to minimongo and not the mongodb server, you're looking for a client-side collection. Just pass null in as the first parameter to the call to create your collection i.e.
var localChatCollection = new Mongo.Collection(null)
You can then insert to localChatCollection the same way you would with a synchronized collection.
Source: Meteor docs

Related

How to use Redis and MongoDb together

I should build a web application to track users activities and I have some issues to understand how can use Redis for tracking online users activities and Mongo to store that data for analyzing it.
I could use just Mongo but I'm worried about the fact I have a lot of invocations to follow what a user is doing. So I was thinking to write on Redis the online data and put in Mongo when they become old. I mean old when the data is meaningless for being online.
I thought about one gateway between Mongo and Redis, so could be RabbitMQ?.
Any suggestions?
I should use just Mongo?
Just an example of code I wrote:
Front-end (Angular application / Socket.io):
setInterval(function () {
socket.emit('visitor-data', {
referringSite: document.referrer,
browser: navigator.sayswho,
os: navigator.platform,
page: location.pathname
});
}, 3000);
Back-end ( Node.js/ Socket.io)
socket.on('visitor-data', function(data) {
visitorsData[socket.id] = data;
);
VisitorsData is a just in an array, but I should build a scalable application, so I can't store data anymore in this way.
Then I have some functions like this for computing the data:
function computeRefererCounts() {
var referrerCounts = {};
for (var key in visitorsData) {
var referringSite = visitorsData[key].referringSite || '(direct)';
if (referringSite in referrerCounts) {
referrerCounts[referringSite]++;
} else {
referrerCounts[referringSite] = 1;
}
}
return referrerCounts;
}
Just some numbers:
I estimated something like :
1 million users for day
15 million activities for day.

Updating Mongo Object Field

I'm trying to update an object stored in Mongo that gets created as part of every new users document when they register for my site. By default this object is empty.
How can I push data directly into this object which is within the subfield profile.history.
So far I have been only able to push data into the root of the document itself.
Looking at the image, as stated, I want to write to the history object in profile.
I think you're talking about the Meteor.users collection, below is some code:
let myDynamicField = 'foo'; // Or whatever you want, an input value for example...
let update = {};
update[`profile.history.${myDynamicField1}`] = 'blah';
Meteor.users.update(
{
"_id": "testing123"
},
{
$set: {
update
}
});
*Edited to reflect what the user was asking for in the comments.

Meteor application not seeding db in deployment

I have a meteor application which upon startup seeds a mongo db document:
Meteor.startup(function () {
Dynamics.remove({});
Dynamics.insert({ name : "voteTimer", time : 0 });
Dynamics.insert({ name : "winningWord", content : "" });
});
These are called in a React component eg:
getMeteorData() {
return {
winningWord: Dynamics.findOne({name: "winningWord"}).content
}
},
On my local machine this works fine. Once deployed via meteor deploy however, the app crashes:
Cannot read property 'content' of undefined
This indicates that there are no documents in the Dynamics collection. Even stranger, I am still able to access these variable in the chrome dev console.
Even if you start inserting items on startup, those inserts are asynchronous and your component's getMeteorData probably still tries to fetch your document before it is inserted. Since getMeteorData is reactive (I think), you simply need to check for your findOne to return a proper document and it should work as soon as the document is ready:
getMeteorData() {
var dynamic = Dynamics.findOne({name: "winningWord"});
if (dynamic) {
return {
winningWord: dynamic.content
}
}
return {winningWord:""}; // whatever
},

Subscribing to Meteor.Users Collection

// in server.js
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client.js
Meteor.subscribe("directory");
I want to now get the directory listings queried from the client like directory.findOne() from the browser's console. //Testing purposes
Doing directory=Meteor.subscribe('directory')/directory=Meteor.Collection('directory') and performing directory.findOne() doesn't work but when I do directory=new Meteor.Collection('directory') it works and returns undefined and I bet it CREATES a mongo collection on the server which I don't like because USER collection already exists and it points to a new Collection rather than the USER collection.
NOTE: I don't wanna mess with how Meteor.users collection handles its function... I just want to retrieve some specific data from it using a different handle that will only return the specified fields and not to override its default function...
Ex:
Meteor.users.findOne() // will return the currentLoggedIn users data
directory.findOne() // will return different fields taken from Meteor.users collection.
If you want this setup to work, you need to do the following:
Meteor.publish('thisNameDoesNotMatter', function () {
var self = this;
var handle = Meteor.users.find({}, {
fields: {emails: 1, profile: 1}
}).observeChanges({
added: function (id, fields) {
self.added('thisNameMatters', id, fields);
},
changed: function (id, fields) {
self.changed('thisNameMatters', id, fields);
},
removed: function (id) {
self.removed('thisNameMatters', id);
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
No on the client side you need to define a client-side-only collection:
directories = new Meteor.Collection('thisNameMatters');
and subscribe to the corresponding data set:
Meteor.subscribe('thisNameDoesNotMatter');
This should work now. Let me know if you think this explanation is not clear enough.
EDIT
Here, the self.added/changed/removed methods act more or less as an event dispatcher. Briefly speaking they give instructions to every client who called
Meteor.subscribe('thisNameDoesNotMatter');
about the updates that should be applied on the client's collection named thisNameMatters assuming that this collection exists. The name - passed as the first parameter - can be chosen almost arbitrarily, but if there's no corresponding collection on the client side all the updates will be ignored. Note that this collection can be client-side-only, so it does not necessarily have to correspond to a "real" collection in your database.
Returning a cursor from your publish method it's only a shortcut for the above code, with the only difference that the name of an actual collection is used instead of our theNameMatters. This mechanism actually allows you to create as many "mirrors" of your datasets as you wish. In some situations this might be quite useful. The only problem is that these "collections" will be read-only (which totally make sense BTW) because if they're not defined on the server the corresponding `insert/update/remove' methods do not exist.
The collection is called Meteor.users and there is no need to declare a new one on neither the server nor the client.
Your publish/subscribe code is correct:
// in server.js
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client.js
Meteor.subscribe("directory");
To access documents in the users collection that have been published by the server you need to do something like this:
var usersArray = Meteor.users.find().fetch();
or
var oneUser = Meteor.users.findOne();

Mongoose - update after populate (Cast Exception)

I am not able to update my mongoose schema because of a CastERror, which makes sence, but I dont know how to solve it.
Trip Schema:
var TripSchema = new Schema({
name: String,
_users: [{type: Schema.Types.ObjectId, ref: 'User'}]
});
User Schema:
var UserSchema = new Schema({
name: String,
email: String,
});
in my html page i render a trip with the possibility to add new users to this trip, I retrieve the data by calling the findById method on the Schema:
exports.readById = function (request, result) {
Trip.findById(request.params.tripId).populate('_users').exec(function (error, trip) {
if (error) {
console.log('error getting trips');
} else {
console.log('found single trip: ' + trip);
result.json(trip);
}
})
};
this works find. In my ui i can add new users to the trip, here is the code:
var user = new UserService();
user.email = $scope.newMail;
user.$save(function(response){
trip._users.push(user._id);
trip.$update(function (response) {
console.log('OK - user ' + user.email + ' was linked to trip ' + trip.name);
// call for the updated document in database
this.readOne();
})
};
The Problem is that when I update my Schema the existing users in trip are populated, means stored as objects not id on the trip, the new user is stored as ObjectId in trip.
How can I make sure the populated users go back to ObjectId before I update? otherwise the update will fail with a CastError.
see here for error
I've been searching around for a graceful way to handle this without finding a satisfactory solution, or at least one I feel confident is what the mongoosejs folks had in mind when using populate. Nonetheless, here's the route I took:
First, I tried to separate adding to the list from saving. So in your example, move trip._users.push(user._id); out of the $save function. I put actions like this on the client side of things, since I want the UI to show the changes before I persist them.
Second, when adding the user, I kept working with the populated model -- that is, I don't push(user._id) but instead add the full user: push(user). This keeps the _users list consistent, since the ids of other users have already been replaced with their corresponding objects during population.
So now you should be working with a consistent list of populated users. In the server code, just before calling $update, I replace trip._users with a list of ObjectIds. In other words, "un-populate" _users:
user_ids = []
for (var i in trip._users){
/* it might be a good idea to do more validation here if you like, to make
* sure you don't have any naked userIds in this array already, as you would
*/in your original code.
user_ids.push(trip._users[i]._id);
}
trip._users = user_ids;
trip.$update(....
As I read through your example code again, it looks like the user you are adding to the trip might be a new user? I'm not sure if that's just a relic of your simplification for question purposes, but if not, you'll need to save the user first so mongo can assign an ObjectId before you can save the trip.
I have written an function which accepts an array, and in callback returns with an array of ObjectId. To do it asynchronously in NodeJS, I am using async.js. The function is like:
let converter = function(array, callback) {
let idArray;
async.each(array, function(item, itemCallback) {
idArray.push(item._id);
itemCallback();
}, function(err) {
callback(idArray);
})
};
This works totally fine with me, and I hope should work with you as well