PLATFORM
  • Tails

    Create websites with TailwindCSS

  • Blocks

    Design blocks for your website

  • Wave

    Start building the next great SAAS

  • Pines

    Alpine & Tailwind UI Library

  • Auth

    Plug'n Play Authentication for Laravel

  • Designer comingsoon

    Create website designs with AI

  • DevBlog comingsoon

    Blog platform for developers

  • Static

    Build a simple static website

  • SaaS Adventure

    21-day program to build a SAAS

Question By
Unsolved

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'characters.duel_id' in 'where clause'

ookma-kyi

Jul 1st, 2024 01:58 PM

I am getting the following error:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'characters.duel_id' in 'where clause'

select * from characters where characters.duel_id in (1) and characters.deleted_at is null

Here is my duel model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Duel extends Model
{
    use HasFactory;

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

Here is the migration:

<?php

use App\Models\Character;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('duels', function (Blueprint $table) {
            $table->id();
            $table->foreignIdFor(Character::class, "challenger_id")->constrained("characters");
            $table->foreignIdFor(Character::class, "opponent_id")->constrained("characters");
            $table->tinyInteger("status");
            $table->timestamps();
        });

        // Add the CHECK constraint
        DB::statement('ALTER TABLE duels ADD CONSTRAINT status_is_valid_range CHECK (status IN (-4, -3, -2, -1, 0, 1))');
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('duels');
    }
};

Here is the duel resource:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class DuelResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'challenger_id' => $this->whenLoaded('character', fn () => CharacterResource::make($this->character)),
            'opponent_id' => $this->whenLoaded('character', fn () => CharacterResource::make($this->character)),
            'status' => $this->status,
        ];
    }
}

Here is my character model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;

class Character extends Model
{
    use HasFactory;
    use SoftDeletes;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'user_id',
        'active',
        'name',
        'belt_id',
    ];

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

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

Here is the character migration:

<?php

use App\Models\Belt;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('characters', function (Blueprint $table) {
            $table->id();
            $table->foreignIdFor(User::class)->constrained();
            $table->boolean("active")->default(false);
            $table->string("name", 15);
            $table->unsignedInteger("xp")->default(0);
            $table->foreignIdFor(Belt::class)->constrained();
            $table->unsignedMediumInteger("wins")->default(0);
            $table->unsignedMediumInteger("loses")->default(0);
            $table->unsignedMediumInteger("draws")->default(0);
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('characters');
    }
};

Here is the character resource:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class CharacterResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'xp' => $this->xp,
            'belt' => $this->whenLoaded('belt', fn () => BeltResource::make($this->belt)),
            'wins' => $this->wins,
            'loses' => $this->loses,
            'draws' => $this->draws,
            'active' => $this->active,
        ];
    }
}

Finally here is the index action of the duel controller:

<?php

namespace App\Http\Controllers;

use App\Http\Resources\DuelResource;
use App\Models\Duel;
use Illuminate\Http\Request;

class DuelController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        // get the character's duels
        $duels = Duel::with('character')->
        where('challenger_id', auth()->user()->id)
            ->get();

        dd($duels);

        return inertia('Duels/Index', [
            'duels' => fn() => DuelResource::collection($duels)
        ]);
    }

and the Vue:

<template>
    <AppLayout title="My Duels">
            <div class="grid row mt-5">
                <div class="col-span-6 m-auto">
                    <div class="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
                        <h1 class="text-center mb-3">
                            <i aria-hidden="true"></i>  Duels
                        </h1>
                        <div v-if="duels.length === 0">
                            <p>You have no duels, why not challenge someone?</p>
                            <Link :href="route('')" class="inline-block border border-blue-500 rounded py-1 px-3 bg-blue-500 text-white" role="link" title="Create a new character">Create a new character</Link>
                        </div>
                        <table v-else class="table-auto border-spacing-2 text-center">
                            <thead>
                            <tr>
                                <th scope="col">Opponent</th>
                                <th scope="col">Options</th>
                            </tr>
                            </thead>
                            <tbody>
                            <tr v-for="duel in duels" :key="duel.id">
                                <th scope="row">{{ duel.challenger_id }}</th>
                                <td>
                                    <Link :href="route('')" class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white focus:outline focus:outline-2 focus:rounded-sm focus:outline-red-500">Cancel</Link>&nbsp;
                                </td>
                            </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
    </AppLayout>
</template>
<script setup>
import AppLayout from "@/Layouts/AppLayout.vue";
import {Link} from "@inertiajs/vue3";

defineProps(['duels']);
</script>

2 Characters fight each other in a single duel. However a character can participate in many duels at once. Here is a video of the now defunt game with 2 player characters fighting each other which should explain the concept of a duel. I tried asking my friend Larry QuickDraw which suggested pivot tables. When I tried to prototype this in Django I didn't need to use pivot tables so the concept of pivot tables is forign to me. Any ideas?

bobbyiliev

Jul 2nd, 2024 01:30 AM

Hi,

Based on the error message and the code you've provided, it seems the main issue is that there's a mismatch between your database schema and your model relationships.

The error "Unknown column 'characters.duel_id' in 'where clause'" suggests that your query is trying to access a 'duel_id' column in the 'characters' table, which doesn't exist.

From your migrations, it's clear that the 'duels' table has foreign keys to the 'characters' table (challenger_id and opponent_id), not the other way around. This means a Duel belongs to two Characters, not that a Character has many Duels.

Your current setup in the Duel model is incorrect. Instead of hasMany, you should use belongsTo for both challenger and opponent.

Here's how you can fix this:

  1. Update the Duel model:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Duel extends Model
{
    use HasFactory;

    public function challenger(): BelongsTo
    {
        return $this->belongsTo(Character::class, 'challenger_id');
    }

    public function opponent(): BelongsTo
    {
        return $this->belongsTo(Character::class, 'opponent_id');
    }
}
  1. Update the Character model to add the inverse relationships:
public function challengerDuels(): HasMany
{
    return $this->hasMany(Duel::class, 'challenger_id');
}

public function opponentDuels(): HasMany
{
    return $this->hasMany(Duel::class, 'opponent_id');
}
  1. Update your DuelController index method:
public function index()
{
    $userId = auth()->id();
    
    $duels = Duel::with(['challenger', 'opponent'])
        ->where('challenger_id', $userId)
        ->orWhere('opponent_id', $userId)
        ->get();

    return inertia('Duels/Index', [
        'duels' => DuelResource::collection($duels)
    ]);
}
  1. Update your DuelResource:
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'challenger' => CharacterResource::make($this->whenLoaded('challenger')),
        'opponent' => CharacterResource::make($this->whenLoaded('opponent')),
        'status' => $this->status,
    ];
}
  1. In your Vue component, update how you're displaying the duel information:
<tr v-for="duel in duels" :key="duel.id">
    <th scope="row">{{ duel.opponent.name }}</th>
    <td>
        <Link :href="route('duel.cancel', duel.id)" class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white focus:outline focus:outline-2 focus:rounded-sm focus:outline-red-500">Cancel</Link>&nbsp;
    </td>
</tr>

You don't need pivot tables for this scenario because the relationship is directly established through foreign keys in the duels table.

- Bobby