In meteor, is there a way to run something before every publish. Something like (pseudo) Metoer.onBeforePublish?
A use case would be to add a protection layer to all the publish functions, for example:
if (!this.userId) {
return this.ready()
}
Thanks to MasterAM comment, here is my implementation:
pub = function(name) {
var cb = Array.prototype.pop.call(arguments)
var args = Array.prototype.slice(arguments, 1, -1)
if(Meteor.isServer) {
Meteor.publish(name, function(args){
if (!this.userId) {
return this.ready()
}
})
cb(args)
}
}
and call it like so:
pub('taxes', query, options, function(){
Taxes.find(query, options)
})
Thanks!
Related
In meteor framework inside pre-added code, the counter increases every time when it gets clicked. How to save the value using mongodb ?
Create a collection on the server side to persist the data:
Meteor.isServer {
Counter= new Mongo.Collection('Counter');
// Server side method to be called from client
Meteor.methods({
'updateCounter': function (id) {
if(typeof id && id) {
return Counter.update({_id: id}, {$set: {counter: {$inc: 1}}});
} else {
return Counter.insert({counter: 1})
}
}
})
// Publication
Meteor.publish("counter", function () {
Counter.find();
})
}
You can subscribe the data at the client:
Meteor.isClient{
Template.yourTemplateName.created = function () {
Meteor.subscribe('counter');
}
Template.yourTemplateName.heplers( function () {
counter: function () {
return Counter.findOne();
}
})
Template.yourTemplateName.event( function () {
'click #counterButtonIdName': function () {
if(Counter.findOne()) {
Meteor.call('updateCounter', Counter.findOne()._id);
} else {
Meteor.call('updateCounter', null);
}
}
})
}
Html sample
<template name="yourTemplateName">
<span>{{counter}}</span> //area where count is written
</template>
By this way you can achieve a secure server side processing of your data and the count will be persistent until you have data in the database. Also, this way you can learn Meteor's basics.
Just insert it to a collection. Here's an upsert (i.e., update if exists, insert if not) function:
if (Saves.find({_id: Meteor.userId()})){
Saves.update( {_id: Meteor.userId()}, {save: save} )
console.log("Updated saves")
}
else {
Saves.insert(save)
}
If the autopublish package exists, you can simply create a Mongo.Collection and insert this counter into the database:
var myCounter = 5;
var collection = new Mongo.Collection('collection');
collection.insert({counter: myCounter});
Hope this helps.
I dynamically create collections with this method:
createPlist: function(jid) {
try {
Plist[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log("oops, I did it again");
}
Plist[jid].insert({
...,
...,
public:true,
uid:this.userId
});
}
Then I am wanting to publish these selectively, and I am attempting to do it via a method:
getPlist: function(jid,pid) {
// var future = new Future();
try {
Plist[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log("oops, I did it again");
}
Meteor.publish(pid, function() {
console.log(Plist[jid].find({}));
// future["return"](Plist[jid].find({}));
return Plist[jid].find();
});
// return future.wait();
},
This returns 'undefined' to my Template helper, and returns nothing (i.e. waits forever) using Future.
Any user can log in and create a Plist collection, which can be either public or not. A user can also subscribe to any collection where public is true. The variable jid is passed to the method 'getPlist' from the template. It is stored in the user's Session.
Thanks! I hope I have explained it well enough!
And of course the template:
Template.plist.helpers({
getPlist: function() {
Pl = []
jid = Session.get('jid');
//alert(jid);
pid = "pl_"+jid;
// console.log(pid);
Meteor.call('getPlist', jid, pid, function(err,res) {
console.log(res); //returns undefined
try {
Pl[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log(e);
}
Meteor.subscribe(pid);
// return Pl[jid].find({}).fetch();
});
}
Router.route('/courses/:_catalog', function () {
var courseCatalog = this.params._catalog.toUpperCase();
Meteor.subscribe("courseCatalog", courseCatalog);
this.render('CourseDetail', {
to: 'content',
data: function () {
return Courses.findOne({catalog: courseCatalog});
}
});
}, {
onAfterAction: function() {
if (!Meteor.isClient) {
return;
}
debugger
var course = this.data(); <======
SEO.set({
title: "course.catalog"
});
}
});
In the above code, please look at the debugger statement. I want to access the data but it seems I am doing something wrong because this.data doesn't exist. I also tried Courses.find().fetch() but I only get an empty array inside onAfterAction. What's the right syntax and what am I missing?
It needs to be inside a this.ready() block:
onAfterAction: function() {
if (this.ready()) {
var course = this.data();
...
}
}
You need to subscribe to data first. Have a look at the waitOn function to do this. The server only sends the documents you subscribed to, and since you didn't subscribe, Courses.find().fetch() returns an empty array.
Also, don't put SEO stuff in onAfterAction. Put it in onRun which is guaranteed to only run once.
I am using keystone#0.2.32. I would like to change the post category to a tree structure. The below code is running well except when I create a category, it goes into a deadlock:
var keystone = require('keystone'),
Types = keystone.Field.Types;
/**
* PostCategory Model
* ==================
*/
var PostCategory = new keystone.List('PostCategory', {
autokey: { from: 'name', path: 'key', unique: true }
});
PostCategory.add({
name: { type: String, required: true },
parent: { type: Types.Relationship, ref: 'PostCategory' },
parentTree: { type: Types.Relationship, ref: 'PostCategory', many: true }
});
PostCategory.relationship({ ref: 'Post', path: 'categories' });
PostCategory.scanTree = function(item, obj, done) {
if(item.parent){
PostCategory.model.find().where('_id', item.parent).exec(function(err, cats) {
if(cats.length){
obj.parentTree.push(cats[0]);
PostCategory.scanTree(cats[0], obj, done);
}
});
}else{
done();
}
}
PostCategory.schema.pre('save', true, function (next, done) { //Parallel middleware, waiting done to be call
if (this.isModified('parent')) {
this.parentTree = [];
if(this.parent != null){
this.parentTree.push(this.parent);
PostCategory.scanTree(this, this, done);
}else
process.nextTick(done);
}else
process.nextTick(done); //here is deadlock.
next();
});
PostCategory.defaultColumns = 'name, parentTree';
PostCategory.register();
Thanks so much.
As I explained on the issue you logged on Keystone here: https://github.com/keystonejs/keystone/issues/759
This appears to be a reproducible bug in mongoose that prevents middleware from resolving when:
Parallel middleware runs that executes a query, followed by
Serial middleware runs that executes a query
Changing Keystone's autokey middleware to run in parallel mode may cause bugs in other use cases, so cannot be done. The answer is to implement your parentTree middleware in serial mode instead of parallel mode.
Also, some other things I noticed:
There is a bug in your middleware, where the first parent is added to the array twice.
The scanTree method would be better implemented as a method on the schama
You can use the findById method for a simpler parent query
The schema method looks like this:
PostCategory.schema.methods.addParents = function(target, done) {
if (this.parent) {
PostCategory.model.findById(this.parent, function(err, parent) {
if (parent) {
target.parentTree.push(parent.id);
parent.addParents(target, done);
}
});
} else {
done();
}
}
And the fixed middleware looks like this:
PostCategory.schema.pre('save', function(done) {
if (this.isModified('parent')) {
this.parentTree = [];
if (this.parent != null) {
PostCategory.scanTree(this, this, done);
} else {
process.nextTick(done);
}
} else {
process.nextTick(done);
}
});
I think it's a bug of keystone.js. I have changed schemaPlugins.js 104 line
from
this.schema.pre('save', function(next) {
to
this.schema.pre('save', true, function(next, done) {
and change from line 124 to the following,
// if has a value and is unmodified or fixed, don't update it
if ((!modified || autokey.fixed) && this.get(autokey.path)) {
process.nextTick(done);
return next();
}
var newKey = utils.slug(values.join(' ')) || this.id;
if (autokey.unique) {
r = getUniqueKey(this, newKey, done);
next();
return r;
} else {
this.set(autokey.path, newKey);
process.nextTick(done);
return next();
}
It works.
In my Meteor application I have this situation in which I have a 'Settings' collection only on the client. So the publish function is:
Meteor.publish('settings', function (option) {
this.added("settings", "settings", {
bar: true,
foo: { .... }
});
this.ready();
});
Initially I subscribe like:
waintOn: function () {
return subs.subscribe('settings')
}
But when the route changes I subscribe again like
return subs.subscribe('settings', 10);
After this I see that the publish function runs, but on the client nothing happens.
For some reason the server thinks that the data did not change and decides to do nothing. So the question is how can I tell Meteor that the data has changed so it sends the data to the client. If the problem is completely different, I'm also very interested!!
UPDATE: the publish function might look like this:
Meteor.publish('settings', function (option) {
var list;
if (option === 10) {
list = [1,2,3,4,5];
}
this.added("settings", "settings", {
bar: true,
foo: list
});
this.ready();
});
UPDATE2: Expected solution:
var isNew = true;
Meteor.publish('settings', function () {
if(isNew) {
this.added("settings", "settings", {
bar: true,
foo: list
});
isNew = false;
}
else {
this.changed('settings', 'settings', {.....});
}
this.ready();
});
It would even be better if there was a function like this.exists('settings', 'settings') because the isNew variable feels a bit like a hack!
Anyway, I have it working now as follows:
try {
this.removed('settings', 'settings');
} catch(e){}
this.added('settings', 'settings', {...});
...