Meteor Collection advanced selector - meteor

I have a Project collection and a Task collection.
Each project has a user_id field, this holds the owner of the project.
Each task has a project_id field. So the structure is something like this:
User 1
Project 1
Task 1
Task 2
Project 2
Task 3
User 2
Project 3
Task 4
Task 5
For security purposes I only want to publish the projects belonging to a certain logged in user. For the project itself that's quite easy:
Meteor.publish('projects', function(){
return Projects.find({user_id: this.userId});
});
But how do I do this in a clean way for the Task collection? And why does the Collection.Allow doesn't have a 'view' option?
Something like:
Tasks.allow({
view: function (userId, doc) {
return Projects.findOne(doc.project_id).user_id == userId;
}
});
would be nice, is there a reason it's not there?

First, some recommended reading:
Reactive joins in meteor
A similar question on SO
Joins in meteor are currently tricky. It's easy to just join the collections in a publish function, but it isn't always straightforward to make them reactive (run again when things change).
Non-Reactive Options
You could publish both collections at the same time with:
Meteor.publish('projectsAndTasks', function() {
var projectsCursor = Projects.find({user_id: this.userId});
var projectIds = projectsCursor.map(function(p) { return p._id });
return [
projectsCursor,
Tasks.find({project_id: {$in: projectIds}});
];
});
The potential problem is that if tasks were added to a new project, they would not be published (see "The Naive Approach" from the first article above). Depending on how your application starts and stops its subscriptions, this may not matter. If you find that it does, keep reading.
Reactive Options
A simple option is just to denormalize the data. If you also added user_id to your tasks, then no joins are necessary, and the publish function looks like:
Meteor.publish('projectsAndTasks', function() {
var projectsCursor = Projects.find({user_id: this.userId});
var tasksCursor = Tasks.find({user_id: this.userId});
return [projectsCursor, tasksCursor];
});
If that doesn't appeal to you and you are using iron-router, you can do a client-side join in your routes (see "Joining On The Client" from the first article above). It's a bit slower because you need a second round trip but it's clean in that no data needs to be modified and no external packages need to be added.
Finally, you can do a reactive join on the server, either manually using observeChanges (not recommended), or by using a package. I have used publish-with-relations in the past, but it has some issues as pointed out in the articles). For a more complete list of package options, you can see this thread.
Not being a core developer on meteor, I don't have a precise answer for why allow/deny doesn't have a "read" option, but I'll take an educated guess. Depending on how the allow/deny function was written, the publisher would potentially have to run an expensive callback for every single document or partial update. The allow/deny callbacks are easy to tolerate when a single document is being modified, but if you suddenly need to publish several hundred documents and each one needs to be separately evaluated before being transmitted, I don't think that would be practical. I'm pretty sure that's why publishers can act alone as the arbiter of document read authorization.

You can do this for the tasks:
Meteor.publish('tasks', function(){
var projects = Projects.find({user_id: this.userId}, {fields: {_id: 1}});
var projectIdList = projects.map(function(project) { return project._id;});
return Tasks.find({project_id: {$in: projectIdList}});
});
First we get all the projects belonging to the user. We will only need the _id field so we filter the other fields
Then we map the _id's of the projects to a new array.
Then we publish a tasks.find that includes all the project ids in the mapped array.
The allow construction you mentionend is by my knowledge only ment to be used with updates and inserts

Related

Meteor Observer Reuse: Will changing my publication return query to not use this.userId make my observer more reusable?

I have a publication that returns a cursor that looks like this:
// Publication RETURNS THIS QUERY
Notepads.find({_id: notepadId, $or: [{userId: this.userId}, {'collaborators.userId': this.userId}], archived: false})
As you can see the query is unique to the user since it includes this.userId
I use this.userId in the publication as a form of security. You will only get back data if you are associated with that particular notepad.
In my app, multiple people can collaborate on a single notepad. So to make the observer more reusable will this adjustment help my app?
// Optimized publication
notepad = Notepads.findOne({_id: notepadId, $or: [{userId: #userId}, {'collaborators.userId': #userId}], archived: false})
if notepad?
// Optimized publication RETURNS THIS QUERY
return Notepads.find({_id: notepad._id, archived: false})
else
return
I think this is what observer reuse means. That the publication returns the exact same query for any user that subscribes to it. So is this correct, is this optimization worth the change?
There are problem with your optimized version that is it is not reactive. Because you use Notepads.findOne as a security check to make sure user have access to the document.
Inside publication .findOne is not reactive, say at the time the publication is executed user have no access to the document so nothing is sent to client, then user is added as an collaborator but that makes no change because the publication will re re-run.
Quoting from kadira.io on How Observers are Handled in Meteor:
In order to create identical observers, you need to create cursors
with:
the same collection
the same selector (query)
the same set of options (including sort, limit, etc.)
In your example the selector is not the same for different users.
Selectors are Object Literals and are evaluated before being passed into the .find call. This means that {_id: notepad._id, archived: false} becomes
{_id: 'myNotebookID', archived: false}, or
{_id: 'anotherNotebookId', archived: false}.
As you can see, these are different selectors after you resolve notepad._id, and this happens before being passed to .find().
If your first db query had returned the same notepad because your second user was a collaborator on the first user's notebook, you would end up with the same selector, and a single cursor / observable. However for most apps this is not likely to be common enough to optimise for, especially as (as #Khang points out) you are going to lose reactivity.
So, it's not the same query, it's not going to reuse the observer, and is not worth the change.

Where Should Meteor Subscriptions Be Located in My Application?

I currently have my publications stored in /server/publications.js. I would like to store my client-side subscriptions in a central file too, like in /client/subscriptions.js. Is this a good design decision or are there more cons than pros? Thanks
There are three main places you can subscribe to a Meteor collection.
Globally
In this approach, you create subscriptions in a file somewhere on the client. Like your example, subscriptions.js is what most people name it. As long as it's in the client folder, it's fine.
Meteor.subscribe("posts");
Good candidates for this are collections that you will play around with on all or most of your templates. A friends list similar to Facebook, a feed of some kind.
Router
If you're using something like iron-router or flow-router, you can subscribe to collections based on the URL. I prefer this approach the most. It's flexible, but not too taxing on performance. For example:
// Inside lib/router.js
FlowRouter.route('/blog/:postId', {
subscriptions: function(params) {
this.register('myPost', Meteor.subscribe('blogPost', params.postId));
}
});
Now you can access the data you need using myPost. Very neat, and you can subscribe to as many things as you need.
Template
This is the most flexible of all and the most taxing if you have multiple templates. Honestly, just avoid this approach because it's way too much work. 90% of the time I find myself using Router subscriptions.
Template.posts.onCreated(function () {
// will re-run when the "limit" reactive variables changes
instance.autorun(function () {
// get the limit
var limit = instance.limit.get();
console.log("Asking for "+limit+" posts…")
// subscribe to the posts publication
var subscription = instance.subscribe('posts', limit);
// if subscription is ready, set limit to newLimit
if (subscription.ready()) {
console.log("> Received "+limit+" posts. \n\n")
instance.loaded.set(limit);
} else {
console.log("> Subscription is not ready yet. \n\n");
}
});
});
I suppose you could if you only want global, automatically-activated subscriptions. For some small apps that makes sense. As your code grows in complexity you may want more fine-grained control over your subscriptions. Generally speaking, you have three patters for placing subscriptions in your app (in order of granularity and control):
Globally (as suggested in your question)
In the router - assuming you use iron router
In the template. Also see this post for additional template subscription examples.
While the community has recently fallen in love with template subscriptions, I'm not prepared to make a general recommendation that they are always they way to go. As you move up the subscription hierarchy, you lose some control but also gain reusability.
Let's take a simple example - imagine you have a social app and you have a list of friends. Let's say the majority of routes and templates assume the existence of your fiends' user data. If each template or route made the same subscription for this list, you'd end up with a lot of duplication and, potentially, a lot of unnecessary starting and stopping (this translates to server load and network bandwidth). On the other hand, had you made your friends a global subscription, those performance pitfalls could have been avoided.
Try to use template based subscriptions if possible. In that case, if you have /client/templates/home.html, you would put subscriptions related to that template in /client/templates/home.js for example.
Template.home.onCreated(function() {
this.subscribe('somePublication');
});
In my experience, the better way to manage the subscriptions is using the template subscription pattern pattern
Snippet taken from the reference:
Template.posts.onCreated(function () {
// 1. Initialization
var instance = this;
// initialize the reactive variables
instance.loaded = new ReactiveVar(0);
instance.limit = new ReactiveVar(5);
// 2. Autorun
// will re-run when the "limit" reactive variables changes
instance.autorun(function () {
// get the limit
var limit = instance.limit.get();
console.log("Asking for "+limit+" posts…")
// subscribe to the posts publication
var subscription = instance.subscribe('posts', limit);
// if subscription is ready, set limit to newLimit
if (subscription.ready()) {
console.log("> Received "+limit+" posts. \n\n")
instance.loaded.set(limit);
} else {
console.log("> Subscription is not ready yet. \n\n");
}
});
// 3. Cursor
instance.posts = function() {
return Posts.find({}, {limit: instance.loaded.get()});
}
});

How can I make a Meteor publish method reactive to a server-side parameter?

I have a publication based on server-side user permissions. I want it to be reactive to changes in these permissions.
// SERVER CODE
Meteor.publish("my_publication", function(parent_id) {
//fetch our parent record and lookup this user's permissions
var parent = ParentCollection.findOne({_id: parent_id});
var myPermissionsList = parent.permissionsDict[this.userId];
//use these permissions to make our query
ChildCollection.find({parent_id: parent_id, permissions: {$in: myPermissionsList}})
}
// CLIENT CODE
Tracker.autorun(function () {
Meteor.subscribe('my_publication', Session.get("my_parent_id"));
});
This properly returns all the elements of the "child" collection specified parent, as long as the parent says the user has at least one of the permissions in the child element's list. It does this without the user actually knowing what their permissions are, which is a requirement.
This behaves like one would expect in Meteor:
The subscription does automatically update if any of the returned ChildCollection elements are changed.
The subscription does automatically update if the client changes the "my_parent_id" Session variable, triggering the Tracker.autorun resubscribe.
The subscription does not automatically update if the permissions used to make the query (parent.permissionsDict[this.userId]) are changed.
We're looking for the best (highest performing) way to get an automatic update in the last case.
This article was a helpful, more detailed resource on the topic:
https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
My current understanding is that I need to utilize cursor.observeChanges() to react to changes in my permissions query. However, I am not sure how this fits into the rest of the Meteor publish/subscribe model--where would I call this, and how could the callback instruct Meteor to republish "my_publication"?
I believe https://atmospherejs.com/mrt/reactive-publish addresses this, but I feel like I should try to get a better grasp on core reactivity in meteor before turning to an external package. I also lack an understanding about the performance costs.
Any help would be greatly appreciated!
You can use the reactive-publish package (I am one of authors):
Meteor.publish("my_publication", function(parent_id) {
this.autorun(function (computation) {
//fetch our parent record and lookup this user's permissions
var parent = ParentCollection.findOne({_id: parent_id}, {fields: {permissionsDict: 1}});
var myPermissionsList = parent.permissionsDict[this.userId];
//use these permissions to make our query
return ChildCollection.find({parent_id: parent._id, permissions: {$in: myPermissionsList}});
});
}
It is important that you limit the fields you are interested in the parent document, otherwise autorun would rerun every time any field changes in the document, even if you do not care/use that field.

Meteor - subscribe to same collection twice - keep results separate?

I have a situation in which I need to subscribe to the same collection twice. The two publish methods in my server-side code are as follows:
Meteor.publish("selected_full_mycollection", function (important_id_list) {
check(important_id_list, Match.Any); // should do better check
// this will return the full doc, including a very long array it contains
return MyCollection.find({
important_id: {$in: important_id_list}
});
});
Meteor.publish("all_brief_mycollection", function() {
// this will return all documents, but only the id and first item in the array
return MyCollection.find({}, {fields: {
important_id: 1,
very_long_array: {$slice: 1}
}});
});
My problem is that I am not seeing the full documents on the client end after I subscribe to them. I think this is because they are being over-written by the method that publishes only the brief versions.
I don't want to clog up my client memory with long arrays when I don't need them, but I do want them available when I do need them.
The brief version is subscribed to on startup. The full version is subscribed to when the user visits a template that drills down for more insight.
How can I properly manage this situation?
TL/DR - skip to the third paragraph.
I'd speculate that this is because the publish function thinks that the very_long_array field has already been sent to the client, so it doesn't send it again. You'd have to fiddle around a bit to confirm this, but sending different data on the same field is bound to cause some problems.
In terms of subscribing on two collections, you're not supposed to be able to do this as the unique mongo collection name needs to be provided to the client and server-side collections object. In practice, you might be able to do something really hacky by making one client subscription a fake remote subscription via DDP and having it populate a totally separate Javascript object. However, this cannot be the best option.
This situation would be resolved by publishing your summary on something other than the same field. Unfortunately, you can't use transforms when returning cursors from a publish function (which would be the easiest way), but you have two options:
Use the low-level publications API as detailed in this answer.
Use collection hooks to populate another field (like very_long_array_summary) with the first item in the array whenever very_long_array changes and publish just the summary field in the former publication.
A third option might be publishing the long version to a different collection that exists for this purpose on the client only. You might want to check the "Advanced Pub/Sub" Chapter of Discover Meteor (last sub chapter).

Meteor publication with calculations

I have 2 collections: Meteor.users and Projecs.
Users collection have field "projects" which contains array of user's project's ids.
"projects" : [
"jut6MHx6a7kSALPEP",
"XuJNvq7KTRheK6dSZ"
]
Also I have a publication for user's projects:
Meteor.publish('projects', function() {
var userProjects = Meteor.users.findOne(this.userId).projects;
return Projects.find({_id: {$in: userProjects}});
});
Everything works fine, but when I add new project (and update users ("projects" field) who are in this project) reactive publication doesn't works. Projects page doesn't contains recently added project. It works only when I refresh page.
Subscription made in router:
waitOn: function() {
return [
Meteor.subscribe('projects')
]
},
What should I do with this publication? Thanks a lot.
This is happening because Meteor.users is not reactive. I don't know what the reason behind but I saw many developers, specially developers who try to get famous by publish really cool articles about their awesome application, exposing the tokens.
So if some idiot publish the Meteor.users to the browser, it's a security flaw. It would be even worst if it was reactive because the token would be updated in realtime. Maybe this a block to newbie who don't really know that they're doing. Just my opinion about this decision.
This collection is design to be used for managing users and after the login, it makes no sense to use to store data, as it is designed.
Yea, this is a known "problem". Publish functions aren't reactive, so Meteor.users.findOne(this.userId).projects will only be evaluated when the client subscribes. You'll find a lot of information about this if you search for "meteor reactive joins", for example https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
In your case, the clients will always have access to their array of project ids, right? Then the simplest solution would probably be to do something like this on the client:
Tracker.autorun(function(){
var user = Meteor.user()
if(user){
Meteor.subscribe(user.projects)
}
})
So, when the client notices that the array of project ids has changed, it renews the subscription (I'm unsure if passing user.projects to the subscribe call is necessary, but I'm a bit afraid that the subscription isn't is renewed if it's called with the same arguments as before).
Using the reactive-publish package (I am one of authors) you can do:
Meteor.publish('projects', function () {
this.autorun(function (computation) {
var userProjects = Meteor.users.findOne(this.userId, {fields: {projects: 1}}).projects;
return Projects.find({_id: {$in: userProjects}});
});
});
Just be careful to limit the first query only to projects so that autorun is not rerun for changes in other fields.

Resources