I have a Meteor app that has become very unresponsive now that I am trying to use Templates and Template helpers more. I basically have two large collections in MongoDB say collection1 with 1531589 documents and collection2 with 1517397 documents. For each collection, the documents within it adhere to a single schema, i.e. collection1 has it's own schema, and collection2 has it's own schema. Both schemas have only a few properties (~5). In my Meteor code I have taken the following approach
Col1 = new Mongo.Collection('collection1');
Col2 = new Mongo.Collection('collection2');
In publications.js
Meteor.publish('collection1', function() {
if (Roles.userIsInRole(this.userId, 'admin')) {
return Col1.find({"ID": "12345"});
} else {
this.stop();
return;
}
});
Meteor.publish('collection2', function() {
if (Roles.userIsInRole(this.userId, 'admin')) {
return Col2.find({"ID": "12345"});
} else {
this.stop();
return;
}
});
In subscriptions.js
Meteor.subscribe('collection1');
Meteor.subscribe('collection2');
I then have templates that should display data from each collection by means of template helpers, so in essence
<template name="superInfo">
{{#each latestInfo}}
<p>{{latestInfo.prop1}}</p>
<p>{{latestInfo.prop2}}</p>
{{/each}}
</template>
with a template helper
Template.superInfo.helpers({
latestInfo: function() {
return Col1.find({"ID": "12345"}, {
sort: {Date: -1},
limit: 1
});
}
});
I know the fault probably lies with how I am querying/publishing/subscribing and I have read posts like this but I still can't figure out the most efficient way of doing this. Another issue might be how I have configured Mongo locally? I have made sure that there are indexes on each collection for the ID field, but that hasn't helped. Whenever I start my app and browser to the page using these templates it becomes totally unresponsive.
I put a few console.logs in the template helpers and noticed they were getting called quite a lot so that also might be an issue. Any suggestions as to how best to address these issues would be really appreciated.
Related
In Mongoose, I can use a query populate to populate additional fields after a query. I can also populate multiple paths, such as
Person.find({})
.populate('books movie', 'title pages director')
.exec()
However, this would generate a lookup on book gathering the fields for title, pages and director - and also a lookup on movie gathering the fields for title, pages and director as well. What I want is to get title and pages from books only, and director from movie. I could do something like this:
Person.find({})
.populate('books', 'title pages')
.populate('movie', 'director')
.exec()
which gives me the expected result and queries.
But is there any way to have the behavior of the second snippet using a similar "single line" syntax like the first snippet? The reason for that, is that I want to programmatically determine the arguments for the populate function and feed it in. I cannot do that for multiple populate calls.
After looking into the sourcecode of mongoose, I solved this with:
var populateQuery = [{path:'books', select:'title pages'}, {path:'movie', select:'director'}];
Person.find({})
.populate(populateQuery)
.execPopulate()
you can also do something like below:
{path:'user',select:['key1','key2']}
You achieve that by simply passing object or array of objects to populate() method.
const query = [
{
path:'books',
select:'title pages'
},
{
path:'movie',
select:'director'
}
];
const result = await Person.find().populate(query).lean();
Consider that lean() method is optional, it just returns raw json rather than mongoose object and makes code execution a little bit faster! Don't forget to make your function (callback) async!
This is how it's done based on the Mongoose JS documentation http://mongoosejs.com/docs/populate.html
Let's say you have a BookCollection schema which contains users and books
In order to perform a query and get all the BookCollections with its related users and books you would do this
models.BookCollection
.find({})
.populate('user')
.populate('books')
.lean()
.exec(function (err, bookcollection) {
if (err) return console.error(err);
try {
mongoose.connection.close();
res.render('viewbookcollection', { content: bookcollection});
} catch (e) {
console.log("errror getting bookcollection"+e);
}
//Your Schema must include path
let createdData =Person.create(dataYouWant)
await createdData.populate([{path:'books', select:'title pages'},{path:'movie', select:'director'}])
I want to update only the name field, the problem with the code I have is that if I update a document, all the mongo documents are updated.
As I update a document in specific, I must admit that I am new to this mongo so any help I thank you.
Client
updatename.html
<template name="updatename">
<form class="editor-container">
<input class=“save” type="text" id="card" value=“{{name}}”>
<button type="button" class=“save” id="save">save</button>
</form>
</template>
updatename.js
Template.updatename.events({
'click .save’: function (e, t) {
e.preventDefault();
FlowRouter.watchPathChange();
var name = FlowRouter.current().params._id;
var name = $('#card').val();
Meteor.call('name.update',name);
FlowRouter.go('/');
}
});
Server
name.js
Meteor.methods({
'name.update'( name) {
Name.update({
Name.update({},{ $set: { nam: name }},{multi:true})
});
}
});
I recommend few improvisations over your Meteor code.
Atleast use Title Case/ CamelCase for better readability of the template name and Meteor Methods for other developer.
use submit .formClassName instead of using click .save, also specifiy parameter name with sincerity like function (event, template)
When you updating document for logged user and not other user, as dmayo mentioned in the code use Name.update({_id: Meteor.userId()},{ $set: {nam: name}}), but there is no sense of specifying { multi: true } when you know that there is going to be only 1 record when you are updating. You can use { multi: true } when you desire to impact many records based on criteria that are definitely going to return more than 1 record.
use check(name, String) in Meteor.method call to ensure what you are sending to the server is eligible for further operations.
Use aldeed autoforms when you know there is no out of the box implementation and is going to be simple.
Below is the improvised code for better readability and up to standards
Client
update-name.html
<template name="UpdateName">
<form class="editorContainerForm">
<input type="text" id="card" value=“{{name}}”>
<button type="submit">Save</button>
</form>
</template>
update-name.js
Template.UpdateName.events({
'submit .editorContainerForm’: function (event, template) {
event.preventDefault();
FlowRouter.watchPathChange();
var name = FlowRouter.current().params._id;
var name = $('#card').val();
Meteor.call('updateName',name, function(error, response){
if(error){
// Show some BERT ERROR message for better user experience
// use "meteor add themeteorchef:bert" to add package
} else {
// Show some BERT SUCCESS message for better user experience
});
FlowRouter.go('/');
}
});
Server
name.js
Meteor.methods({
updateName( name ) {
check(name, String);
Name.update({ _id: Meteor.userId },{ $set: { name: name }});
// Use below code only if you know there can be multiple records for same ID
// Name.update({ _id: Meteor.userId },{ $set: { name: name }}, { multi: true });
}
});
In your name.js file (on the server) your mongo query is empty, so when mongo queries your database, it matches all of the documents/records.
Name.update(query, changes, options)
That is the format per the mongo docs. You need to have a unique identifier. Your form is saving a "name", and that's what you are passing to the Meteor.method, but you're not telling the method who's changing their name. If the user is logged in, then you can just use the meteor unique id Meteor.userId()
Name.update({_id: Meteor.userId()},{ $set: {nam: name}},{multi:true})
Also, your option multi:true says to update any and all documents that match the query. If in your original method as written, you had multi:false (the default) then only one document would have been updated (but probably not the one you wanted as the first match would have been updated because of your empty query field.
Mongo docs: https://docs.mongodb.com/manual/reference/method/db.collection.update/
Metor docs: https://docs.meteor.com/api/collections.html#Mongo-Collection-update
I am very new to meteor.js and try to build an application with it. This time I wanted to try it over MEAN stack but at this point I am struggled to understand how to join two collection on server side...
I want very identical behaviour like mongodb populate to fetch some properties of inner document.
Let me tell you about my collection it is something like this
{
name: 'Name',
lastName: 'LastName',
anotherObject: '_id of another object'
}
and another object has some fields
{
neededField1: 'asd',
neededField2: 'zxc',
notNeededField: 'qwe'
}
So whenever I made a REST call to retrieve the first object I want it contains only neededFields of inner object so I need join them at backend but I cannot find a proper way to do it.
So far while searching it I saw some packages here is the list
Meteor Collections Helper
Publish with Relations
Reactive joins in Meteor (article)
Joins in Meteor.js (article)
Meteor Publish Composite
You will find the reywood:publish-composite useful for "joining" related collections even though SQL-like joins are not really practical in Mongo and Meteor. What you'll end up with is the appropriate documents and fields from each collection.
Using myCollection and otherCollection as pseudonyms for your two collections:
Meteor.publishComposite('pseudoJoin', {
find: function() {
return myCollection.find();
},
children: [
{
find: function(doc) {
return otherCollection.find(
{ _id: post.anotherObject },
{ fields: { neededField1: 1, neededField2: 1 } });
}
}
]
});
Note that the _id field of the otherCollection will be included automatically even though it isn't in the list of fields.
Update based on comments
Since you're only looking to return data to a REST call you don't have to worry about cursors or reactivity.
var myArray = myCollection.find().fetch();
var myOtherObject = {};
var joinedArray = myArray.map(function(el){
myOtherObject = otherCollection.findOne({ _id: el.anotherObject });
return {
_id: el._id,
name: el.name,
lastName: el.lastName,
neededField1: myOtherObject.neededField1,
neededField2: myOtherObject.neededField2
}
});
console.log(joinedArray); // These should be the droids you're looking for
This is based on a 1:1 relation. If there are many related objects then you have to repeat the parent object to the number of children.
I am building some little log reporting with meteor.
have a dead simple table, that should contain all the data that received from the external mongodb server.
the html:
<tbody>
{{#each impressions}}
<tr>
{{> impression}}
</tr>
{{/each}}
</tbody>
js :
Meteor.subscribe('impressions');
...
...
Template.logResults.helpers({
'impressions': function() {
var sTs = Router.current().params.fromTs;
var eTs = Router.current().params.toTs;
return Impressions.find({});
}
});
So far, so good. BUT, when I am changing the query to this one :
Impressions.find({
$and: [{
ts: {
$gte: sTs
}
}, {
ts: {
$lte: eTs
}
}]
});
The results aren't displayed on the HTML DOM,
I tried to debug that, and created a console.log of this exact query,
and surprisingly all the correct results return successfully to the console.
screenshot attached.
I am probably doing something wrong, maybe with publish/subscribe thing.
help someone?
Thanks.
P.S. I removed the insecure and auto-publish,
have this code on the server folder
Meteor.publish('impressions', function() {
return Impressions.find();
});
and this code on the main lib folder
Impressions = new Mongo.Collection("banners");
enter image description here
The router stores the parameters for the current route as strings (which makes sense because URLs are strings), so you need to explicitly convert the values to integers before querying the database. Give something like this a try:
var sTs = Number(Router.current().params.fromTs);
var eTs = Number(Router.current().params.toTs);
Notes:
parseInt or parseFloat may be a better choice depending on the nature of your input. See this question for more details.
You could do the type conversion in the router itself and pass the values in the data context to the helpers. See this section of the iron router guide.
I suspect it worked when you typed it into the console because you used numbers instead of strings. E.g. ts: {$gte: 123} instead of ts: {$gte: '123'}.
I have the following scenario. There is a collection Suppliers and another Invited. Now Invited.supplier = Supplier._id (syntax might be wrong) Invited collection refers to Suppliers in One to Many fashion.
In my html , I have
<template name="mytemplate">
{{#each invited_list}}
{{supplier}}
{{f1}}
{{f2}}
{{/each}}
</template>
I have a helper function
Template.mytemplate.helpers({
invited_list : function(){
return Invited.find({"something"});
}
});
I would like to display {{Suppliers.name}} instead of _id in {{supplier}} in my invited_list . What are my options?
You could create a resolver function such as:
Template.mytemplate.helpers({
invited_list : function(){
return resolveSupplierToNames(Invited.find({"something"}).fetch());
}
});
function resolveSupplierToNames(invitedList) {
for (var i=0; i<invitedList.length; i++) {
invitedList[i].supplier = Suppliers.findOne({_id: invitedList[i].supplier}).name;
}
return invitedList;
}
There are generally two options with mongodb, one is the above (manual). The second is to use DBRefs. However I'm not sure meteor supports DBRefs completely yet. As suggested in the mongodb docs theres nothing wrong with doing it manually.
Update
Meteor has since introduced a transform function, you can do something similar like:
Template.mytemplate.helpers({
invited_list : function(){
return Invited.find({"something"},{transform:function(doc) {
doc.supplier_name = Suppliers.findOne({_id: doc.supplier_id}).name;
return doc;
});
}
});