Overlapping subscription does not publish all fields - meteor

When I have two subscriptions that limit the fields to publish it does not do a union between the two
This should publish only the avatar.
Meteor.publish('userStatisticsByYear', function(year) {
check(year, Number);
this.unblock();
var userStats = UserStatistics.find({year: year},{sort: {count: -1}, limit: 5});
var userIds = userStats.map(function(u) {
return u.userId;
});
return [
Meteor.users.find({_id: {$in: userIds}},{fields: {username: 1, "profile.avatar": 1}}),
ProfileImages.find({owner: {$in: userIds}}),
userStats
];
});
And this subscription publishes more details of the profile. Which is fine under some routes.
Meteor.publish('getUserProfile', function(userId) {
this.unblock();
if (!this.userId) {
this.ready();
return;
}
return [
Meteor.users.find({_id: userId}, {fields: {profile: 1}}),
ProfileImages.find({owner: userId})
]
});
The problem is that if I subscribe to both only "profile.avatar" is being published. But not the extra fields from "getUserProfile"
The output of the console:
Meteor.subscribe('userStatisticsByYear')
Object {subscriptionId: "aQFv4HkGDKx54gJLq"}
Meteor.users.findOne("KABEf7SzNCmQapXND").profile
Object {avatar: "NQokwm9bHgfrMtKLY"}
Meteor.subscribe('getUserProfile','KABEf7SzNCmQapXND')
Object {subscriptionId: "hemH2NF88vwd3AkHv"}
Meteor.users.findOne("KABEf7SzNCmQapXND").profile
Object {avatar: "NQokwm9bHgfrMtKLY"}

I have seen this before.
if you use Meteor.users.find({"_id":"KABEf7SzNCmQapXND"}).profile instead of findOne, I believe it should work.

Related

Publish users using id from a different collection

I'm trying to access the userIds stored in a collection and then use them to publish the details of all of the meteor.users. My publish function doesn't isn't return anything?
Meteor.publish('allUsersWithOffers', function () {
var user = Offers.find({}, {fields: {"UserId": 1}});
return Meteor.users.find({_id: user});
});
Give this a try:
Meteor.publish('allUsersWithOffers', function () {
var offers = Offers.find({}, { fields: { UserId: 1 } }).fetch();
var ids = _.pluck(offers, 'UserId');
// This is critical - you must limit the fields returned from
// the users collection! Update this as needed.
options = { fields: { username: 1, emails: 1 } };
return Meteor.users.find({ _id: { $in: ids } }, options);
});
find returns a cursor - you need to call fetch to actually get the documents.

Publish a virtual collection in meteor

I'm trying to publish a collection with 2 different names.
freeCourses contains courses without paid_url field.
premiumCourses contains all courses which id exist in userCourses collection.
userCourses collection :
{ user_id: "1", course_id: "1" }
Meteor.publish('freeCourses', function () {
this.added('freeCourses', Courses.find({}, {fields: {'Seasons.Episodes.paid_url': 0}}));
this.ready();
});
Meteor.publish('premiumCourses', function () {
//userPremiumCourses is array of course_ids
var userPremiumCourses = userCourses.find({'user_id': this.userId}, {fields: {course_id: 1, _id: 0}}).map(
function (doc) {
return doc.course_id;
}
);
this.added('premiumCourses', Courses.find({_id: {$in: userPremiumCourses}}));
this.ready();
});
if(Meteor.isClient){
Meteor.subscribe('freeCourses');
Meteor.subscribe('premiumCourses');
}
I want to get freeCourses and premiumCourses as two different collections on the client.
I've never seen this done before but if it was possible I believe you would need to define two collections that referred to the same underlying mongo collection:
freeCourses = new Mongo.collection('userCourses');
premiumCourses = new Mongo.collection('userCourses');
I just tested that and that fails.
A collection can have multiple publications each with its own query parameters and fields but it appears you want something more like a SQL view. That doesn't exist in Meteor afaik.
so I used publishVirtual function. thanks to #michel floyd
function publishVirtual(sub, name, cursor) {
var observer = cursor.observeChanges({
added : function(id, fields) { sub.added(name, id, fields) },
changed: function(id, fields) { sub.changed(name, id, fields) },
removed: function(id) { sub.remove(name, id) }
})
sub.onStop(function() {
observer.stop() // important. Otherwise, it keeps running forever
})
}
and added this into publish :
Meteor.publish('freeCourses', function () {
var cursor = Courses.find({}, {fields: {'Seasons.Episodes.paid_url': 0}});
publishVirtual(this, 'freeCourses', cursor);
this.ready();
});
Meteor.publish('premiumCourses', function () {
//userPremiumCourses contains array of course_ids
var userPremiumCourses = userCourses.find({'user_id': this.userId}, {fields: {course_id: 1, _id: 0}}).map(
function (doc) {
return doc.course_id;
}
);
var cursor = Courses.find({_id: {$in: userPremiumCourses}});
publishVirtual(this, 'premiumCourses', cursor);
this.ready();
});
and made two client-side collections for subscribe :
if (Meteor.isClient) {
freeCourses = new Mongo.Collection("freeCourses");
premiumCourses= new Mongo.Collection("premiumCourses");
Meteor.subscribe('freeCourses');
Meteor.subscribe('premiumCourses');
}

Subcribe gets more records than server publishes

I am trying to publish all admin users from server
on server something like this:
Meteor.publish("users_with_roles", function (options, role) {
//{fields: {emails: 1, profile: 1}}
Counts.publish(this, 'numberOfUsers', Meteor.users.find({$and:[
{'roles.tripro': {$exists: true}},
{'roles.tripro': role}
]}),
{ noReady: true });
return Meteor.users.find({
$and:[
{'roles.tripro': {$exists: true}},
{'roles.tripro': role}
]
}, options);
});
Then on client side, I am trying to subscribe this:
$meteor.autorun($scope, function() {
$meteor.subscribe('users_with_roles', {
limit: parseInt($scope.getReactively('perPage')),
skip: (parseInt($scope.getReactively('page')) - 1) * parseInt($scope.getReactively('perPage')),
sort: $scope.getReactively('sort'),
fields: {emails: 1, profile: 1}
},'admin').then(function() {
$scope.usersCount = $meteor.object(Counts ,'numberOfUsers', false);
console.log('user counter:' + $scope.usersCount.count);
$scope.users.forEach( function (user) {
// user.onClicked = function () {
// //$state.go('userProfile', {userId: user._id});
// };
console.log(user._id);
});
},
function(error)
{
console.log(error);
}
);
});
$scope.users = $meteor.collection(function() {
console.log('looking for role: ' + role);
return Meteor.users.find({}, {
//sort : $scope.getReactively('sort')
});
});
However, from the logging, it appears that the client side received all users , but from the logging on server side, it does give correct result.
What am I missing here?
A couple things to think about here.
When you request users you will always have "you". So if the user you are logged into is not an admin, it will still show up in the collection.
Because you are using $meteor.subscribe instead of $scope.$meteorSubscribe you are not clearing the subscription when the scope is destroyed so it's possible that it's mixing with other subscriptions on the client side from other scopes.

Client data not being updated in Meteor application

I have a Meteor application with a publish of:
Meteor.publish('my_items', function() {
var selector = {owner_id: this.userId};
var items = ItemOwnership.find(selector, {fields: {item_id: 1}}).fetch();
var itemIds = _.pluck(items, 'item_id');
return Items.find({
_id: {$in: itemIds},
item_archived_ts: { $exists: false }
});
});
and a subscription of this:
Meteor.subscribe('my_items');
The application allows for the user to add items to the 'Items' collection and this is done by calling a server method. The Items collection on the server is updated with the new record, but the client-side equivalent collection is not showing the new record. Is there anything obviously wrong with what I am doing, or some way to debug this?
p.s. there are no client/server-side errors occurring?
I found a way to accomplish this using the reywood:publish-composite Meteor package. Here is the publish that achieves this:
Meteor.publishComposite('my_items', {
find: function () {
var selector = {owner_id: this.userId};
return ItemOwnership.find(selector, {fields: {item_id: 1}});
},
children: [
{
find: function(IOrecord){
return Items.find({
_id: IOrecord.item_id,
item_archived_ts: { $exists: false }
});
}
}
]
});

Meteor.User does not load with all fields on initial load

I have added an additional field on Meteor.Users collection called usertype. In Iron-router I am returning the user object if user is logged in. Now in my template I need to check if this usertype field is present else, I direct the user to user registrations screen.
What is happening here is that even though I have the usertype field in my publish function, the user object is not returned with this field initially. It only shows up after 2-3 object loads. And this confuses the template loading logic, as this field is not found on initial load, but when infact the field is present.
DashboardController = RouteController.extend({
template: 'dashboard',
subscriptions: function() {
this.userProfileSub = Meteor.subscribe('singleUser', this.myId());
},
myId: function() {
var userId = Meteor.userId();
if(!userId)
{ userId = ''; }
return userId;
},
user: function() {
return Meteor.users.findOne(this.myId());
},
data: function() {
var user = this.user();
console.log(user);
if(user)
{
return {
user: this.user(),
ready: this.userProfileSub,
};
}
}
});
Here is the publish method:
Meteor.publish('singleUser', function(id) {
check(id, String);
return Meteor.users.find({_id:id}, {fields: { emails: 1,
profile: 1,
usertype: 1,
"services.facebook.id": 1,
"services.facebook.email": 1,
"services.twitter.screenName": 1,
"services.twitter.profile_image_url": 1,
"services.google.email": 1,
"services.google.picture": 1}});
});
EDIT
As answer given below, if the subscription is moved inside the waitOn block it should wait for the subscription to load completely.
waitOn: function() {
this.userProfileSub = Meteor.subscribe('singleUser', this.myId());
return [this.userProfileSub];
},
But now when I try to wait for multiple subscriptions to load in the wait array, it apparently is not waiting for all of them. I still get empty array. Even though I check in the action block. I can find the data later from my console.
waitOn: function() {
return [
Meteor.subscribe('singleUser', this.myId()),
Meteor.subscribe('singleAgentByUserId', this.myId()),
Meteor.subscribe('singleClientByUserId', this.myId())];
},
action: function () {
// this.ready() is true if all items returned from waitOn are ready
if (this.ready())
{
this.render();
}
else
this.render('Loading');
},
Update EDITED (See revision history for much different version due to original question)
Try:
waitOn: function() {
return [
Meteor.subscribe('singleUser', Meteor.userId()),
Meteor.subscribe('singleAgentByUserId', Meteor.userId()),
Meteor.subscribe('singleClientByUserId', Meteor.userId()];
},
loadingTemplate: "loading",
action: function () {
this.render('dashboard');
}

Resources