Not sure if I'm doing this right (reply system)?


#1

It’s me again, first it was CFS now it’s something else!

So I’ve set up a simple posts system, working a treat. I’ve got my textarea’s accepting markdown and all those lovely bells and whistles. But now I want replies to my posts… And I’ve run into a couple of problems.

The ‘schema’ for posts looks like this:

_id: obvious
title: title of post
content: content of post
owner: Meteor.user();
replies {
       content: content of reply,
       owner: Meteor.user(),
       createdAt: Date.now(),
}
createdAt: Date.now()

So yeah, I want to get the content from the ‘replies’ sub-array but I can’t seem to figure out how to do it…

As always, any help or guidance would be greatly appreciated!

Cheers,

Tom.


#2

EDIT: Or should I create a separate ‘replies’ collection and link the post with that collection? If so, what would be the best way to go about doing this?

Thanks,

Tom.


#3

Hi,
replies should be array of objects I think.

Well, there are many possibilities - you can go one route and have all replies inside main post.
Or you can add “parent” optional field to schema and work with posts and replies same way as top level post documents.


#4

So would you recommend a collection just for replies then? I’d prefer all replies to be stored in the posts collection ideally, but if it’s problematic I don’t mind making a separate collection for replies.

Can you maybe point me in the right direction of any examples at all? Googled to hell and back but not found many tangible examples.


#5

I vaguely remember from somewhere that Meteor and it’s implementations don’t do too well with ‘diffing’ a documents nested inner-objects, as in, a change in a “reply” object to that posts replies attribute would trigger an unnecessary amount of data exchange but I can’t find the source for that right now :frowning:


#6

So basically don’t hold replies within a ‘posts’ document? Are collections in meteor better as one-dimensional arrays then?

I come from a PHP/SQL kind of background and NoSQL is new to me (in a good way though!).

Thanks for the response too btw :smiley:


#7

I’m still struggling with this, does anyone know of a ‘best practice’ way to get replies working?

I’m thinking of having a ‘replies’ collection, separate from the main post itself. That being the case, does anyone know of a good way to get the ID of a post you’re already viewing? In my routes.js file I have:

var currentPost = this.params._id;
return Posts.findOne({ _id: currentPost });

But when I try something similar on my replies insert form it’s not quite working alright. Ideally, what I would like is something like the following:

  • Get the postId from the post. On reply insert, add the postId as a field.
  • Use a find() function to get all the replies for that post from the postId.

I think I can do the second part based on the above but it’s the first bit I’m struggling with! I had a look at http://joshowens.me/adding-comments-to-your-meteor-js-app/ but there is zero mention of the reply form or anything like that, so it’s difficult to gather what’s going on (it’s also quite an old post now).

Thanks in advance for any responses :smile:


#8

I guess not? Can no-one even point me in the right direction? It’s frustrating me to no end right now! :confounded:


#9

You could have a type of document, say type:'post' and type:'reply', and then all replies would have a reply_to key, containing the _id of a post?

Post

_id: obvious,
type: 'post',
title: title of post,
content: content of post,
owner: Meteor.user(),
createdAt: Date.now()

Reply

_id: obvious,
type: 'reply',
reply_to: id of post,
title: title of reply,
content: content of reply,
owner: Meteor.user(),
createdAt: Date.now()

#10

Many thanks for your reply @mdingena.

That’s pretty much what I’m trying to achieve, but how do I get the id of the post? That’s the bit I’m stuck on. Do you have any ideas how I could achieve this?


#11

There are different ways to do it, depending on what your interface and workflow looks like.

I would inject the post’s ID into a form or button element. For instance, the reply button on the post could have the ID of that post saved in a data-post-id attribute:

<button class="reply-to-post-button" data-post-id="9371">Reply</button>

In JavaScript (I’m familiar with jQuery, but you can use anything you want, really):

$( '.reply-to-post-button' ).on( 'click', function( event ) {
    var parent_post_id = $( this ).data( 'post-id' );
});

#12

Or if you look at this forum’s source code, there is a parent container that contains a post and all UI elements inside. This parent container has the ID, and all children calling an action could bubble up to this ID.

In jQuery (notice the .parent() calls):

$( '.reply-to-post-button' ).on( 'click', function( event ) {
    var parent_post_id = $( this ).parent().parent().parent().data( 'post-id' );
});


#13

Many thanks for this @mdingena!

I’ll give this a go today and let you know how I get on :smile:


#14

The way I structured it for the comments section of my app, every comment is within a single “comments” collection, and has the properties: parent and children. On a reply, the parent id gets added to the new post, and a reply gets added to an array of “replies” on the parent post.

That way you can find replies by the field “parent: postId” in your query. Any new comments will then be reactively loaded as usual by Meteor.

You can get the id of a comment/post with a blaze event:

'click .replytocomment': function () {
    
        var commentId = this._id;
}

Hope that helps


#15

Thank you @jracollins

You mean so the ‘post’ collection would look something like this:

_id
title
content
createdAt
replies {
     owner
     content
     createdAt
}

So it’s kind-of sub-array within the posts collection? If so, how would you then display this sub-collection (which will be a collection of objects if I’m correct) as I kind of got this to work before but struggled to display the replies.

Cheers!


#16

Not quite, the replies would be an array of postId’s that you could then find and display like you would any other post.

_id:
title:
content:
createdAt:
parent: parentPostId
replies: {
    [replyPostId1, replyPostId2, replyPostId3]
}

and you could find the replies like this:

find({ _id: {$in: parentPost.replies}})
or
find ({ parent: parentPostId}})


#17

That looks like a great way of doing it. I should have some time tonight to have a tinker :smile:


#18

Even though I got this working before, I think I’m doing something a bit wrong. I’m nearly there, but I’m lacking coffee and it’s too close to bed-time for that now!

Here is the result of my Posts.find().fetch() and selecting the current post:

replies: Array[1]
0: "suz2NNLZuNJEAShPC"

So it’s definitely created something. Here is my form (no security set for this yet, I’m aware of that!):smile:

Template.post.events({
   'submit .add-post': function(event) {
        event.preventDefault();
        var contentInput = $('#mark').val();
        var content = parseMarkdown(contentInput);
        var owner = Meteor.user();
        var commentId = this._id;
        Replies.insert({
            owner: owner,
            content: content,
            createdAt: Date.now()
        });
        Posts.update(this._id, {
            $set: {
                replies: [commentId]
            }
        });
   }
});

I think the issue is with the Posts.update part of the code. I tried insert but it inserts a completely new record (ie, a post) rather than insert something into the ‘replies’ array, which is annoying.

Almost there though, so close I can smell it!


#19

EDIT: IGNORE THIS POST. I realised I wasn’t returning anything in my helper. All working now!

Actually, I think I found a solution but again I’ve run into a problem!

In the “replies” collection, I’m now storing the parent of the reply which has the _id of the post (I realised that’s what was happening above with this._id). So now all I need to do is find all the replies that match the id of the post.

Here is my helper:

Template.post.helpers({
    replies: function() {
        var currentPost = this._id;
        replies: Replies.find({parent: currentPost});
    }
});

Here is what I’ve used in Iron:Router:

data: function() {
    var currentPost = this.params._id;
    return Posts.findOne({ _id: currentPost});
    return Replies.find({ parent: currentPost});
}

Which all seems fairly sound. But where I use:

{{#each replies}}
    {{{content}}}
{{/each}}

Nothing is returned. Pretty sure something is missing and it’s probably something really minor. Checked across how my posts are being displayed on my homepage and it’s essentially the same but just not getting anything here :frowning:

Again any help would be appreciated!


#20

Maybe checkout my blog post: Creating a comment/reply system with Meteor