Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Rating Laravel Package

ghanem/rating

Ghanem Rating is a flexible Laravel package for adding ratings and reactions to any Eloquent model. It supports multiple rating types, average score calculations, user-specific ratings, and simple integration with Blade views and APIs. Ideal for star ratings, likes, and other feedback.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require ghanem/rating

On Laravel 5.5+ the Ghanem\Rating\RatingServiceProvider is auto-discovered. On older versions add it to config/app.php. Then publish the migration (and config, if present):

php artisan vendor:publish --provider="Ghanem\Rating\RatingServiceProvider"
php artisan migrate

Attach the trait to the model being rated

use Ghanem\Rating\Traits\Rating;

class Post extends Model
{
    use Rating;
}

Where to look first

  1. vendor/ghanem/rating/src/Traits/Rating.php — this is the entire public API surface; read it before writing anything (the package is small enough to audit in five minutes).
  2. The published migration — confirm the column names (rating vs value), the polymorphic rateable_type / rateable_id columns, and whether a unique index on (user_id, rateable_type, rateable_id) exists.
  3. config/rating.php if published — model bindings and table names are usually swappable here.

First use case

The canonical flow is a logged-in user rating a post and the view rendering an average:

// Controller
public function rate(Post $post, Request $request)
{
    $request->validate(['rating' => 'required|integer|min:1|max:5']);

    $post->rating($request->rating, $request->user());

    return back();
}

Then expose $post->averageRating() and $post->ratingCount() (or the equivalent accessors on your installed version) in the Blade view as a star widget. Because the trait is polymorphic, the same code works on Product, Article, Comment — no per-model table changes.

Implementation Patterns

One rating per user, updated not duplicated. Treat rating as idempotent: a second submission should update the existing row rather than inflate the count. If the package's rating() method always inserts, wrap it:

$existing = $post->ratings()->where('user_id', $user->id)->first();
if ($existing) {
    $existing->update(['rating' => $value]);
} else {
    $post->rating($value, $user);
}

Better still, add a unique composite index and a dedicated updateRating() helper on your model so controllers never branch.

Explicit user argument over ambient auth. Pass $request->user() (or an explicit User instance) rather than relying on the package resolving auth()->user(). This keeps the same code path working for API token guards, impersonation, admin backfills, and seeders.

Avoid N+1 on index pages. A listing of 25 posts that each calls an aggregate method fires 25+ queries. Prefer:

$posts = Post::withCount('ratings')
    ->withAvg('ratings', 'rating')
    ->paginate(25);

This requires that the trait's relation is named consistently (typically ratings()); if not, define your own ratings() relation in the model and let the package methods delegate to it.

Sort by rating. Since averages are computed across rows, sort with a subquery join or a denormalized average_rating / ratings_count column on the parent table updated in a model observer on the Rating model. For anything beyond a hobby app, the denormalized column is the right call — it also makes filtering and pagination cheap.

API endpoint pattern.

Route::post('/posts/{post}/ratings', [RatingController::class, 'store'])
    ->middleware(['auth:sanctum', 'throttle:20,1']);

Return { average: 4.2, count: 31, mine: 5 } so the front end can update the widget without a refetch.

Authorization and eligibility. The package will happily let a user rate their own post. Add a policy or a guard clause:

abort_if($post->user_id === $request->user()->id, 403);

Testing. Use RefreshDatabase, create two users and a post, submit ratings, and assert the average and count. Because the surface is tiny, an integration test against the trait is more valuable than mocking anything. Factories for the Rating model let you seed realistic averages in feature tests.

Caching. Averages are read far more than written. Cache averageRating() per model with a short TTL and bust it in the Rating model's saved/deleted events.

Gotchas and Tips

  • Confirm the method names before committing. The package has had multiple releases and the trait's public methods (rating(), averageRating(), ratingCount(), ratings()) are not a large, stable, documented API. Grep the installed source once and add a thin wrapper service or model helper around it so an upgrade touches one file, not fifty call sites.
  • Migration republishing duplicates tables. vendor:publish copies the migration with a timestamp; running it twice creates a second ratings table migration that will fail. Publish once, commit the file, and edit it in place thereafter.
  • Morph map breakage. If you later call Relation::enforceMorphMap(), existing rows storing fully-qualified class names in rateable_type will no longer resolve. Either adopt the morph map from day one or write a data migration.
  • Orphaned ratings. There is usually no cascade from users to ratings. Add ->onDelete('cascade') on the user_id foreign key, or clean up in a deleting observer on User.
  • Integer vs decimal storage. Check whether the migration stores the rating as tinyint/int or decimal. Averages come back as floats or strings depending on the driver; cast explicitly ((float)) before formatting in Blade, and round at the display layer only.
  • No built-in rate limiting or spam protection. Add throttle middleware and a unique index; otherwise a determined user can skew an average with repeated submissions.
  • Auth guard assumptions. If the package defaults to auth()->id(), queue jobs, artisan commands, and unauthenticated API routes will all resolve null and either error or insert a rating with user_id = null. Always pass the user explicitly.
  • $model->rating() collision. If your model already has a rating attribute or method, the trait will conflict. Rename your column or alias the trait method (use Rating { rating as rateModel; }).
  • Extension points are limited, so fork-friendly. With 87 stars and zero dependents the package is unlikely to grow a plugin system. The practical approach is to publish the migration, extend the Rating model in config/rating.php, and override trait methods in your own Rateable trait if you need custom behavior — the source is small enough to own.
  • Pin the version. Add an explicit constraint ("ghanem/rating": "^1.0") rather than relying on *, since the sparse release history makes breaking changes hard to predict.
  • Debugging. When averages look wrong, drop to the query layer: DB::table('ratings')->where('rateable_id', $post->id)->get() tells you immediately whether the problem is duplicate rows, null users, or the aggregate method.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
cgv/localized-resource