Search code examples
javascriptlaravelvue.jslaravel-7

How to make the sum of votes in a review, and then the average too?


Here's the situation, I'm in a Vue context, Laravel as backend. In my homepage I have a select for searching musicians based on their genre, so when I get to this page I want to show in each musician card the stars relative to the average votes received.

But I've got problems to manipulate the votes inside the template, and no idea of how to do in the methods actually. I know it's simple but it's getting me problems.

Here's the visual part of the problem, circled in red

enter image description here

And here's the code:

FilteredMusicians.vue

Here i cicle through the musicians array taken from the Laravel endpoint, then to get the votes i cicle through each musician.reviews to get the review.vote. Here I have problems to sum all the votes in one, and get the average of it.

<template>
  <div class="container">
    <div class="row py-4">
      <div
        v-for="musician in musicians"
        :key="musician.id"
        class="col-xs-12 col-md-4 col-lg-4 my-4"
      >
        <div class="card">
          <div class="musician-img">
            <img
              :src="'/storage/' + musician.cover"
              :alt="musician.stagename"
            />
          </div>
          <div class="card-body text-center">
            <h5 class="card-title py-2">{{ musician.stagename }}</h5>
            <!-- reviews -->
            <div v-for="review in musician.reviews" :key="review.id">
              {{ review.vote }}
            </div>
            <!-- /reviews -->
            <router-link
              class="btn btn-orange text-white"
              :to="{ name: 'musician', params: { slug: musician.slug } }"
              >Vedi profilo</router-link
            >
          </div>
        </div>
      </div>
    </div>
    <div class="text-center">
      <router-link class="btn btn-orange text-white mb-3" :to="{ name: 'home' }"
        >Indietro</router-link
      >
    </div>
  </div>
</template>

<script>
export default {
  name: "FilteredMusicians",

  data() {
    return {
      musicians: [],
    };
  },

  methods: {
    getMusicians(slug) {
      axios
        .get(`http://127.0.0.1:8000/api/musicians/${slug}`)
        .then((res) => {
          res.data.forEach((el) => {
            this.musicians = el.musicians;
          });
        })
        .catch((err) => {
          console.log(err);
        });
    },

    
  },

  created() {
    this.getMusicians(this.$route.params.slug);
  },
};
</script>

The function to get all the musicians related to a genre

public function filterMusicians($slug) {

        $genres = Genre::where('slug', $slug)->with('musicians')->get();

        return response()->json($genres);
    }

Here's the Musician model, here I used protected $with to take the reviews of each genre-filtered musician

class Musician extends Model
{
    protected $fillable = [
        'user_id', 
        'stagename',
        'slug', 
        'description',
        'bio',
        'services',
        'typology',
        'cover'
    ]; 

    protected $with = [
        'reviews'
    ];


    public function user() {
        return $this->hasOne('App\User'); 
    }

    public function genres() {
        return $this->belongsToMany('App\Genre'); 
    }

    public function sponsorships() {
        return $this->belongsToMany('App\Sponsorship')->withPivot('end_date'); 
    }

    public function messages() {
        return $this->hasMany('App\Message'); 
    }

    public function reviews() {
        return $this->hasMany('App\Review');
    }
}

and the Review model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Review extends Model
{
    protected $fillable = [
        'name',  
        'email', 
        'review', 
        'vote', 
        'musician_id'
    ]; 

    public function musician() {
        return $this->belongsTo('App\Musician')->with('reviews'); 
    }
}

Solution

  • I'm more of a React person, but it seems pretty straight forward what you need to do here. First create a new method underneath your getMusicians one, you can call it getAverageVotes. I'd imagine it would look something like this:

    getAverageVotes(reviews) {
      const sum = reviews.reduce((acc, review) => acc += review.vote, 0)
      return sum / reviews.length
    }
    

    In your template, change this:

    <div v-for="review in musician.reviews" :key="review.id">
      {{ review.vote }}
    </div>
    

    to this:

    <div>
      {{ getAverageVotes(musician.reviews) }}
    </div>