I have two collections: one for Posts and one for Categories. Posts have one or more categories. I'd like for the Posts collection to be updated if a category is deleted, so that posts do contain any non-existent categories. I've got this to work, but I'm not sure it is the most efficient way to do it.
This code is what fires when a category delete button is pressed. This both removes the category from the collection, and goes through each post that contains the category and updates the category array to exclude the deleted category.
Template.listCategories.events({
'click .delete-category': function(e){
e.preventDefault();
var category = this.name;
Categories.remove(this._id);
var posts = Posts.find({categories: category}).fetch();
for (var i=0;i<posts.length;i++){
var cats = _.without(posts[i].categories, category);
Posts.update(posts[i]._id, {$set: {categories: cats}});
}
}
});
First I set the a 'category' variable to equal the name of the category being deleted.
Second, I actually remove the category from the collection.
Lastly, I set a 'posts' variable equal to a fetch of all posts that contain the category name, which returns an object of posts. I iterate through the posts, and, with the help of underscore, I use the '_.without' function to return an array of categories that excludes the deleted category. I then call Posts.update to update the categories with the new array.
My concern, though, is that I'm calling Posts.update each time in the For loop. It's a client side call, so maybe that doesn't matter as much? I still feel like there is a better way to do this. Any help?
I think you are looking for the $pull operator (docs):
The $pull operator removes all instances of a value from an existing
array.
With this your code can be simplified to this:
Template.listCategories.events({
'click .delete-category': function(e){
e.preventDefault();
var category = this.name;
Categories.remove(this._id);
Posts.update({categories: category}, {$pull: {categories: category}}, {multi:true});
}
});
NB:
It's a client side call, so maybe that doesn't matter as much?
Your assertion is wrong, Posts.update is not just a client-side call. Actually, a stub that using minimongo simulates the effect of the update operation is executed on the client while in parallel the update is also remotely executed on the server.
Related
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'm in the process of learning meteor. I followed the tutorial to create microscope. If some one submits a post meteor will re render the template for all users. This could be very annoying if there are hundreds of posts then the user will come back to the top of the page and loose track of where he was. I want to implement something similar to what facebook has. When a new post is submitted template isn't rendered rather, a button or link will appear. Clicking it will cause the template to re-render and show the new posts.
I was thinking of using observeChanges on the collection to detect any changes and it does stop the page from showing new posts but only way to show them is to reload the page.
Meteor.publish('posts', function(options) {
var self = this, postHandle = null;
var initializing = true;
postHandle = Posts.find({}, options).observeChanges({
added: function(id, post) {
if (initializing){
self.added('posts', id, post);
}
},
changed: function(id, fields) {
self.changed('posts', id, fields);
}
});
self.ready();
initializing = false;
self.onStop(function() { postHandle.stop(); });
});
Is this the right path to take? If yes, how do I alert the user of new posts? Else, what would be a better way to implement this?
Thank you
This is a tricky question but also valuable as it pertains to a design pattern that is applicable in many instances. One of the key aspects is wanting to know that there is new data but not wanting to show it (yet) to the user. We can also assume that when the user does want to see the data, they probably don't want to wait for it to be loaded into the client (just like Facebook). This means that the client still needs to cache the data as it arrives, just not display it immediately.
Therefore, you probably don't want to restrict the data displayed in the publication - because this won't send the data to the client. Rather, you want to send all the (relevant) data to the client and cache it there until it is ready.
The easiest way involves having a timestamp in your data to work from. You can then couple this with a Reactive Variable to only add new documents to your displayed set when that Reactive Variable changes. Something like this (code will probably be in different files):
// Within the template where you want to show your data
Template.myTemplate.onCreated(function() {
var self = this;
var options = null; // Define non-time options
// Subscribe to the data so everything is loaded into the client
// Include relevant options to limit data but exclude timestamps
self.subscribe("posts", options);
// Create and initialise a reactive variable with the current date
self.loadedTime = new ReactiveVar(new Date());
// Create a reactive variable to see when new data is available
// Create an autorun for whenever the subscription changes ready() state
// Ignore the first run as ready() should be false
// Subsequent false values indicate new data is arriving
self.newData = new ReactiveVar(false);
self.autorun(function(computation) {
if(!computation.firstRun) {
if(!self.subscriptionsReady()) {
self.newData.set(true);
}
}
});
});
// Fetch the relevant data from that subscribed (cached) within the client
// Assume this will be within the template helper
// Use the value (get()) of the Reactive Variable
Template.myTemplate.helpers({
displayedPosts = function() {
return Posts.find({timestamp: {$lt: Template.instance().loadedTime.get()}});
},
// Second helper to determine whether or not new data is available
// Can be used in the template to notify the user
newData = function() {
return Template.instance().newData.get();
});
// Update the Reactive Variable to the current time
// Assume this takes place within the template helper
// Assume you have button (or similar) with a "reload" class
Template.myTemplate.events({
'click .reLoad' = function(event, template) {
template.loadedTime.set(new Date());
}
});
I think this is the simplest pattern to cover all of the points you raise. It gets more complicated if you don't have a timestamp, you have multiple subscriptions (then need to use the subscription handles) etc. Hope this helps!
As Duncan said in his answer, ReactiveVar is the way to go. I've actually implemented a simple facebook feed page with meteor where I display the public posts from a certain page. I use infinite scroll to keep adding posts to the bottom of the page and store them in a ReactiveVar. Check the sources on github here and the live demo here. Hope it helps!
I have a collection called "Articles". Each article has a category. I would like to have a global variable being an array with each distinct category value in my Articles collection.
I tried to do it this way:
/models/article.coffee:
#Articles = new Meteor.Collection "articles"
Articles.categories = ->
Meteor.call "articleCategories", (e, r) ->
unless e
return r
/server/article_server.coffee:
Meteor.methods
articleCategories: ->
categories = _.uniq(Articles.find({}, {sort: {category: 1}, fields:
{category: true}}).fetch().map (x) ->
x.category
, true)
return categories
This doesn't work. The result is "undefined" when I call Articles.categories() from the console.
What am I doing wrong?
EDIT:
I want to do this because I want my article categories to be available everywhere in the website.
As Articles collection will not be published on every pages, I tought, I could just generate an array server side and pass it over to the client.
But maybe it's not a good idea...
A Meteor.method will always return undefined on the client (unless a simulation/stub exists and it's called within another parent method) so this behavior is expected.
I'm not sure why you'd need a Meteor.method in this particular use case though, can't you just copy your method code inside your class method ?
EDIT :
To accomplish what you want to do, I'd suggest changing your model to create a Categories collection filled with every possible categories and just publish the entire content to the client.
Then just use a foreign key in your Articles collection.
An added benefit will be that your categories access client side will be reactive, contrary to using a Meteor.method.
Whether it's Telescope or even Wordpress I think this schema is very popular.
Take a look at this package:
https://github.com/dburles/meteor-collection-helpers
And add something like this (I wrote in javascript) in your model:
Articles.helpers({
categories: function(){
return _.uniq(
_.pluck(Articles.find({}, {sort: {category: 1}, fields:
{category: true}}).fetch(), 'category'),
true
);
}
});
I have an app where you can choose (or add if they don't exist!) a superhero/villain character from a certain universe on the first page; then outfit him with weapons, clothes, and gadgets on the second page (build).
I have this route defined:
Router.route('/build/:character', {
name: 'build'
waitOn: Meteor.subscribe('characters', {name: this.params.character})
//and a few other subscriptions and sessions as well for the items
//and stuff, but those don't matter here.
}
The link from the specific character, though, passes along a query as well:
<a href="{{pathFor 'build' query=this.universe}}">
So the final link could look something like this:
/build/Aquaman?DCComics
Now the page you are on will display a list of weapons and gadgets where you could also add other stuff if you so wish. Then you are supposed to drag the items you want to include onto your version of this hero.
Problem is, at this point the app doesn't know you even want to create your own hero. Maybe the user is just looking through them for fun. There's a button that the user has to click first to initialize the creating process, and that's when the actual _id is created, something like this:
Meteor.methods({
buildHero: function(heroCharacterName, heroUniverse) {
var heroToAdd = {}
heroToAdd['characterName'] = heroCharacterName
heroToAdd['universe'] = heroUniverse
heroToAdd['_createdAt'] = new Date()
CreatedHeroes.insert(heroToAdd, function() {
if (! error)
//Update the subscription somehow...
})
}
})
So, the _id that is created here in the new Collection must be passed along to a subscription somehow, because I don't want the user to see other personal heroes that have been created, only his own newly created one.
The solution I have in mind is adding the _id onto the URL in form of a hastag, and use this.params.hash in the subscription like so:
Router.route('/build/:character', {
name: 'build'
waitOn: [Meteor.subscribe('characters', {name: this.params.character}),
Meteor.subscribe('createdheroes', this.params.hash)]
}
First of all, is this a valid approach? If so, how do I accomplish it; how do I actually update the URL to include this hash?
If not, what would be a better approach?
I think you have to handle this logic in the data context or in a template helper and not in the way of subscribing/publishing.
If I was you I would besure that the newly created item is being published and subscribed by the client and modify your search query just that it only adds the newly created item.
I am not sure if I understand your question well but what I got, you will know the last _id which was used on your insert.
Instead of letting done this automatically by meteor, just use the meteor method to create / get that _id value >> see Meteor Documentation
var new_id = new Mongo.ObjectID()
col1.insert({ _id: new_id, ... });
col2.insert({ ..., ref_col1_id: new_id, ... });
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.