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 Illuminate\Database\Eloquent\Relations\Pivot;
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();