active-record icon indicating copy to clipboard operation
active-record copied to clipboard

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

Open e-frank opened this issue 9 years ago • 6 comments

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();

e-frank avatar Aug 27 '14 08:08 e-frank

How do you expect the entry in the relation table to be created when the model has no primaryKey value assigned?

cebe avatar Aug 27 '14 18:08 cebe

sure you are right. i wished i found some automagic here and expected a 'linked save' to save the complete model-tree. [or the same behaviour with and without ->viaTable. with viaTable the child model is saved, without isn't.]

when you use ->link a second time, you will get an additional row in the relation table, and no error, if you don't have an unique index. i didn't find anything like a isLinked method, so what i think i have to do is test the parent relationship for an existing child by the child's primary key, like:

$hasChild = $parent->getRelation($relation)->andWhere('id=1')->exists();

and then decide if you need ->link. i am working on a hydrator and hope to publish it asap. still: fantastic work!

e-frank avatar Aug 28 '14 07:08 e-frank

closed in favor of https://github.com/yiisoft/yii2/issues/4834

dynasource avatar Oct 31 '16 20:10 dynasource

@dynasource this issue is #4834 :)

cebe avatar Nov 03 '16 15:11 cebe

:), if this was an algorithm, all the issues would have been closed at once

dynasource avatar Nov 03 '16 16:11 dynasource

https://github.com/yiisoft/yii2/pull/15882

samdark avatar Apr 23 '19 21:04 samdark