laravel-shift / blueprint

A code generation tool for Laravel developers.
MIT License
2.9k stars 276 forks source link

pivot / morph: add additional columns to intermediate table? #710

Closed wivaku closed 2 days ago

wivaku commented 2 days ago

What is the recommended way to add additional columns to intermediate tables?

E.g. movies are released in countries, and optionally have a local name in some countries. How can I add column local_name to the pivot table countries_movies?

models:
  Country:
    name: string
    relationships:
      belongsToMany: Movie

  Movie:
    name: string
    relationships:
      belongsToMany: Country # have the option to add column: local_name

I saw how I can customize the pivot table name (belongsToMany: Country:mytablename), but it is not clear to me if/how I can add additional columns.

proposed syntax

If it is not possible yet, would this work as proposed syntax?

  Movie:
    name: string
    relationships:
      belongsToMany: Country with local_name

Which then results in local_name being added to the migration for table countries_movies.

It can be added to either Country > relationships > belongsToMany: Movie or to Movie > relationships > belongsToMany: Country. If both of them contain with columns, then a merge is done of the two.

wivaku commented 2 days ago

Currently using intermediate model as mentioned in the Blueprint documentation, -combined with meta info (that is not documented?)- but that is not identical to what I am trying to achieve.

models:
  Country:
    name: string
    relationships:
      belongsToMany: Movie:&Local

  Movie:
    name: string
    relationships:
      belongsToMany: Country:&Local

  Local:
    meta:
      pivot: true
      table: country_movie
    country_id: id
    movie_id: id
    local_name: string # the extra field
    timestamps: false

This results in the correct migration files.
But for the models, it results in adding an additional intermediate model Local.

// Models/Movie.php

    public function countries(): BelongsToMany
    {
        return $this->belongsToMany(Country::class)
            ->using(Local::class)
            ->as('local')
            ->withPivot('id', 'local_name');
    }

For me there's no need for an additional model Local.
Would like to add local_name directly to the relationship, i.e.:

    public function countries(): BelongsToMany
    {
        return $this->belongsToMany(Country::class)
            ->withPivot('id', 'local_name');
    }
jasonmccreary commented 2 days ago

This is the correct behavior as you made an intermediate model. If you just want the last example code, you can update the migration and relationship afterward.

Blueprint is about balancing Laravel conventions and speed of generation. In the case of adding columns to a pivot table, without an intermediate model, the amount of typing is roughly the same.

wivaku commented 2 days ago

Yes, the amount of typing is roughly the same when I add it afterwards. For me the benefit would be that the intent is clear in a single blueprint file.

Instead of the original suggestion (which probably is not feasible),
it could be a variation of the current approach:

  Local:
    meta:
      pivot: true
      using: false #   <--  don't create intermediate model, and use direct relationships, without ->using(...)->as(...)
      table: country_movie
    country_id: id
    movie_id: id
    local_name: string # the extra field
    timestamps: false