skip to Main Content

I have a three tables post(Model: Post), user(Model: User) and post_user_pivot for votes.
The Schema of my pivot table

Schema::create('post_user', function (Blueprint $table) {
        $table->id();
        $table->enum('type', ['up', 'down'])->default('up');
        $table->unsignedBigInteger('post_id');
        $table->unsignedBigInteger('user_id');
        $table->timestamps();
    });

Storing the votes using attach along with the type, up or down.
Fetching the voters like following ways

$post->voters as $voter using foreach()

Each $voter consist of $user as usual.
But what I want to see the current authenticated user is voted or not, and if voted the type of it(up or down) from voters as mentioned above. No iteration just query for that?
Thanks!

I have tried this

$post->voters->find(auth()->user()->id)->wherePivot('type', '=', 'down');

3

Answers


  1. If you want to see whether the current user has voted or not then you can do something as follows:

    $post->voters()
        ->where('user_id', auth()->id())
        ->where('type', 'down')
        ->exists();
    
    Login or Signup to reply.
  2. First you can improve your migration by replacing unsignedBigInteger with foreignId like so:

    Schema::create('post_user', function (Blueprint $table) {
        $table->id();
        $table->enum('vote', ['up', 'down'])->default('up');
        $table->foreignId('post_id')->constrained()->cascadeOnDelete(); // or onDelete('set null')
        $table->foreignId('user_id')constrained()->cascadeOnDelete(); // or onDelete('set null')
        $table->timestamps();
    
        // make sure a user can only vote once for each post
        $table->unique(['user_id', 'post_id']);
    });
    

    If you need to access the count of up and down votes frequently, it may help to add another index for post_id and vote.

    $table->index(['post_id', 'vote']);
    

    in User model:

    public function votedPosts()
    {
        return $this->belongsToMany(Post::class, 'votes')
                    ->withPivot('vote')
                    ->withTimestamps();
    }
    

    in Post model:

    public function voters()
    {
        return $this->belongsToMany(User::class, 'votes')
                    ->withPivot('vote')
                    ->withTimestamps();
    }
    

    Create a fluent model for updating user’s vote on a post (add to Post model):

    use IlluminateDatabaseEloquentRelationsPivot;
    
    class Post extends Model
    {
        // Define voters relationship as above...
    
        public function vote(User $user)
        {
            return new class($this, $user) {
                private $post;
                private $user;
    
                public function __construct(Post $post, User $user)
                {
                    $this->post = $post;
                    $this->user = $user;
                }
    
                public function up()
                {
                    $this->post->voters()->syncWithoutDetaching([
                        $this->user->id => ['vote' => 'up']
                    ]);
                }
    
                public function down()
                {
                    $this->post->voters()->syncWithoutDetaching([
                        $this->user->id => ['vote' => 'down']
                    ]);
                }
            };
        }
    }
    

    I used syncWithoutDetaching to prevent deleting any existing vote, we just want to update the vote.

    then update user’s vote in a single line like so:

    // User casts an "up" vote
    $post->vote(Auth::user())->up();
    
    // User changes the vote to "down"
    $post->vote(Auth::user())->down();
    
    Login or Signup to reply.
  3. in order to check if a user has voted for a post or not you need to define the reverse relation on the user model:

    public function votedPosts()
    {
        return $this->belongsToMany(Post::class, 'votes')
                    ->withPivot('vote')
                    ->withTimestamps();
    }
    

    than in your controller you can do something like this:

    class PostController extends Controller
    {
      public function index(Request $request)
      {
        /** @var User $auth */
        $auth = $request->user();
    
        $posts = Post::query()->paginate();
    
        // load user votes for current posts
        $currentVotes = $auth->votedPosts()
          ->whereIn('posts.id', $posts->pluck('id'))
          ->pluck('vote', 'post_id']);
        // resulting array of post_id => vote_type ('up' or 'down')
    

    and in view:

    @foreach($posts as $post)
      <div class="card">
        <div class="votes">
          <button class="@if(isset($currentVotes[$post->id]) && $currentVotes[$post->id] == 'up') active @endif" data-post="{{ $post->id }}" data-type="up">
          <button class="@if(isset($currentVotes[$post->id]) && $currentVotes[$post->id] == 'down') active @endif" data-post="{{ $post->id }}" data-type="down">
        </div>
      </div>
    @endforeach
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search