I am working on an admin page that shows a list of the user's orders. For some reason, publication function is not receiving the user's ID as a parameter from my subscription function.
My user object contains the attributes:
_id
userId
confirmed
My orders object contains the attributes:
_id
userId
amount
date
My publication:
Meteor.publish('clientOrders', function(userId) {
console.log('user is ' + userId);
return Order.find({_id: userId});
});
My subscription:
viewClientOrders = RouteController.extend({
loadingTemplate: 'loading',
waitOn: function () {
Meteor.subscribe('clientOrders', this.params._id);
},
action: function() {
this.render('viewClientOrders');
}
});
My route:
Router.route('/viewClientOrders/:id', {name: 'viewClientOrders', controller: 'viewClientOrders', onBeforeAction: requireLogin});
I did a console.log for the userId and it returns null, although I clearly passed a userId parameter (this.params._id) in my subscription function. However, if I pass a parameter of 3, for testing purposes, it will work. Also, I used a similar publication/subscription method to view a client's profile, passing in the exact same parameters and it works fine...
Anyone know what's going on? Thanks!
Make sure you return in your waitOn. Iron Router will only proceed to render if it knows the subscription is ready:
viewClientOrders = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('clientOrders', this.params._id);
}
...
Make sure you've also set a loadingTemplate (docs).
Related
I have a sample code that goes like this:
Client Helper:
getUsername: function (userId) {
Meteor.call("getUsername", userId, function (err, result) {
if(!err) {
Session.set("setUsername", result);
else {
console.log(err);
}
});
return Session.get("setUsername");
}
Server
Meteor.methods({
"getUsername": function (userId) {
var x = Meteor.users.find({_id: userId}, {fields: {username:1}}).fetch()[0];
return x.username;
}
});
The result of this code is an infinite loop of username passing to the client. Is there a way to stop the loop and pass only the data that is needed on the client? I believe the reactivity is causing the data to loop infinitely and I am not sure how to stop it. I tried using "reactive":false on my query in the server but it does not work.
If you want to access username everywhere in client templates (so thats why you put it into session), I would not set it in template helper. I would set it on startup and get username from session in template helpers (without calling server method)
If you need username just in one template, so you want to return its value from your template helper, do not put it into session, just return it in your server method callback.
Based on your sample code, I assume, you have a set of posts and you are retrieving user name based on user id for each post. Then instead of doing it this way, you should use publish composite package to publish related users as well.
Meteor.publishComposite('getPosts', function (postIds) {
return [{
find: function() {
return Posts.find({ _id: { $in: postIds }});
// you can also do -> return Posts.find();
// or -> return Posts.find({ /* or what ever your selector is to get the posts you need*/ });
},
children: [{
find: function(post) {
return Meteor.users.find({
id: post.userId //or the correct field in your post document to get user id
}, {
fields: {
"profile": 1
}
});
}
}}
}]
});
This way your publication will take care of publishing related users along with posts. You don't need to use methods and call them each time.
I am trying to do the following when visiting reviews/show (/reviews/:id):
Load two models from the server: One review and one user.
I only have access to the review's id, so I need to load it first, to get the userId
And then, when that has finished loading and I now have the userId, query the user using the userId attribute from the review
Finally, return both of these in a hash so I can use them both in the template
So two synchronous database queries, and then return them both at once in the model hook of the route.
I don't mind if it's slow, it's fast enough for now and I need it to work now.
This is what I've tried and it doesn't work:
reviews/show.js
export default Ember.Route.extend({
model: function(params) {
var user;
var review = this.store.findRecord('review', params.id).then(
function(result) {
user = this.store.findRecord('user', result.get('userId'));
}
);
return Ember.RSVP.hash({
review: review,
user: user
});
}
});
You can do this:
export default Ember.Route.extend({
model: function(params) {
var reviewPromise = this.store.findRecord('review', params.id);
return Ember.RSVP.hash({
review: reviewPromise,
user: reviewPromise.then(review => {
return this.store.findRecord('user', review.get('userId'));
})
});
}
});
The reason why user is undefined is because it hasn't been assigned in your first promise until the review is resolved, but Ember.RSVP.hash has received user as undefined.
Im new to meteor and have been trying to learn the framework via the discover meteor book. Im having a few issue understanding what exactly is going on in my application (found here https://github.com/Themitchell/Sound-wav.es).
Essentially, my understanding is that on my server side I allow publications for certain collections which take arguments from my client side subscribe calls. For this part on my server i have this in my server/publications.js file:
Meteor.publish('studios', function (options) {
return Studios.find({
userId: this.userId
}, options);
});
Meteor.publish('studio', function (id) {
return id && Studios.find({
userId: this.userId,
_id: id
});
});
Next we would need a controller to handle the routing and deal with waiting for any subscriptions required, then, once the subscriptions are ready (hence the waitOn) go and render the template providing the data function as a reactive data source for the templates.
So I then set up my 2 routes for indexing studios and showing one studio using iron router and 'Controllers' as follows:
StudiosIndexController = RouteController.extend({
template: 'studiosIndex',
increment: 20,
limit: function () {
return parseInt(this.params.studiosLimit) || this.increment;
},
findOptions: function () {
return {
sort: this.sort,
limit: this.limit()
};
},
waitOn: function () {
return Meteor.subscribe('studios', this.findOptions());
},
studios: function () {
return Studios.find({}, this.findOptions());
},
data: function () {
return {
studios: this.studios()
};
}
});
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
studio: function () {
return Studios.findOne(this.params._id);
},
data: function () {
return {
studio: this.studio()
};
}
});
Router.map(function () {
this.route('studiosIndex', {
path: '/',
controller: StudiosIndexController
});
this.route('showStudio', {
path: '/studios/:_id',
controller: ShowStudioController
});
});
Now this is great and works fine when displaying my index page. I get a list of collections which is reactive and the as i introduce new studios to the collection i see them get created on the server and on the client respectively. However in my show view when the view is rendered the data always seems to be empty. On dropping into my show controller's data function with a debugger I tried querying the Studios and this always returns undefined even when i try to fetch everything possible. Oddly I know that my publications are logging the correct id for a studio (using console.log). It seems that i get all the correct data up until the routing on the client side. The parameter ids are all correct but a find() call on studios always returns nothing. Am I missing something obvious here.
Its also worth noting i deleted my helpers for 'studios' and 'studio' in views/studios/index.js and views/studios/show.js respectively as my understanding is that this is what im doing with studios and studio in the controller. These helpers are now defined at the controller level and passsed to the reactive 'data' function. IS this correct?
TL;DR
With the code above my index action works however my show action fails where the data function always returns undefined and in fact on the show page i cannot get access to any of my studio information (Studios.find({}).count() always returns 0). I'm unsure how the 2 routes differ. Can anyone spot the issue?
Edit: Its also worth noting having looked through some of the iron router issues on github i have tried using this.ready(). The first time the route is run i hit data and this is false but then even wrapping my data helpers to wait for this.ready() gets an undefined return value when this.ready() returns true.
Extra Notes
Im running meteor 0.8.0 with latest 0.7.0 release of iron router and collection2 with simple schema, just in case you are interested / require this info.
EDIT: Possible solution
So having fiddled around it seems like my helpers are the issue. By using the 'studio' section and then calling this.studio() inside my data function this seems to break. If I change the code below:
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
studio: function () {
return Studios.findOne(this.params._id);
},
data: function () {
return {
studio: this.studio()
};
}
});
to this
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
data: function () {
return Studios.findOne(this.params._id);
}
});
My view renders correctly again. Im unsure where i saw this pattern however I had assumed the function assigned to 'studio' was essentially the same as doing
Template.showStudio.helpers({
studio: function () {
return Studios.findOne(this.params._id)
}
});
but it seems not!
So I have successfully implemented Ember-Auth using a pure token based approach. I would like to redirect my user to the root of my app once they sign in.
I know I can use actionRedirectable (http://ember-auth.herokuapp.com/docs in the docs) but since I am using a pure token approach and not storing anything in cookies I am effectively signing my user in again every time the page refreshes using a remember_token (which seems unideal but I'll work it out shortly). This means that using actionRedireactable would mean that I would be redirecting every time the user refreshes the page. Perhaps there is an anti-pattern in there somewhere?
Anyway here is my SignInView:
App.SignInView = Ember.View.extend({
templateName: 'auth/sign_in',
email: null,
password: null,
submit: function(event, view) {
event.preventDefault();
event.stopPropagation();
App.Auth.signIn({
data: {
email: this.get('email'),
password: this.get('password')
}
});
}
});
If I call this.get("controller").transitionToRoute('...') directly after the signIn call then my user invariably isn't signed in by this point so they get redirected to the login page again. And if I try:
App.Auth.on('signInSuccess', function() {
// ...
});
then I don't have any sensible way to access the router to do a transition. Any bright ideas would be greatly appreciated. Thanks!
As a best practice you should not have logic in your view, logic is much better suited to live in controllers, so for your use case, create a App.SignInController an instrument there your authentication process:
View
App.SignInView = Ember.View.extend({
templateName: 'auth/sign_in',
email: null,
password: null,
submit: function(event, view) {
event.preventDefault();
event.stopPropagation();
var data = {
email: this.get('email'),
password: this.get('password')
}
// forward the action to your controller passing along the
// data object your sign in process needs
this.get("controller").send("signIn", data);
}
});
Furthermore, you should not transitionTo from elsewhere other than inside the router. By doing so, you could run into serious issues because you don't know in which state your router actually is. So the best thing to do is to get a reference to your router and call the transitionTo on the router instead:
Controller
App.SignInController = Ember.ObjectController.extend({
signIn: function(data) {
// grab your passed data object and issues you sign in
App.Auth.signIn({
data: data
});
// subscribe to the `signInSuccess` event and
// then transition to your route but using
// the router itself
App.Auth.one('signInSuccess', function() {
var router = this.get('target.router');
router.transitionTo('route_name');
});
}
});
Hope this helps.
I don't have tested, but I think that this works:
App.SignInView = Ember.View.extend({
templateName: 'auth/sign_in',
email: null,
password: null,
submit: function(event, view) {
event.preventDefault();
event.stopPropagation();
var controller = this.get('controller');
App.Auth.signIn({
data: {
email: this.get('email'),
password: this.get('password')
}
});
App.Auth.one('signInSuccess', function() {
controller.transitionToRoute('...');
});
}
});
I'm using Backbone.js to route profile views so I can view data belonging to /user, and that part works fine. I'm able to generate an _id based on the username and pass it into the server publish function, which logs it. However, when I log the results back to the client in the subscribe function, my result looks like this:
Object {stop: function, ready: function}
//Client Side
Template.userquery.userproject = function() {
var query = Session.get('userquery');
var user = Meteor.users.findOne({username: query});
if (user) {
console.log(user._id); //(works)
campaigns = Meteor.subscribe('userquery', user._id, function() {
console.log('ready'); //(works)
});
console.log(campaigns); //(returns Object {stop: function, ready: function})
return campaigns;
}
}
//Server Side
Meteor.publish('userquery', function(userid) {
console.log('break');
console.log(userid); //(I get userid in Terminal)
var campaigns = Campaigns.find({owner: userid}, {fields: {owner: 1, name: 1}});
if (campaigns) {
console.log(campaigns);
return campaigns;
}
});
Am I missing something in this function? I have autopublish turned off because it was generating my search twice.
Meteor.subscribe, according to the docs, "Returns a handle that provides stop() and ready() methods." So the behaviour you're seeing is intended.