3

I'm working on a website for a simracing hotlapping competition. Participants can submit an unlimited amount of lap times for a specific round in a specific season, and admins can either approve or deny these lap times. The fastest approved lap time for each participant is shown on that round's leaderboard.

In case it's relevant, I'm using this package to generate Snowflake IDs instead of incremental IDs or GUIDs.

I'm using a pretty simple enum for the lap time status;

enum LapTimeStatus: int
{
    case SUBMITTED = 0;
    case APPROVED = 1;
    case DENIED = 2;
}

In the LapTime model I cast this field to the enum, and I've of course added the relationship to the Round model;

class LapTime extends Model
{
    use HasFactory, Snowflake;

    protected $casts = [
        'status' => LapTimeStatus::class,
    ];

    public function round(): BelongsTo
    {
        return $this->belongsTo(Round::class);
    }
}

In the Round model, I've set up the inverse of the relationship, as well as a method to fetch the lap times for the aforementioned leaderboard;

class Round extends Model
{
    use HasFactory, Snowflake;  

    public function times(): HasMany
    {
        return $this->hasMany(LapTime::class);
    }

    public function timesForLeaderboard(): array
    {
        $times = $this->times()
                      ->with('user')
                      ->orderBy('lap_time')
                      ->where('status', LapTimeStatus::APPROVED)
                      ->get();

        return $times->unique('user_id')->values()->toArray();
    }
}

timesForLeaderboard gets all approved times for that round, orders them by laptime (laptimes are stored in milliseconds for easy sorting), and makes sure only one lap time for each user is selected.

However, the ->where('status', LapTimeStatus::APPROVED) line is causing issues. It works perfectly fine for the first season, however for whatever reason this line returns 0 models for each round, despite all of them having at least one approved lap time. I've been able to fix it by changing the line to ->where('status', (string) LapTimeStatus::APPROVED->value), but

  1. this seems like a band aid fix for a deeper issue, as it behaves correctly elsewhere and
  2. I'd have to make this change in a few other places, which I'd like to avoid.

Any idea what could be causing this inconsistent behaviour despite near-identical circumstances?

As requested, here's the DB schema

And as request as well, the LapTime table migration;

return new class extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('lap_times', function (Blueprint $table) {
            $table->unsignedBigInteger('id')->primary();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->foreignId('round_id')->constrained()->cascadeOnDelete();
            $table->unsignedBigInteger('lap_time');
            $table->string('video_url');
            $table->unsignedInteger('status')->default(LapTimeStatus::SUBMITTED->value);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('lap_times');
    }
};
Alex
  • 778
  • 3
  • 12
  • 27
  • 1
    Facinated to know WHY you would use the Snowflake instead of a simple AUTO_INCREMENT – RiggsFolly Feb 04 '23 at 13:29
  • Just for clarity, can we see the schema for the `Round` table please – RiggsFolly Feb 04 '23 at 13:32
  • @RiggsFolly I've attached the entire DB schema. As for snowflake IDs, I used to use AUTO_INCREMENT IDs but have read many times about possible issues with guessable IDs. I don't like the look of GUIDs, so I went with Snowflake IDs instead. – Alex Feb 04 '23 at 13:58
  • @Alex. think you should use ->where('status', (string) LapTimeStatus::APPROVED->value) .Better use scope so you can avoid repeating ->value – John Lobo Feb 04 '23 at 13:59
  • I could, yes, but I don't understand why I need to convert the value to a string, when `status` is an integer in the database, and it works without calling `->value` for other rounds/seasons. – Alex Feb 04 '23 at 14:01
  • @Alex php enum works like that way .not like other language. – John Lobo Feb 04 '23 at 14:01
  • LapTimeStatus::APPROVED will return array with name and value key App\Enums\LapTimeStatus { +name: "APPROVED" +value: 1 } – John Lobo Feb 04 '23 at 14:04
  • How come it works perfectly fine without the `(string)` and `->value` for other rounds/seasons though? In fact - this exact same code is currently working fine in production with the exact same data, the issue only exists on my local environment. – Alex Feb 04 '23 at 14:07
  • @Alex not sure .as per i know without ->value it wont get value .will wait some one to answer. – John Lobo Feb 04 '23 at 14:09
  • can you share migration for model Round ? – Win Feb 04 '23 at 14:55
  • @Win I've added the migration. – Alex Feb 04 '23 at 15:10
  • @Alex, thanks but I'm sorry, I meant LapTime migration where this status is stored – Win Feb 04 '23 at 15:24
  • @Win I was wondering why you wanted the `Round` migration, haha. I've updated the question again :) – Alex Feb 04 '23 at 15:29
  • i think your migration is already okay , status as unsigned integer, use enum in relationship `->where('status', LapTimeStatus::APPROVED->value)` , the same as inside your migration `->default(LapTimeStatus::SUBMITTED->value)` no need with added (string) – Win Feb 05 '23 at 09:18
  • That's the thing, without the `(string)` cast, it still doesn't work, with or without `->value`. – Alex Feb 05 '23 at 11:44
  • I use this package for enum in projects, perhaps you can try it as well, it support attribute casting , bensampo/laravel-enum – Win Feb 05 '23 at 16:45

0 Answers0