Best way to handel nested model objects


#1

I have a collection, lets say Authors and every Author has many Books (separate collection). So as part of my Authors SimpleSchema config I have:

bookId: {
  type: Array
  }

And within that array I have all the book ID’s belonging to the specific author:

["id_1","id_2","id_3","id_4","id_5"]

So my question is, how can I create a Author.books method (think Rails) that would return all my book objects (not just the ID’s) of the given author?

I think there might be a few packages that can do this (please feel free to suggest) but I would love to know how to do it manually as a great learning experience.

Thanks.


#2

apply a transform function to your Authors collection, which extends a doc to return a fetched cursor of associated books:

Authors = new Mongo.Collection('authors', { transform: function(doc) {

  doc.books = Books.find({ _id: { $in: doc.bookId }}).fetch();
  return doc;

} });

you can also make the books property a method which fetches every time you invoke it

of course, you’d have to also publish all associated books accordingly


#3

Wow, thanks for the prompt response. I will give it a shot and report back.


#4

Also have a look at matb33/collection-hooks. It uses transform under the hood, but has a nicer api.


#5

@chenroth, your answer worked like a charm. Thanks for the help.

@fvg thanks for the suggestion, I will definitely use that package for some beforeInsert logic.


#6

Haha sorry, actually wanted to show you dburles/collection-helpers :smile:
Just typed “collection-” in my browser and didn’t think any further …

Using this package you just get a nicer syntax:

Authors = new Mongo.Collection('authors');

Authors.helpers({
  books: function () {
    return Books.find({ _id: { $in: this.bookId }});
  }
});

However, maybe you might find collection-hooks helpful in the future :wink:


#7

So I am having a small issue now. When I go to /author/_id/books the books will not render unless I manually refresh the page. I am using template level subscriptions as follows:

Template.authorBooks.onCreated(function () {
  var self = this;
  self.subscribe('authors');
  self.subscribe('books');
});

In my publications.js I have:

Meteor.publish('authors', function () {
  return Authors.find();
});

Meteor.publish('books', function () {
  return Books.find();
});

I am a little confused as to why it only works on a manual page reload, if I am browsing my /authors page and click an author, the books will not load until I manually refresh the page.


#8

Where is the file located that defines the /author/_id/books route?


#9

/both/router.js (iron Router)


#10

I don’t know if it makes a difference, but maybe move it to /lib/router.js. If the books come up on a manual refresh (server route) but not just surfing to it (client route), maybe it’s related to the routes not being loaded before everything else?

In your template, are you waiting till the subscriptions are ready before showing the content? e.g.:

{{#if Template.subscriptionsReady}}
  <!-- show books -->
{{/if}}

#11

This fixed the issue! Thanks for the help.


#12

Glad it worked! You could also, of course, use an {{else}} block to show a spinner or some “loading…” text or whatever. Very handy. Template based subscriptions are awesome!