yiisoft / yiisoft/active-record

ActiveRecord "link" requires additional "save" when linked "viaTable" otherwise "Unable to link models: both models must NOT be newly created" error

Open
#47 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
119
Forks
38
Avg merge
1h 11m
Merged PRs (30d)
1

Description

usually i link 1:n models with its "link" method.
if i use a pivot table, i have to use on more "save" command on the related model.
i am not sure if this is a bug or a feature, but i would find it more conclusive to avoid the additonal save.
here the example scenario:
setup db, creates tables "test", "testline" and "test_testline" (attention: drop table) :

DROP TABLE IF EXISTS `test`;
CREATE TABLE IF NOT EXISTS `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date1` date NOT NULL,
  `msg` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

INSERT INTO `test` (`id`, `date1`, `msg`) VALUES
(1, '0000-00-00', 'parent item');
DROP TABLE IF EXISTS `testline`;
CREATE TABLE IF NOT EXISTS `testline` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `test_id` int(11) NOT NULL,
  `info` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

INSERT INTO `testline` (`id`, `test_id`, `info`) VALUES
(1, 1, 'item #1'),
(2, 1, 'item #2');
DROP TABLE IF EXISTS `test_testline`;
CREATE TABLE IF NOT EXISTS `test_testline` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `test_id` int(11) NOT NULL,
  `testline_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_test_idx` (`test_id`),
  KEY `fk_testline_idx` (`testline_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;

INSERT INTO `test_testline` (`id`, `test_id`, `testline_id`) VALUES
(1, 1, 1),
(2, 1, 2);

ALTER TABLE `test_testline`
  ADD CONSTRAINT `fk_test` FOREIGN KEY (`test_id`) REFERENCES `test` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  ADD CONSTRAINT `fk_testline` FOREIGN KEY (`testline_id`) REFERENCES `testline` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;

Test-Model with two different relationships, doing almost the same thing:

class Test extends \yii\db\ActiveRecord
{
    public static function tableName()
    {
        return 'test';
    }

    public function rules()
    {
        return [
            [['msg'], 'string', 'max' => 255],
        ];
    }

    public function getTestlines()
    {
        return $this->hasMany(Testline::className(), ['id' => 'testline_id'])
            ->viaTable('test_testline', ['test_id' => 'id']);
    }

    public function getTestlinesWorking()
    {
        return $this->hasMany(Testline::className(), ['test_id' => 'id']);
    }
}

Testline Model:

class Testline extends \yii\db\ActiveRecord
{
    public static function tableName()
    {
        return 'testline';
    }

    public function rules()
    {
        return [
            [['info'], 'required'],
        ];
    }

    public function getTest()
    {
        //return $this->hasOne(Test::className(), ['id' => 'test_id']);
        return $this->hasOne(Test::className(), ['id' => 'test_id'])->viaTable('test_testline', ['testline_id' => 'id']);
    }


}

in a test view:

$model         = Test::findOne(1);
$newline       = new Testline;
$newline->info = 'created by code (direct link)';

// working direct link, should return n+1 rows
$transaction = $model->getDb()->beginTransaction();
$model->link('testlinesWorking', $newline);
foreach ($model->testlinesWorking as $key => $item) {
    var_dump($item->attributes);
}
$transaction->rollback();

// --- viaTable example
$newline       = new Testline;
$newline->info = 'created by code (linked viaTable)';

// not working viaTable if you don't use save before
$transaction = $model->getDb()->beginTransaction();

// UNCOMMENT THIS LINE TO MAKE IT WORK:
// $newline->save();
$model->link('testlines', $newline);
foreach ($model->testlines as $key => $item) {
    var_dump($item->attributes);
}
$transaction->rollback();

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the ActiveRecord link() path for relationships configured with viaTable(), using the supplied Test and Testline reproduction as the first check. Compare it with the direct hasMany link path and determine whether a newly created related model can be linked without an extra save. Done means the viaTable example succeeds with the additional save removed and existing linking behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
database
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.