Meteor/Mongo: $in not working to retrieve users groups - mongodb

I'm trying to return all groups that the current user is a part of. Each user has a groups field that's simply an array of id's for the groups the user belongs to. Here's what my code looks like:
My server method for returning the correct groups
userGroups: function(){
var currentUser = Meteor.users.find({_id: Meteor.userId()});
return Groups.find({_id: { $in: currentUser.groups}});
}
Then the call to that method in my helper:
groups: function(){
return Meteor.call('userGroups');
}
I've tried debugging this in the console but I'm just getting more confused. I can call var user = Meteor.users.find(_id: Meteor.userId()) and it correctly assigns the current user to the variable, but then when I call user.groups (which is the array of group id's) it says it's undefined. If I check the document in the meteor mongo command line interface, the current user has a groups field with group id's in it.

find query in Meteor returns a cursor which is not an array, but an object.
You should add .fetch() or use findOne().
userGroups: function(){
var currentUser = Meteor.users.findOne({_id: Meteor.userId()});
// or use var currentUser = Meteor.users.find({_id: Meteor.userId()}).fetch();
return Groups.find({_id: { $in: currentUser.groups}});
}
This should work!
Update on publishing groups form Meteor.users collection
To add groups from Meteor.users collection to your Meteor.user() auto-publish which acccounts-password package does, you need to include that into a null publication.
Something like this:
Meteor.publish(null, function () {
if (!this.userId) return this.ready();
return Meteor.users.find({_id: this.userId}, {
fields: {
profile : 1,
emails : 1,
groups : 1
}
});
});

Related

ObjectId as key to another collection-Migration

I am trying to add the ObjectId as a "Foreign key" to a collection. I have the previous id to link but I am having problem with the script.
Following is the script
db.users.find().forEach(function (user) {
var cursor = db.po1.find({"owner:": user.ID});
cursor.forEach(function(property) {
property.user_id = user._id;
db.po1.save(property);
});
});
The script runs but I do not get the field added to the documents of the po1 collection.
I am using mongoose for the api so I need the ObjectId. I do not want to embed the documents because of the rarity of the calls and the size of the po1 per user.
user.ID and po1.owner field are of the same type.
Thanks you for your time
From comment the answer.
Although save() has also been deprecated and finally the bellow script did the trick
db.users.find().forEach(function (user) {
var cursor = db.views.find({"user": user.ID});
cursor.forEach(function(object) {
object.userId = user._id;
db.views1.insertOne(object);
});
});
So through the innsert I created new collection and dropped previous.

Meteor: Publish using users profile properties rather than ID

I'm currently creating an app that will be used by multiple companies.
Each user has the following profile:
username: johnDoe
emails: [{address: "some#email.com", verified: true}],
profile: {
name: "John Doe",
companyId: "1234"
}
I then have a collection (called Companies) of company objects that contain configuration info, templates etc specific to that company.
{
id: "1234",
configuration: {},
templates: []
}
In order to isolate each companies data I want to only publish data that matches the users profile companyId to the companies id.
if (Meteor.isServer) {
// collection to store all customer accounts
Companies = new Mongo.Collection('Companies');
// publish collection
Meteor.publish("Company", function () {
return Companies.find({id: Meteor.user().profile.companyId});
})
}
This currently works if I hardcode the clinic Id
// publish collection
Meteor.publish("Company", function () {
return Companies.find({id: "1234");
})
But returns an empty cursor with the Meteor.user().profile.companyId.
This means that the issue is either that I'm using the wrong function or more probably, the publish is happening before the user().profile.companyId can run.
Anybody know what I'm doing wrong? and do you have any advice on what to read up about so that I have an understanding of this moving forwards?
Thanks
Try doing an explicit findOne() in your publish function:
// publish collection
Meteor.publish("Company", function () {
var user = Meteor.users.findOne({_id: this.userId});
if(user && user.profile && user.profile.companyId) {
return Companies.find({id: user.profile.companyId});
} else {
console.log(user);
return this.ready();
}
});

Accessing services field of Meteor.users

When I query Meteor.users I do not receive the services field or any other custom fields I have created outside of profile. Why is it that I only receive _id and profile on the client and how can I receive the entire Meteor.users object?
Thanks.
From the DOcs
By default, the current user's username, emails and profile are published to the client. You can publish additional fields for the current user with:
As said above If you want other fields you need to publish them
// server
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'services': 1, 'others': 1}});
} else {
this.ready();
}
});
// client
Meteor.subscribe("userData");
The above answer does work, but it means you have to subscribe to said data, which you should do if you are getting data from users other than the currently logged in one.
But if all you care about is the logged in user's data, then you can instead use a null publication to get the data without subscribing.
On the server do,
Meteor.publish(null, function () {
if (! this.userId) {
return null;
}
return Meteor.users.find(this.userId, {
fields: {
services: 1,
profile: 1,
roles: 1,
username: 1,
},
});
});
And this is actually what the accounts package does under the hood

Subscribing to Meteor.Users Collection

// in server.js
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client.js
Meteor.subscribe("directory");
I want to now get the directory listings queried from the client like directory.findOne() from the browser's console. //Testing purposes
Doing directory=Meteor.subscribe('directory')/directory=Meteor.Collection('directory') and performing directory.findOne() doesn't work but when I do directory=new Meteor.Collection('directory') it works and returns undefined and I bet it CREATES a mongo collection on the server which I don't like because USER collection already exists and it points to a new Collection rather than the USER collection.
NOTE: I don't wanna mess with how Meteor.users collection handles its function... I just want to retrieve some specific data from it using a different handle that will only return the specified fields and not to override its default function...
Ex:
Meteor.users.findOne() // will return the currentLoggedIn users data
directory.findOne() // will return different fields taken from Meteor.users collection.
If you want this setup to work, you need to do the following:
Meteor.publish('thisNameDoesNotMatter', function () {
var self = this;
var handle = Meteor.users.find({}, {
fields: {emails: 1, profile: 1}
}).observeChanges({
added: function (id, fields) {
self.added('thisNameMatters', id, fields);
},
changed: function (id, fields) {
self.changed('thisNameMatters', id, fields);
},
removed: function (id) {
self.removed('thisNameMatters', id);
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
No on the client side you need to define a client-side-only collection:
directories = new Meteor.Collection('thisNameMatters');
and subscribe to the corresponding data set:
Meteor.subscribe('thisNameDoesNotMatter');
This should work now. Let me know if you think this explanation is not clear enough.
EDIT
Here, the self.added/changed/removed methods act more or less as an event dispatcher. Briefly speaking they give instructions to every client who called
Meteor.subscribe('thisNameDoesNotMatter');
about the updates that should be applied on the client's collection named thisNameMatters assuming that this collection exists. The name - passed as the first parameter - can be chosen almost arbitrarily, but if there's no corresponding collection on the client side all the updates will be ignored. Note that this collection can be client-side-only, so it does not necessarily have to correspond to a "real" collection in your database.
Returning a cursor from your publish method it's only a shortcut for the above code, with the only difference that the name of an actual collection is used instead of our theNameMatters. This mechanism actually allows you to create as many "mirrors" of your datasets as you wish. In some situations this might be quite useful. The only problem is that these "collections" will be read-only (which totally make sense BTW) because if they're not defined on the server the corresponding `insert/update/remove' methods do not exist.
The collection is called Meteor.users and there is no need to declare a new one on neither the server nor the client.
Your publish/subscribe code is correct:
// in server.js
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client.js
Meteor.subscribe("directory");
To access documents in the users collection that have been published by the server you need to do something like this:
var usersArray = Meteor.users.find().fetch();
or
var oneUser = Meteor.users.findOne();

Meteor Publish Distinct Values of Field in Collection

I'm stuck on a pretty simple scenario in Meteor:
I have a huge collection of things with many fields, some of them containing quite a bit of text.
I want to create a page for searching that collection.
One of the fields that each item in the collection has is "category".
I'd like to give the user the ability to filter by that category.
For that, I need to publish just the distinct values of the category field in the collection.
I can't figure out a way to do that without publishing the whole collection which takes way too long. How can I publish just the distinct categories and use them to fill a dropdown?
Bonus question and somewhat related: How do I publish a count of all items in the collection without publishing the whole collection?
A good starting point to make this easier would be to normalize your categories into a separate database collection.
However assuming that is not possible or practical, the best (though imperfect) solution will be to publish two separate versions of your collection, one which returns only the categories field of the entire collection and another which returns all fields of the collection for the selected category only. That would look like the following:
// SERVER
Meteor.startup(function(){
Meteor.publish('allThings', function() {
// return only id and categories field for all your things
return Things.find({}, {fields: {categories: 1}});
});
Meteor.publish('thingsByCategory', function(category) {
// return all fields for things having the selected category
// you can then subscribe via something like a client-side Session variable
// e.g., Meteor.subscribe("thingsByCategory", Session.get("category"));
return Things.find({category: category});
});
});
Note that you will still need to assemble your array of categories client side from the Things cursor (for example, by using underscore's _.pluck and _.uniq methods to grab the categories and remove any dups). But the data set will be much smaller as you are only working with single-field documents now.
(Note that ideally, you would want to use Mongo's distinct() method in your publish function to publish only the distinct categories, but that is not possible directly as it returns an array which cannot be published).
You could use the internal this._documents.collectionName to only send new categories down to the client. Tracking which categories to remove becomes a bit ugly so you probably will still end up maintaining a separate 'categories' collection eventually.
Example:
Meteor.publish( 'categories', function(){
var self = this;
largeCollection.find({},{fields: {category: 1}).observeChanges({
added: function( id, doc ){
if( ! self._documents.categories[ doc.category ] )
self.added( 'categories', doc.category, {category: doc.category});
},
removed: function(){
_.keys( self._documents.categories ).forEach( category ){
if ( largeCollection.find({category: category},{limit: 1}).count() === 0 )
self.removed( 'categories', category );
}
}
});
self.ready();
};
Re: the bonus question, publishing counts: take a look at the meteorite package publish-counts. I think that does what you want.
These patterns might be helpful to you. Here is a publication that publishes counts:
/*****************************************************************************/
/* Counts Publish Function
/*****************************************************************************/
// server: publish the current size of a collection
Meteor.publish("countsByProject", function (arguments) {
var self = this;
if (this.userId) {
var roles = Meteor.users.findOne({_id : this.userId}).roles;
if ( _.contains(roles, arguments.projectId) ) {
//check(arguments.video_id, Integer);
// observeChanges only returns after the initial `added` callbacks
// have run. Until then, we don't want to send a lot of
// `self.changed()` messages - hence tracking the
// `initializing` state.
Videos.find({'projectId': arguments.projectId}).forEach(function (video) {
var count = 0;
var initializing = true;
var video_id = video.video_id;
var handle = Observations.find({video_id: video_id}).observeChanges({
added: function (id) {
//console.log(video._id);
count++;
if (!initializing)
self.changed("counts", video_id, {'video_id': video_id, 'observations': count});
},
removed: function (id) {
count--;
self.changed("counts", video_id, {'video_id': video_id, 'observations': count});
}
// don't care about changed
});
// Instead, we'll send one `self.added()` message right after
// observeChanges has returned, and mark the subscription as
// ready.
initializing = false;
self.added("counts", video_id, {'video_id': video_id, 'observations': count});
self.ready();
// Stop observing the cursor when client unsubs.
// Stopping a subscription automatically takes
// care of sending the client any removed messages.
self.onStop(function () {
handle.stop();
});
}); // Videos forEach
} //if _.contains
} // if userId
return this.ready();
});
And here is one that creates a new collection from a specific field:
/*****************************************************************************/
/* Tags Publish Functions
/*****************************************************************************/
// server: publish the current size of a collection
Meteor.publish("tags", function (arguments) {
var self = this;
if (this.userId) {
var roles = Meteor.users.findOne({_id : this.userId}).roles;
if ( _.contains(roles, arguments.projectId) ) {
var observations, tags, initializing, projectId;
initializing = true;
projectId = arguments.projectId;
observations = Observations.find({'projectId' : projectId}, {fields: {tags: 1}}).fetch();
tags = _.pluck(observations, 'tags');
tags = _.flatten(tags);
tags = _.uniq(tags);
var handle = Observations.find({'projectId': projectId}, {fields : {'tags' : 1}}).observeChanges({
added: function (id, fields) {
if (!initializing) {
tags = _.union(tags, fields.tags);
self.changed("tags", projectId, {'projectId': projectId, 'tags': tags});
}
},
removed: function (id) {
self.changed("tags", projectId, {'projectId': projectId, 'tags': tags});
}
});
initializing = false;
self.added("tags", projectId, {'projectId': projectId, 'tags': tags});
self.ready();
self.onStop(function () {
handle.stop();
});
} //if _.contains
} // if userId
return self.ready();
});
I have not tested it on Meteor, and according to the replies, I'm getting skeptical that it will work but using a mongoDB distinct would do the trick.
http://docs.mongodb.org/manual/reference/method/db.collection.distinct/