How to publish data based on URL in Meteor with Flow Router - meteor

I'm trying to publish data specific to the author of a document in my Jobs collection. My route is setup specifically to each unique author, which I then get via FlowRouter.getParam, but it still does not produce any data. I am subscribed to the 'refiJobs' publication but I'm still struggling. Thanks for reading - help is much appreciated!
My Publication
Meteor.publish('refiJobs', function () {
if (Roles.userIsInRole(this.userId, 'admin')) {
var author = FlowRouter.getParam('author');
return Jobs.find({author: author});
} else {
this.error(new Meteor.Error(403, "Access Denied"));
}
});
My route:
authenticatedRoutes.route( '/admin/:author', {
action: function() {
BlazeLayout.render( 'default', { yield: 'user' } );
}
});

The route parameters are not directly available on the server where you are creating your publication. You need to pass your route parameter through to your publication via your subscription as follows:
Client:
Meteor.subscribe('refiJobs',FlowRouter.getParam('author'));
Server:
Meteor.publish('refiJobs',(author)=>{
check(author,String); // be sure to check the parameter(s) to your publication
if (Roles.userIsInRole(this.userId, 'admin')) {
return Jobs.find({author: author});
} else {
this.error(new Meteor.Error(403, "Access Denied"));
}
});

Related

Tracker autorun using findone

I have this piece of code in client side:
Tracker.autorun(function () {
if (params && params._id) {
const dept = Department.findOne({ _id: params._id }) || Department.findOne({ name: params._id });
if (dept) {
}
}
});
params will be passed into the url. So, initially we won't have the department data and the findOne method will return null, and then later on, when data arrives, we can find the department object.
But if user enters an invalid id, we need to return them 404. Using tracker autorun, how can I distinguish between 2 cases:
a. Data is not there yet, so findOne returns null
b. There is no such data, even in server's mongodb, so findOne will also returns null.
For case a, tracker autorun will work fine, but for case b, I need to know to return 404
I would suggest you to subscribe to data inside template, like below so you know when subscriptions are ready, then you can check data exists or not
Template.myTemplate.onCreated(function onCreated() {
const self = this;
const id = FlowRouter.getParam('_id');
self.subscribe('department', id);
});
Template.myTemplate.onRendered(function onRendered() {
const self = this;
// this will run after subscribe completes sending records to client
if (self.subscriptionsReady()) {
const id = FlowRouter.getParam('_id');
const dept = Department.findOne({ _id: params._id }) || Department.findOne({ name: params._id });
if (dept) {
// found data in db
} else {
// 404 - no department found in db
}
}
});
If you are using Iron-Router, you may try this hack.
Router.route('/stores', function() {
this.render('stores', {});
}, {
waitOn: function() {
return [
Meteor.subscribe('stores_db')
];
}
});
The sample code above will wait for the subscription "stores_db" to complete, before rendering anyhing. Then you can use your findOne logic no problems, ensuring that all documents are availble. This suits your situation.
This is what I used to do before I completely understand MeteorJS publications and subscriptions. I do not recommend my solution, it is very bad to user experience. Users will see the page loading forever while the documents are being download. #Sasikanth gave the correct implementation.

access data from iron-router in rendered function

I'm trying to access data passed from iron router in the javascript function
router.js
this.route('editOrganization', {
path: '/editOrganization',
waitOn: function() {
return [
Meteor.subscribe('organization', this.userId)
];
},
data: function() {
return Organizations.findOne();
}
});
now if I wanted to access a property of organization in html (editCompany.html) I can do the following
{{name}}
but how do I access that same property in the js file
Template.editOrganization.rendered = function() {
//how do I access name?
}
UPDATE:
so if I click a link to edit organization I can get the value via
this.data.name
However, if I reload the page (same url) it throws an error saying data is null.
It is accessible through the rendered function context.
Template.editOrganization.rendered = function() {
var name = this.data && this.data.name;
};
This is confusing for many people but you need to configure the router to actually wait for the subscriptions you returned with waitOn.
Router.onBeforeAction('loading')
You can read the author's explanation here:
https://github.com/EventedMind/iron-router/issues/554#issuecomment-39002306

meteor subscription manager change collection name

I'm using subscriptions manager with iron-router and my problem is this one.
I have a collection "participants" with 2 publications: allParticipants and todayParticipants.
if I go to this page:
Router.map(function () {
this.route('winners', {
waitOn: function () {
return [subs.subscribe('allWinners'),
subs.subscribe('allParticipants')];
console.log("subscribed!");
},
data: function () {
return {
winners: Winners.find(),
participants: Participants.find(),
loginBox: "True"
}
}
});
AllParticipants publication is subscribed and put in cache by the subscription manager package.
If after this, I go to this page:
Router.map(function () {
this.route('participants', {
path: '/',
waitOn: function () {
return subs.subscribe('todayParticipants');
},
data: function () {
return {
participants: Participants.find()
}
}
});
I'm expecting to subscribe only the todayParticipants but as my subscription is automatically named "Participants", It uses the cached subscription from the previous page being allParticipants.
Is there a way to change the name of my subscriptions in order to have each of them in the right cache?
Thanks.
What I do in my waitOn function is first stop my subscriptions like
if (App.subs) {
for (name in App.subs) {
App.subs[name].stop();
}
}
And then I create new subscriptions
App.subs = {
settings: Meteor.subscribe('settings', project),
...
};
return [App.sub.settings, .....];
Hope this helps!
Today, there seems to be no solution to this problem.
More explanation here: https://github.com/meteorhacks/subs-manager/issues/11
What I'm doing now is using a very limited number of subscriptions (filtered mainly on user) and then I create as much data objects as I want filtering my subscriptions in different ways.

Create a method not limited by subscriptions

I've created a method that checks if an email already has an account:
insertGroupMember: function(eventId, memberDetails) {
var memberAccount = Meteor.users.findOne({'emails.address': memberDetails.email});
if (memberAccount) {
console.log('Existing User')
} else {
console.log('Create User')
}
}
But will only receive a result when I am subscribed to a publication with all users.emails. How can I achieve the same results without having to publish everyone's email? I think thats kind of bad for security/privacy, right?
You are correct - you don't want to publish all of the users to the client just to accomplish this. The best solution is to create a method defined only on the server, and then call it from the client.
server/methods.js
Meteor.methods({
insertGroupMember: function(eventId, memberDetails) {
...
}
});
client/someTemplate.js
Meteor.call('insertGroupMember', eventId, memberDetails, function (err, result){
...
});

How to explicitly unsubscribe from a collection?

I have a MongoDB with a large "messages" collection; all messages belonging to a specific groupId. So have started with a publication like this:
Meteor.publish("messages", function(groupId) {
return Messages.find({
groupId: groupId
});
});
and a subscription like this:
Deps.autorun(function() {
return Meteor.subscribe("messages", Session.get("currentGroupId"));
});
This got me into trouble because initially currentGroupId is undefined but sill mongod would use up the CPU to find messages with groupId == null (although I know there are none).
Now, I tried to rewrite the publication as follows:
Meteor.publish("messages", function(groupId) {
if (groupId) {
return Messages.find({
groupId: groupId
});
} else {
return {}; // is this the way to return an empty publication!?
}
});
and/or to rewrite the subscription to:
Deps.autorun(function() {
if (Session.get("currentGroupId")) {
return Meteor.subscribe("messages", Session.get("currentGroupId"));
} else {
// can I put a Meteor.unsubscribe("messages") here!?
}
});
which both helps initially. But as soon as currentGroupId becomes undefined again (because the user navigates to a different page), mongod is still busy requerying the database for the last subscribed groupId. So how can I unsubscribe from a publication such that the mongod is stopped being queried?
According to the documentation it must be http://docs.meteor.com/#publish_stop
this.stop()
Call inside the publish function. Stops this client's subscription;
the onError callback is not invoked on the client.
So something like
Meteor.publish("messages", function(groupId) {
if (groupId) {
return Messages.find({
groupId: groupId
});
} else {
return this.stop();
}
});
And I guess on the client side you can just remove your if/else like in your first example
Deps.autorun(function() {
return Meteor.subscribe("messages", Session.get("currentGroupId"));
});
I found it more simple and straight-forward to call the .stop() function on the handler which is returned from the .subscribe() call:
let handler = Meteor.subscribe('items');
...
handler.stop();
Simply adding a condition to the publication:
Meteor.publish("messages", function(groupId) {
if (groupId) {
return Messages.find({
groupId: groupId
});
});
and keeping the subscription:
Deps.autorun(function() {
return Meteor.subscribe("messages", Session.get("currentGroupId"));
});
does the job.
There is no need to stop the publication explicitly. Eventually, the MongoDB is not queried anymore after finishing the currently running query and issuing yet another one (which seems to be queued somewhere in the system).
in your case, you should stop the autorun
there is an example in the documentation
Your autorun is actually called with a parameter that allows you to stop it:
Deps.autorun(function (c) {
if (! Session.equals("shouldAlert", true))
return;
c.stop();
alert("Oh no!");
});

Resources