TOA_Anakin's avatar

Eloquent manyToMany relationship fails on SQL

I have been struggling to make this happen, but every time I failed.

I have two models - Map and User, and both can have many Maps (or users, respectively). The thing is, map has a primary key slug, while User has primary key id and I want to create a pivot table that connects these two models. Here is what I got so far:

Schemes:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->string('password')->nullable();
    $table->enum('role', \App\Enum\Role::toArray());
    $table->timestamps();
});

Schema::create('maps', function (Blueprint $table) {
    $table->string('name');
    $table->string('slug')->primary();
    $table->string('perex')->nullable();
    $table->string('categories')->nullable();
    $table->enum('company', \App\Enum\Company::toArray());
    $table->string('icon')->nullable();
    $table->boolean('is_public')->default(false);
    $table->boolean('bounds')->default(false);
    $table->timestamps();
});

Schema::create('map_user', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained();
    $table->string('map_slug');

    $table->foreign('map_slug')->references('slug')->on('maps');
});

Map.php

class Map extends Model
{
    protected $primaryKey = 'slug';

    protected $keyType = 'string';

    protected $fillable = [
        'name',
        'slug',
        'perex',
        'categories',
        'company',
        'icon',
        'is_public',
        'bounds',
    ];

    public function suppliers(): Collection
    {
        return $this->hasMany(Supplier::class)->get();
    }

    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'map_user', 'map_slug', 'user_id');
    }
}

User.php

class User extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    public function maps(): BelongsToMany
    {
        return $this->belongsToMany(Map::class, 'map_user', 'user_id', 'map_slug');
    }
}

When creating a new Map, I want to attach the user to it:

/**
 * Store a newly created resource in storage.
*/
public function store(StoreMapRequest $request): RedirectResponse
{
   $user = User::where('email', auth()->user()->getAuthIdentifier())->firstOrFail();

   $map = Map::create([
      'name' => $request->input('name'),
      'slug' => $request->input('slug'),
      'perex' => $request->input('perex'),
      'categories' => $request->input('categories'),
      'company' => $request->enum('company', Company::class),
      'icon' => $request->input('icon'),
      'is_public' => $request->boolean('isPublic'),
      'bounds' => $request->boolean('bounds'),
   ]);

   $map->users()->attach($user->id);

   return to_route('admin.dashboard')->with('success', 'Map created!');
}

and on the ->attach() method I get following error:

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`mapy-dek`.`map_user`, CONSTRAINT `map_user_map_slug_foreign` FOREIGN KEY (`map_slug`) REFERENCES `maps` (`slug`)) 

insert into
  `map_user` (`map_slug`, `user_id`)
values
  (0, 1)

Thank you for your help!

0 likes
3 replies
TOA_Anakin's avatar

The problem was with the code that was attaching the map to the user. It looks like the model instance was not stored to the DB yet, thus user had nothing to attach, hence the error.

I fixed it with

 Map::create([
            'name' => $request->input('name'),
            'slug' => $request->input('slug'),
            'perex' => $request->input('perex'),
            'categories' => $request->input('categories'),
            'company' => $request->enum('company', Company::class),
            'icon' => $request->input('icon'),
            'is_public' => $request->boolean('isPublic'),
            'bounds' => $request->boolean('bounds'),
        ]);

        $map = Map::where('slug', $request->input('slug'))->firstOrFail();

        $user->maps()->attach($map);

and it works like a charm.

Snapey's avatar
return $this->belongsToMany(User::class, 'map_user', 'map_slug', 'user_id');

User does not have a user_id column

Please or to participate in this conversation.