How to have BelongsToMany with multiple relations in the backend?

Hello,
I have models domain, domain_view and view.

In domain I have this realations

public $belongsToMany = [

        'views' => [
            \QBUT\Manager\Models\View::class,
            'table' => 'qbut_manager_domain_view',
            'pivot' => ['id', 'position', 'position_detail', 'active'],
            'pivotSortable' => 'sorting',
            'timestamps' => true
        ],
    ];

In the backend when I add a View model with ID 1 to the a Domain model I can only add that ID 1 View once. Why is that? Why can’t I have multiple record of the same View?
Maybe I used the wrong relationship?
I tried to add the same record manually in the database and it works and I don’t see any errors. However when I delete one type all the same View types get deleted, so there’s obviously a constraint there.

I think this is not related to how the database or relationships are set but more about how the backend is set?
Still trying to figure out how and why this the default behavior?
Why does it assume I can only have one specific View model related for each Domain model?

Hi sandros87

one unusual thing I see in your relationshop configuration, is that you included the ‘id’ in your pivot columns list. Remove that in your Domain model and the counterpart, your View model, and check if it works now.

Hi, it doesn’t change the behavior whatsoever. I can still add (in the UI) one view model once.

Well, then I would like to see the rest of your configuration:

  • relationship definitions in the models
  • relation controller definition
  • table schema for both model tables and the intermediary table

because the problem should be somewhere in there.

Just to make sure: you’re on the latest OCMS version, right?

So you think this isn’t a standard behavior and could not happen?
Just wanna add that when I select a Model View I can’t select it anymore from the list (disappears) so it’s totally intended behavior.
I’m running 3.5.5.

Here’s the configuration
Domain Model

    public $belongsToMany = [
        'views' => [
            \qbut\manager\Models\View::class,
            'table' => 'qbut_manager_domain_view',
            'pivot' => ['position', 'position_detail', 'active'],
            'pivotSortable' => 'sorting',
            'timestamps' => true
        ]
    ];

View Model

No relation definition

Domain View Model

    public $belongsTo = [
        'domain' => [
            \qbut\manager\Models\Domain::class,
        ],
        'view' => [
            \qbut\manager\Models\View::class,
        ],
    ];

Domain Controller

class Domain extends Controller
{
    public $implement = [        'Backend\Behaviors\ListController',        'Backend\Behaviors\FormController', 'Backend\Behaviors\RelationController'    ];
    
    public $listConfig = 'config_list.yaml';
    public $formConfig = 'config_form.yaml';
    public $relationConfig = 'config_relation.yaml';
}

Domain config_relation.yaml

views: 
  structure:
    showReorder: true
    showTree: false
  label: Views
  view: 
    list: 
      columns:
        name: 
          label: Name
        pivot[position]:
          label: Position
          type: text
        pivot[position_detail]:
          label: Position detail
          type: text
        pivot[active]:
          label: Active
          type: switch
  manage:
    list: $/qbut/manager/models/view/columns.yaml
  pivot:
    form:
      fields:
        name: 
          label: Name
          disabled: true
        pivot[position]:
          label: Position
          span: full
          type: text
          default: head
        pivot[position_detail]:
          label: Position detail
          span: full
          type: dropdown
          options:    
            prepend: prepend
            append: append
        pivot[active]:
          label: Active
          default: 1
          span: left
          type: checkbox
    

Schemas

mysql> describe qbut_manager_domain;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int unsigned | NO   | PRI | NULL    | auto_increment |
| domain_name | varchar(255) | NO   |     | NULL    |                |
| created_at  | timestamp    | YES  |     | NULL    |                |
| updated_at  | timestamp    | YES  |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+
mysql> describe qbut_manager_domain_view; (intermediate)
+-----------------+--------------------------+------+-----+---------+----------------+
| Field           | Type                     | Null | Key | Default | Extra          |
+-----------------+--------------------------+------+-----+---------+----------------+
| position        | varchar(255)             | NO   |     | head    |                |
| active          | tinyint(1)               | NO   |     | 1       |                |
| sorting         | int                      | YES  |     | NULL    |                |
| view_id         | int                      | NO   |     | NULL    |                |
| domain_id       | int                      | NO   |     | NULL    |                |
| created_at      | timestamp                | YES  |     | NULL    |                |
| updated_at      | timestamp                | YES  |     | NULL    |                |
| id              | int unsigned             | NO   | PRI | NULL    | auto_increment |
| position_detail | enum('prepend','append') | NO   |     | prepend |                |
+-----------------+--------------------------+------+-----+---------+----------------+
mysql> describe qbut_manager_view;
+-----------------+--------------+------+-----+---------+----------------+
| Field           | Type         | Null | Key | Default | Extra          |
+-----------------+--------------+------+-----+---------+----------------+
| id              | int unsigned | NO   | PRI | NULL    | auto_increment |
| name            | varchar(255) | NO   |     | NULL    |                |
| filename        | varchar(255) | NO   |     | NULL    |                |
| created_at      | timestamp    | YES  |     | NULL    |                |
| updated_at      | timestamp    | YES  |     | NULL    |                |
| category_id     | int          | YES  |     | NULL    |                |
| is_cookiebanner | tinyint(1)   | NO   |     | 0       |                |
| auto_create     | tinyint(1)   | NO   |     | 0       |                |
+-----------------+--------------+------+-----+---------+----------------+

Ok ok, I think I see what is going on.

Domain View Model is intended to act as a Pivot Model to connect Domain Model and View Model.
I think you’re set up now is somewhere inbetween, because its not all propery declared.

Check out the docs here to see how to use a custom pivot model: Relationships - October CMS - 3.x

If you would like to define a custom model to represent the intermediate table of your relationship, you may use pivotModel attribute when defining the relationship. Custom many-to-many pivot models should extend the October\Rain\Database\Pivot class while custom polymorphic many-to-many pivot models should extend the October\Rain\Database\MorphPivot class.

And I would also set up the inverse relationship in the View model. I’m honestly never not doing inverse relationships, so im not actually sure if it’s really neccessary. But shouldn’t hurt.

Hi,
ok so now that I created a custom pivot Model and used that in relationship what should I expect? Cause I’m seeing the same behavior.

Domain.php

    public $belongsToMany = [
        'views' => [
            \qbut\manager\Models\View::class,
            'pivotModel' => \qbut\manager\Models\DomainViewPivot::class,
            'table' => 'qbut_manager_domain_view',
            'pivot' => ['position', 'position_detail', 'active'],
            'pivotSortable' => 'sorting',
            'timestamps' => true
        ],
    ];

DomainViewPivot.php

<?php namespace qbut\manager\Models;

/**
 * Model
 */
class DomainViewPivot extends \October\Rain\Database\Pivot
{
    use \October\Rain\Database\Traits\Validation;

    /**
     * @var string The database table used by the model.
     */
    public $table = 'qbut_manager_domain_view';

    /**
     * @var array Validation rules
     */
    public $rules = [
    ];
}

Still don’t understand what is the advantage (database wise) for this constrain.

You maybe meant that once I have the custom Pivot model I can make custom behaviors?