Getting nested object from mongo


#1

I feel a little silly, but I can’t work this out.

I have a mongo collection like this:

{
	"_id": "jZHu8j9cbttAKrCa7",
	"message": [
		{
			"time": 1509329180962,
			"from": "System",
			"message": "Your account has been created!"
		},
         {
			"time": 1509329180964,
			"from": "System",
			"message": "User data has been populated."
		}
	]
}

I want to do something like this in the template:

{{#each message}}
  {{time}}
  {{from}}
  {{mesage}}
{{/each}}

I’ve subscribed to the collection, and the data is available in the temple (checked using Mongol).

I have a helper like this:

Template.activityLog.helpers({
  message() {
        return ActivityLog.find({});
      }
});

What am I doing wrong here? What should my helper look like so I can use the nested message objects?


#2

This looks like a problem with your schema design. Is there a reason many messages are part of each ActivityLog document?

I’d arrange the data like so:

{
    "_id": "randomString",
    "time": 1509329180962,
    "from": "System",
    "message": "Your account has been created!"
}, {
    "_id": "randomString",
    "time": 1509329180964,
    "from": "System",
    "message": "User data has been populated."
}

With each document being a single message. Then when you call ActivityLog.find({}) you get a cursor of each message. Then the rest of your code will work just fine.

If you need the data in that schema, you’ll need to get each message of each Log document (assuming you want them all displayed) like so:

// js
Template.activityLog.helpers({
  logs() {
        return ActivityLog.find({});
      }
});
// template
{{#each log in logs }}
  {{#each message in log.message }}
    {{ message.time }}
    {{ message.from }}
    {{ message.mesage }}
  {{/each}}
{{/each}}

I’ve also switched to using the preferred each in syntax. See
http://blazejs.org/guide/reusable-components.html#Prefer-lt-16-gt


#3

Thanks @coagmano, very much appreciated, that solved it! :slight_smile:

The reason I’ve used this schema is because I don’t want users to have access to other users’ messages. The _id of each message object is actually the user _id of the owner. I’ve used this to only publish the right data for each user:

Meteor.publish('activitylog', function pending() {
  if(ActivityLog.findOne({_id: Meteor.user()._id})) {
    return ActivityLog.find({_id: Meteor.user()._id});
  } else {
    return null;
  }
})

I’m not sure how this will hold up when the collection size starts to get large, I only did it this way because I don’t know any other way. Is there a better or more meteoric way to go about this?


#4

How to structure data is pretty subjective. My recommendation is to put the user_id in as a separate field:

// Schema
{
    _id: String,
    user_id: String,
    time: ISODate,
    from: String,
    message: String
}

Then in your publication:

// publication
import { ActivityLog } from '/imports/api/ActivityLog';
Meteor.publish('activityLog', function() {
    return ActivityLog.find({ user_id: this.userId });
});

note that in a publication this.userId is available and more performant than Meteor.user()

And then you don’t need to double nest on the client, don’t need to nest in your db, and your front-end code becomes simpler. You also get the benefit of indexing on your messages.