I have ran into a recent problem where my router doesn't seem to have the collection to route the name.
I have a collection named Nodes. In this collection there are many reading from many nodes with various kinds of data in each node. Although the name attribute for these nodes are not unique(using a simpleSchema). This is that particular node can send many points of data. Later I will graph this data from the collection.
Nodes insert is
var sampleInput_4 ={
name : "Tulsa Node",
longitude : -95.982,
latitude : 36.137,
humidity : 78,
tem p: 80,
dew : 20,
pressure : 32,
speed : 12,
direction : 60
};
But there could be thousands of these inserts. And thousands of a different nodes inserts. I publish the whole nodes collection and in the sidebar js file is subscribe on Created to the whole collection. this is just for testing this problem. here is the sidebar js file.
Template.sidebar.helpers({
nodes: function(){
var col= Nodes.find().fetch();
return _.uniq(_.pluck(col,'name'));
}
});
Template.sidebar.onCreated(function () {
this.subscribe('nodes');
});
This works fine in the HTML loading just the unique names like I want.
{{#each nodes}}
<li>
<a href="{{pathFor 'NodePage'}}">
{{this}}
</a>
</li>
{{/each}}
However this does not route the way I want. There in fact is no route when I do it this way. I want the route to be /name of the unique name. Does not matter which name of which document. just the unique name of any as long as it is the one I clicked on.
Here is the router
Router.route('/:_id', {
name : 'NodePage',
data : function() { return Nodes.findOne(
// this refers to the currently matched
//route.this.params access parts of route
this.params._id);
}
});
Although If I put
return Nodes.find();
in the sidebar js file for the return the route works. Am I missing some fundamental aspect of Iron router? Also the sidebar after this just returns every [object] in the entire collection. Although you can click on these and the router works on them.
Turns out for the Router to use the name attribute it needed to be pulling it from an object so I sent an array of object through the each code in the HTML. So the helper just needed to form an array of object with unique names to return.
Template.sidebar.helpers({
nodes : function() {
//make array to hold objects
var myObjectArray = [];
// grab entire collection
var nodeCollection = Nodes.find().fetch();
// Get the unique names from collection
var nodeNames = _.uniq(_.pluck(nodeCollection,'name'));
// find the Node with that name and
// place into object array loop till done
for(i = nodeNames.length; i>0; i--){
var arrayItem = nodeNames[i-1];
var nodeObject = Nodes.findOne({name: arrayItem});
myObjectArray.push(nodeObject);
}
return myObjectArray;
}
});
Related
I'm using React with Meteor and am having trouble keeping my data updated. Here is my getMeteorData() code in a Conversation component
getMeteorData() {
var vertices_handle = Meteor.subscribe('VertexIDs', this.props.conversation_id);
return {
vertices: Vertices.find({conversation: this.props.conversation_id}).fetch(),
ready: vertices_handle.ready()
};
}
The subscription only returns the IDs of the posts (vertices) and I use this data to render more components:
renderPostList() {
return this.data.vertices.map((post) => {
return <PostThread
key = {post._id}
root_id = {post._id}
conversation_id = {this.props.conversation_id} />;
});
}
Within the PostThread component I subscribe to each post individually by its ID to get the rest of the data as needed. However, when I remove something from the Vertices collection, the Conversation component doesn't seem to be updating. I can see in MeteorToys that the Vertices collection on the client has removed a post, but this change sometimes isn't reflected in the UI. Sometimes when a post is removed the UI updates correctly but other times it doesn't and I have not been able to find a pattern to this.
I have two different templates in one route. Both return a number of items from a uniform collection however, when I do
Template.stepOneSelect.onCreated(function() {
var instance = this;
instance.autorun(function() {
var subsciption = instance.subscribe('stepOne');
});
instance.occupations = function() {
return Occupations.find();
}
});
it returns Occupations from the whole route. There isn't a way for me to query the ones I need in one template because I query them on the server with other Collection that I am not subscribed to in that template.
So I need
Template.stepOneSelect.helpers({
stepOneTitles: function () {
return Template.instance().occupations();
}
});
to only return the Occupations from that template and I am getting all the occupations from the route
// MORE INFO
I have two collections, CareerPaths and Occupations.
CareerPaths has fields like occupationOneId which is an _id of the Occupation. It would be easy if I had a field in each Occupation that states which step of the CareerPath it is but one Occupation can be in different steps of a CareerPath. So I need to be returning Occupations based on CareerPaths. The route has two sections, one with a list of CareerPaths with a limit (only 10 at a time) and the other section should have ALL the Occupations from the first step of a career path, etc. I haven't found anything in publishComposite to only return the Children of a publication.
If I am getting this right, you are trying to display only the data published by a particular subscription. The easy way to do this would be to move the query used in the publication into client/server code, then call the query from both the publication and the client-side query.
I have a template that displays documents from three different collections Cars, CarPaints, and CarPaintTypes. I know I need all these upfront at the Router level. The template will show a Car document, all the CarPaints that reference that Car, and all the CarPaintTypes that reference the returned CarPaints respectively (think nested list). The route to the template takes an id from the URL that represents Car._id.
Both the Cars collection and CarPaints collection make use of the Car._id as a field (it's the native _id of the Cars collection and a field in the CarPaints collection) so that's easy. However, the CarPaintTypes uses the CarPaint._id as a reference to what CarPaint it belongs to.
So I have three publications:
Meteor.publish('car', function(carId) {
return Cars.find({_id: carId});
});
Meteor.publish('carPaints', function(carId) {
return CarPaints.find({carId: carId});
});
Meteor.publish('carPaintTypes', function(carPaintId) {
return CarPaintTypes.find({carPaintId: carPaintId});
});
My route looks like:
this.route('car', {
path: '/car/:_id',
waitOn: function() {
return [Meteor.subscribe('car', this.params._id),
Meteor.subscribe('carPaints', this.params._id)];
// Can't figure out how to subscribe to or publish
// the carPaintTypes using all the results of what gets
// returned by 'carPaints'
}
});
My question is CarPaintTypes doesn't have the Car._id as a field, just the CarPaint._id to reference to a CarPaint document. Where and how I do take the results of the subscription to carPaints and pass each carPaint document that's returned to a subscription to carPaintTypes? Or is there a way to combine them all in the publication? Is it better to do it later on in my helpers? I figure since I know what I need at the route level, all the subscription calls should be in the route code.
You can grab all 3 cursors inside Meteor.publish method and simply return them:
Meteor.publish('carThings', function(carId){
var carPaint = CarPaints.findOne({carId:carId});
return [
Cars.find({_id: carId}),
CarPaints.find({carId: carId}),
CarPaintTypes.find({carPaintId: carPaint._id});
]
})
On client:
this.route('car', {
path: '/car/:_id',
waitOn: function() {
return [Meteor.subscribe('carThings', this.params._id)]
}
}
With Kuba Wyrobek's help, I figured it out. For what I was trying to achieve, the publish looks like this:
Meteor.publish('carThings', function(carId){
var carPaints = CarPaints.find({carId: carId}).fetch();
return [
Cars.find({_id: carId}),
CarPaints.find({carId: carId}),
CarPaintTypes.find({carPaintId: {$in: _.pluck(carPaints, "_id")}})
];
});
I didn't get that you could do manipulations inside your publication blocks. This is super cool and flexible. Thanks for your help.
I'd like to create a method that returns the count of a generic collection.
Calling the method would look something like this:
Meteor.call('getCollectionCount', 'COLLECTION_NAME');
And the result would be the collection count.
The server method code would look something like this:
getCollectionCount: function (collectionName) {
return window[collectionName].find().count();
}
This won't work because window isn't defined on the server, but is something similar possible?
Use global instead of window.
Note that this uses the variable name assigned to the collection object, not the name given to the collection. For this to work with Meteor.users you need to assign another variable name.
if (Meteor.isServer) {
users = Meteor.users;
}
if (Meteor.isClient) {
Meteor.call('count', 'users', function (err, res) {
// do something with number of users
});
}
Also probably a good idea to check that global[collectionName] is actually a collection.
I came up with this code which makes the following assumptions :
collections are declared in the global scope as top level objects.
collections are searched by collection name, not the collection variable identifier.
So client code should declare their collections like this :
MyCollection=new Meteor.Collection("my-collection");
And use the function like this :
var clientResult=Meteor.call("getCollectionCount","my-collection",function(error,result){
if(error){
console.log(error);
return;
}
console.log("actual server-side count is : ",result);
});
console.log("published subset count is : ",clientResult);
The method supports execution on the client (this is known as method stub or method simulation) but will only yield the count of the collection subset replicated client-side, to get the real count wait for server-side response using a callback.
/packages/my-package/lib/my-package.js
getCollection=function(collectionName){
if(collectionName=="users"){
return Meteor.users;
}
var globalScope=Meteor.isClient?window:global;
for(var property in globalScope){
var object=globalScope[property];
if(object instanceof Meteor.Collection && object._name==collectionName){
return object;
}
}
throw Meteor.Error(500,"No collection named "+collectionName);
};
Meteor.methods({
getCollectionCount:function(collectionName){
return getCollection(collectionName).find().count();
}
});
As Meteor.users is not declared as a top level variable you have to account for the special case (yes, this is ugly).
Digging into Meteor's collection handling code could provide a better alternative (getting access to a collection handle by collection name).
Final words on this : using a method call to count a collection documents is unfortunately non-reactive, so given the Meteor paradigm this might be of little use.
Most of the time you will want to fetch the number of documents in a collection for pagination purpose (something like a "Load more" button in a posts list for example), and as the rest of the Meteor architecture you'll want this to be reactive.
To count documents in a collection reactively you'll have to setup a slightly more complicated publication as showcased in the "counts-by-room" example in the docs.
http://docs.meteor.com/#meteor_publish
This is something you definitely want to read and understand.
This smart package is actually doing it right :
http://atmospherejs.com/package/publish-counts
It provides a helper function that is publishing the counts of any cursor.
Keep track of the collections on some other property that the server has access too. You could even call it window if you really wanted to.
var wow = new Meteor.Collection("wow");
collections["wow"] = wow;
getCollectionCount: function (collectionName) {
return collections[collectionName].find().count();
}
If you don't want the package users to change how they work with collections in the app then I think you should use MongoInternals to get collections by name from the db. Not tested but here is an example:
//on server
Meteor.methods({
count: function( name ){
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var collection = db.collection( name );
return collection && collection.count({});
}
});
Another example of MongoInternals use is here. Documentation of the count() function available from the mongo driver is here.
I have made a collection
var Words = new Meteor.Collection("words");
and published it:
Meteor.publish("words", function() {
return Words.find();
});
so that I can access it on the client. Problem is, this collection is going to get very large and I just want to publish a transform of it. For example, let's say I want to publish a summary called "num words by length", which is an array of ints, where the index is the length of a word and the item is the number of words of that length. So
wordsByLength[5] = 12;
means that there are 12 words of length 5. In SQL terms, it's a simple GROUP BY/COUNT over the original data set. I'm trying to make a template on the client that will say something like
You have N words of length X
for each length. My question boils down to "I have my data in form A, and I want to publish a transformed version, B".
UPDATE You can transform a collection on the server like this:
Words = new Mongo.Collection("collection_name");
Meteor.publish("yourRecordSet", function() {
//Transform function
var transform = function(doc) {
doc.date = new Date();
return doc;
}
var self = this;
var observer = Words.find().observe({
added: function (document) {
self.added('collection_name', document._id, transform(document));
},
changed: function (newDocument, oldDocument) {
self.changed('collection_name', oldDocument._id, transform(newDocument));
},
removed: function (oldDocument) {
self.removed('collection_name', oldDocument._id);
}
});
self.onStop(function () {
observer.stop();
});
self.ready();
});
To wrap transformations mentioned in other answers, you could use the package I developed, meteor-middleware. It provides a nice pluggable API for this. So instead of just providing a transform, you can stack them one on another. This allows for code reuse, permissions checks (like removing or aggregating fields based on permissions), etc. So you could create a class which allows you to aggregate documents in the way you want.
But for your particular case you might want to look into MongoDB aggregation pipeline. If there is really a lot of words you probably do not want to transfer all of them from the MongoDB server to the Meteor server side. On the other hand, aggregation pipeline lacks the reactivity you might want to have. So that published documents change counts as words come in and go.
To address that you could use another package I developed, PeerDB. It allows you to specify triggers which would be reactively called as data changes, and stored in the database. Then you could simply use normal publishing to send counts to the client. The downside is that all users should be interested in the same collection. It works globally, not per user. But if you are interested in counts of words per whole collection, you could do something like (in CoffeesScript):
class WordCounts extends Document
#Meta
name: 'WordCounts'
class Words extends Document
#Meta
name: 'Words'
triggers: =>
countWords: #Trigger ['word'], (newDocument, oldDocument) ->
# Document has been removed.
if not newDocument._id
WordCounts.update
length: oldDocument.word.length
,
$inc:
count: -1
# Document has been added.
else if not oldDocument._id
WordCounts.update
length: newDocument.word.length
,
$inc:
count: 1
# Word length has changed.
else if newDocument.word.length isnt oldDocument.word.length
WordCounts.update
length: oldDocument.word.length
,
$inc:
count: -1
WordCounts.update
length: newDocument.word.length
,
$inc:
count: 1
And then you could simply publish WordCounts documents:
Meteor.publish 'counts', ->
WordCounts.documents.find()
You could assemble the counts by going through each document in Words, (cursor for each)
var countingCursor = Words.find({});
var wordCounts = {};
countingCursor.forEach(function (word) {
wordCounts[word.length].count += 1;
wordCounts[word.length].words = wordCounts[word.length].words || []
wordCounts[word.length].words.push(word);
});
create a local collection,
var counts = new Meteor.Collection('local-counts-collection', {connection: null});
and insert your answers
var key, value;
for (key in wordCounts) {
value = object[key];
counts.insert({
length: key,
count: value.count,
members: value.words
});
}
Counts is now a collection, just not stored in Mongo.
Not tested!