active-record
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
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();
How do you expect the entry in the relation table to be created when the model has no primaryKey value assigned?
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!
closed in favor of https://github.com/yiisoft/yii2/issues/4834
@dynasource this issue is #4834 :)
:), if this was an algorithm, all the issues would have been closed at once
https://github.com/yiisoft/yii2/pull/15882