1

I've been trying to create a Many To Many (Polymorphic) system that saves the state of every update done by a specific user on specific Models (for instance: Company, Address, Language models) with a note field and the updated_by. The goal is to keep track of who updated the model and when, and the note field is a text field that states where in the system the model was updated.

Below is what I created, but I'm open to getting a different solution that, in the end, allows me to accomplish the goal described above.

I've created the model Update (php artisan make:model Update -m) with the migration:

/**
 * Run the migrations.
 *
 * @access public
 * @return void
 * @since
 */
public function up()
{
    Schema::create('updates', function (Blueprint $table) {
        $table->id()->comment('The record ID');
        $table->bigInteger('updatable_id')->unsigned()->comment('THe model record id');
        $table->string('updatable_type')->comment('THe model name');
        $table->string('note')->nullable()->comment('The record note');
        $table->integer('updated_by')->unsigned()->nullable()->comment('The user ID which updated the record');
        $table->timestamps();
        $table->softDeletes();
    });
}

On the model Update all the $fillable, $dates properties are standard, and the method of the Update model:

class Update extends Model
{
    /**
     * Method to morph the records
     *
     * @access public
     */
    public function updatable()
    {
        return $this->morphTo();
    }
}

After trying several ways on the different models, my difficulty is getting the relation because when I save to the updates table, it saves correctly. For instance, in the Company model: Company::where('id', 1)->with('updates')->get(); as in the model Company I have the method:

public function updates()
{
    return $this->morphToMany(Update::class, 'updatable', 'updates')->withPivot(['note', 'updated_by'])->withTimestamps();
}

Most certainly, I'm doing something wrong because when I call Company::where('id', 1)->with('updates')->get(); it throws an SQL error "Not unique table/alias".

Thanks in advance for any help.

McRui
  • 1,879
  • 3
  • 20
  • 31
  • Have you considered `update` is a reserved name in SQL? – dbf Dec 25 '22 at 11:48
  • Hmmm... that's a good point I did not consider. I'll try to change the name and post it here if it works. Thanks. – McRui Dec 25 '22 at 11:51
  • Changed the Update model name to TrackChange (table: track_changes) set to trackable and the same error. – McRui Dec 25 '22 at 12:03
  • The only problem I see is the confusion with `morphToMany` while using a table name called `updates`. The `morphMany` would use a table name called `updates`, `morphToMany` would be using the table name `updatables`. So at the end you are using two different tables with the same name, hence `(Update::class, 'updatable', 'updates')` should be using the `updatables` table, not the `updates` table. If you would just return `$this->morphToMany(Update::class, 'updatable')` it will try to reach a table called `updateables` where `morphMany(Update::class, 'updatable')` should work with your setup. – dbf Dec 25 '22 at 12:32
  • 1
    I'll try to conclude this in an answer .. – dbf Dec 25 '22 at 12:41

1 Answers1

1

The problem I see here is using morphToMany instead of morphMany.

return $this->morphToMany(Update::class, 'updateable', 'updates'); will use the intermediate table (and alias) updates (3rd argument) instead of using the default table name updateables. Here it will clash with the table (model) updates, so it will produce the error you are receiving.

return $this->morphMany(Update::class, 'updateable'); will use the table updates and should work with your setup.

Do notice that morphMany does not work with collecting pivot fields (e.g. withPivot([..]), it's not an intermediate table. Only morphToMany does.

dbf
  • 3,278
  • 1
  • 24
  • 34
  • 1
    First, thank you for your valuable help @dbf! Made those changes, and suddenly I found one small error in my fields. I had `updatable_id` instead of `updateable_id` and `updatable_type` instead of `updateable_type`. It was missing the 'e'. Now it works. Thank you! – McRui Dec 25 '22 at 13:14