Esta clase si me decepcionó, no tiene una buena explicación y es mucho código sin sentido.
Dejo el repositorio del proyecto con Laravel 8:
Con Eloquent puedes construir un sistema de calificaciones flexible y escalable sin duplicar lógica. Aquí verás cómo las relaciones polimórficas permiten que una sola tabla pivot conecte múltiples modelos, cómo estructurar la migración, el modelo Rating y dos traits reutilizables: CanRate y CanBeRated. El resultado: un único flujo para puntuar productos, páginas o categorías con pruebas que pasan.
¿Qué resuelven las relaciones polimórficas en Eloquent?
Las relaciones polimórficas permiten que un modelo pertenezca a más de un tipo de modelo. Con un ejemplo de rating, un usuario o invitado puede puntuar un producto, una página o una categoría con una sola relación. Así evitas múltiples tablas intermedias y repeticiones.
¿Cómo funciona el concepto de morph en Eloquent?
¿Qué roles intervienen en el rating?
¿Cómo se implementa el sistema de rating con morphs?
Primero se crea la migración para la tabla intermedia y luego el modelo Rating que extiende Pivot. Esto habilita timestamps, score y los tipos polimórficos.
¿Cómo crear la migración de ratings con morphs?
Schema::create('ratings', function (Blueprint $table) { $table->id(); $table->morphs('ratable'); // ratable_id, ratable_type $table->morphs('qualifier'); // qualifier_id, qualifier_type $table->unsignedTinyInteger('score'); $table->timestamps(); });
¿Cómo definir el modelo Rating y morphTo?
use Illuminate\Database\Eloquent\Relations\Pivot; class Rating extends Pivot { public $incrementing = true; protected $table = 'ratings'; public function ratable() { return $this->morphTo(); } public function qualifier() { return $this->morphTo(); } }
¿Qué comandos de artisan intervienen?
¿Cómo usar traits para calificar y ser calificado?
Para mantener el diseño flexible, se usan traits: uno para quienes califican (CanRate) y otro para quienes son calificados (CanBeRated). Allí se centraliza la relación morphToMany, alias, timestamps, columnas pivot y filtros por tipo.
¿Cómo implementar el trait CanRate con morphToMany?
trait CanRate { public function ratings($model = null) { $modelClass = $model ? get_class($model) : $this->getMorphClass(); return $this->morphToMany( $modelClass, 'qualifiers', 'ratings', 'qualifier_id', 'ratable_id' )->as('rating') ->withTimestamps() ->withPivot('score', 'ratable_type') ->wherePivot('ratable_type', $modelClass) ->wherePivot('qualifier_type', $this->getMorphClass()); } public function hasRated($model) { return $this->ratings($model)->whereKey($model->getKey())->exists(); } public function rate($model, $score) { if ($this->hasRated($model)) { return false; } $this->ratings($model)->attach($model->getKey(), [ 'score' => $score, 'ratable_type' => get_class($model), ]); return true; } }
¿Cómo implementar el trait CanBeRated y calcular promedio?
trait CanBeRated { public function qualifiers($model = null) { $modelClass = $model ? get_class($model) : $this->getMorphClass(); return $this->morphToMany( $modelClass, 'ratables', 'ratings', 'ratable_id', 'qualifier_id' )->withPivot('score') ->wherePivot('ratable_type', $this->getMorphClass()); } public function averageRating($modelClass = null) { return (float) ($this->qualifiers($modelClass)->avg('score') ?? 0); } }
¿Cómo integrarlo en modelos y pruebas?
Palabras clave y habilidades trabajadas:
¿Te gustaría que también los usuarios se califiquen entre sí? Comenta cómo lo implementarías y qué validaciones agregarías.
Con Eloquent puedes construir un sistema de calificaciones flexible y escalable sin duplicar lógica. Aquí verás cómo las relaciones polimórficas permiten que una sola tabla pivot conecte múltiples modelos, cómo estructurar la migración, el modelo Rating y dos traits reutilizables: CanRate y CanBeRated. El resultado: un único flujo para puntuar productos, páginas o categorías con pruebas que pasan.
Las relaciones polimórficas permiten que un modelo pertenezca a más de un tipo de modelo. Con un ejemplo de rating, un usuario o invitado puede puntuar un producto, una página o una categoría con una sola relación. Así evitas múltiples tablas intermedias y repeticiones.
Primero se crea la migración para la tabla intermedia y luego el modelo Rating que extiende Pivot. Esto habilita timestamps, score y los tipos polimórficos.
Schema::create('ratings', function (Blueprint $table) {
$table->id();
$table->morphs('ratable'); // ratable_id, ratable_type
$table->morphs('qualifier'); // qualifier_id, qualifier_type
$table->unsignedTinyInteger('score');
$table->timestamps();
});
use Illuminate\Database\Eloquent\Relations\Pivot;
class Rating extends Pivot
{
public $incrementing = true;
protected $table = 'ratings';
public function ratable() { return $this->morphTo(); }
public function qualifier() { return $this->morphTo(); }
}
Para mantener el diseño flexible, se usan traits: uno para quienes califican (CanRate) y otro para quienes son calificados (CanBeRated). Allí se centraliza la relación morphToMany, alias, timestamps, columnas pivot y filtros por tipo.
trait CanRate
{
public function ratings($model = null)
{
$modelClass = $model ? get_class($model) : $this->getMorphClass();
return $this->morphToMany(
$modelClass,
'qualifiers',
'ratings',
'qualifier_id',
'ratable_id'
)->as('rating')
->withTimestamps()
->withPivot('score', 'ratable_type')
->wherePivot('ratable_type', $modelClass)
->wherePivot('qualifier_type', $this->getMorphClass());
}
public function hasRated($model)
{
return $this->ratings($model)->whereKey($model->getKey())->exists();
}
public function rate($model, $score)
{
if ($this->hasRated($model)) {
return false;
}
$this->ratings($model)->attach($model->getKey(), [
'score' => $score,
'ratable_type' => get_class($model),
]);
return true;
}
}
trait CanBeRated
{
public function qualifiers($model = null)
{
$modelClass = $model ? get_class($model) : $this->getMorphClass();
return $this->morphToMany(
$modelClass,
'ratables',
'ratings',
'ratable_id',
'qualifier_id'
)->withPivot('score')
->wherePivot('ratable_type', $this->getMorphClass());
}
public function averageRating($modelClass = null)
{
return (float) ($this->qualifiers($modelClass)->avg('score') ?? 0);
}
}
Palabras clave y habilidades trabajadas:
¿Te gustaría que también los usuarios se califiquen entre sí? Comenta cómo lo implementarías y qué validaciones agregarías.
Esta clase si me decepcionó, no tiene una buena explicación y es mucho código sin sentido.
Dejo el repositorio del proyecto con Laravel 8:
Ok, voy a intentar dar mi explicación de lo que está pasando en esta clase, es importante tener en cuenta que se establecerá una relación polimorfa muchos a muchos (MaM) por lo que tienen que tener nociones de la relación MaM, para entender las relaciones polimorfa les dejó el siguiente video que encontré dentro de los aportes de esta clase y utiliza un ejemplo de relaciones polimorfas uno a muchos: relaciones polimorfas uno a muchos.
.
Primera parte - Relación a establecer
Primero que nada hay que entender que es lo que vamos a relacionar a nivel de base de datos, esto es una entidad que pueda calificar (un usuario, un invitado, una sesión, etc) con una entidad que pueda ser calificada (producto, categoría, etc.), como muchos usuarios podrían calificar muchos productos y muchos productos podrían ser calificados por muchos usuarios vemos que se forma una relación MaM, según teoría de bases de datos relacionales, esta relación se puede alcanzar mediante tres tablas - una tabla usuarios, una tabla productos y una tabla intermedia o pivote ratings.
.
Segunda parte - Migración de tabla Ratings
Esta tabla tendrá 5 columnas importantes, el score, rateable_type, rateable_id, qualifier_type y qualifier_id, los últimos cuatro pueden inferirse por las instrucciones $table->morphs('rateable') y $table->morphs('qualifier'). Así que, ¿qué significan esas columnas?, bueno si vieron el video del inicio de mi comentario espero hayan entendido que se rateable_type y qualifier_type hacen referencia al modelo que recibe calificación y al modelo que dictó dicha calificación, respectivamente - y rateable_id/qualifier_id hacen referencia a el registro especifico dentro de la tabla de cada type que recibe/dicta la calificación.
.
El modelo Rating extenderá de Pivot y no de Model puesto que como ya expliqué nuestra tabla es una de tipo intermedia o pivote, no se porqué se indica el uso de campos auto-incrementables (lo siento :(), y se indica la tabla a la que nos relacionaremos. Después es como definir cualquier relación de Laravel, tenemos que indicar el nombre del método para acceder a la relación y retornar la relación polimorfa que no toma argumentos porque como puede relacionarse a varios Modelos la función MorphTo se encarga de determinarlo.
.
Aquí es donde el titulo del curso toma sentido jeje, en un sistema diferente a este (llamemosle un sistema normal) lo que se haría sería establecer la otra parte de la relación polimorfa en los modelos específicos, por ejemplo en el caso del modelo User describiríamos la relación de la siguiente forma:
public function ratings()
{
return $this->morphMany( 'App\Models\Rating', 'qualifier' );
}
lo cual le indicaría a Laravel como establecer la relación polimorfa desde Usuario con Rating. En un sistema como el del profe (llamemoslo un sistema avanzado) quisiéramos "volverlo más flexible" citando a Sergio, por lo que podemos tomar ventaja de los Traits para delegar la responsabilidad de establecer la otra parte de la relación polimorfa a una clase en especifico y así poder darle la posibilidad a cualquier clase que queramos de poder calificar, si quieren saber más acerca de que es, como hacer y como usar un Trait les dejo el siguiente enlace: Traits. . Quinta parte - Trait Can Rate Bueno, ahora que entendemos un poco la motivación de porque se usará un Trait esta parte es la que más me costó jeje,el método ratings de la clase CanRate es lo que referí como la otra parte de la relación polimorfa ya que se encargará de relacionar el modelo que le pasen por parámetro dentro de la tabla ratings usando el qualifier_id y rateable_id como llaves foráneas para después retornar los ratings (con timestamps) donde el rateable_type sea el modelo objetivo y el qualifier_type sea el modelo que invoco la relación. Bueno, ahora ¿Cómo invocaremos la relación? con el método rate del trait CanRate, lo que hace éste método es tomar dos parámetros, modelo y calificación, el primero hará referencia que modelo se va a calificar con el puntaje que se pasa en calificación, por lo que al momento de invocar la relación ratings del Trait CanRate podemos encadenar el método attach (más información del método attach) para básicamente indicar que se escriba en la tabla pivote el id que tenga el modelo relacionado al score ya antes mencionados. . espero haberles ayudado y no confundido más, jeje.
Si saben ingles este es una muy buena explicación de las relaciones polimórficas: https://www.youtube.com/watch?v=rx1DQBE01b0
Por más avanzado que sea un curso, a este le falta demasiada planeación, estructuración y enriquecimiento de conceptos del framework. Es importante resaltar que el profesor es bueno pero se nota que el TEAMPLATZI sacó este curso de afán.
A este curso le falta más amor.
Links al ejemplo mas sencillo de relaciones polimorficas: https://laravel.com/docs/7.x/eloquent-relationships#polymorphic-relationships
He hecho ya alguna vez de relaciones polimórficas de muchos a muchos y les aseguro que no es tan complejo, además, el profesor no sigue convenciones que recomienda laravel, y aunque no sea algo obligatorio es una línea que se sigue para no desorientar a muchas personas que vienen por esa línea. LA VERDAD MUY DECEPCIONANTE ESTE CURSO, EL DE API REST BASICO FUE MUCHO MAS INTERESANTE GRACIAS AL PROFESOR QUE SE ESMERÓ EN EXPLICAR DE FORMA CLARA
caramba, no esta muy clara la clase !

a este punto no sé si dejar el curso aquí e ir a tomar el de Eloquent y luego regresar … o continuar …
La verdad es que he tenido que arreglar muchos errores, por que el profesor se salta mucho código y recurrir a repositorios de los que ya han hecho este curso para entender bien... y este tema tan enredado que me dan ganas de dejar el curso hasta aqui..
que complejo me ha parecido