Order Posts by Most Votes (Overall, Last Month, etc.) with Laravel MongoDB
Asked Answered
P

0

2

I am trying to understand more advanced functions of mongodb and laravel but having trouble with this. Currently I have my schema setup with a users, posts, and posts_votes collections. The posts_votes has a user_id, post_id and timestamp field.

In a relational DB, I would just left join the posts_votes collection, count, and order by that count. Exclude dates when need be and all that.

MongoDB I am having difficulty b/c there's no left join equivalent. So I'd like to learn how to accomplish my goal in a more document-y way.

On my Post model in Laravel, I reference this way. So looking at an individual post, I can get the vote count, see if current user voted for a specific post, etc.

public function votes()
{
  return $this->hasMany(PostVote::class, 'post_id');
}

And my current working query looks like this:

$posts = Post::forCategoryType($type)
    ->with('votes', 'author', 'businessType')
    ->where('approved', true)
    ->paginate(25);

The forCategoryType method is just extended scope I added. Here it is on the Post model/document class.

public function scopeForCategoryType($builder, $catType)
{
  if ($catType->exists) {
    return $builder->where('cat_id', $catType->id);
  }
  return $builder;
}

So when I look at posts like this one, it's close to what I want to accomplish, but I am not applying it properly. For instance, I changed my main query to look like this:

$posts = Post::forBusinessType($type)
    ->with('votes', 'author', 'businessType')
    ->where('approved', true)
    ->sortByVotes()
    ->paginate(25);

And created this new method on the Post model:

public function scopeSortByVotes($builder, $dir = 'desc')
{
  return $builder->raw(function($collection) {
   return $collection->aggregate([
    ['$group' => [
        '_id' => ['post_id' => 'votes.$post_id', 'user_id' => 'votes.$user_id']
      ], 
      'vote_count' => ['$sum' => 1]
    ],
    ['$sort' => ['vote_count' => -1]]

  ]);

});
}

This returns the error exception: A pipeline stage specification object must contain exactly one field.

Not sure how to fix that (still looking), so then I tried:

return $collection->aggregate([
        ['$unwind' => '$votes'],
        ['$group' => [
            '_id' => ['post_id' => ['$votes.post_id', 'user_id' => '$votes.user_id']],
            'count' => ['$sum' => 1]
          ]
        ]
      ]);

returns an empty ArrayIterator, so then I tried:

public function scopeSortByVotes($builder, $dir = 'desc')
  {
    return $builder->raw(function($collection) {
       return $collection->aggregate([
    '$lookup'    => [
      'from' => 'community_posts_votes',
      'localField' => 'post_id',
      'foreignField' => '_id',
      'as' => 'vote_count'            
    ]
  ]);

    });
  }

But on this setup, I just get the list of posts unsorted. On version 3.2.8.

The default loads everything by most recent. But ultimately I want to be able to pull these posts based on how many votes they got lifetime, but also query based on which posts got the most votes in the last week, month, etc.

That example I shared has the grand total linked in the Post model and an array of all the user ids that voted on it. With the way I have things setup using a separate collection holding the user_id, post_id and timestamps of when the vote happened, can I still accomplish the same goal?

Note: using this laravel mongodb library.

Pyrethrin answered 13/8, 2016 at 20:26 Comment(3)
I might be missing something here, but can't you replace ->sortByVotes() with ->orderBy('votes', 'DESC') ?Leukocyte
votes is a link to another table that has user_id, post_id, and timestamps. Essentially I need to group everything together by post_id and then sort by that resultPyrethrin
You have a simple syntax error in aggregate where you close the $group subdocument too early. So vote_count is not inside $group and that's why you get exception about the pipeline syntax. It may be easier for people to help you if you use native MongoDB syntax to show documents and aggregation pipeline being used.Rafaellle

© 2022 - 2024 — McMap. All rights reserved.