読者です 読者をやめる 読者になる 読者になる

hasManyなどで関連づけたテーブルにシーケンスがない場合のsaveAll

以下のようにCakePHPのCookbookに関連したテーブルへの保存方法が載ってますが、
PostgreSQLのシーケンス部分でハマってしまったのでメモ。

saveAll(array $data = null, array $options = array())

次のいずれかの目的で使用します。 (a) 単一のモデルに、個別のレコードを複数記録する。 (b) あるレコードと同様に、関連したレコードも全て記録する。

あるモデルのデータを保存するとき、それに関連付いているモデルのデータも一緒に処理されるということを意識しておいてください。新しい Post とそれに関連した Comments を保存する場合、保存処理の実行中には Post と Comment モデルを両方とも使用します。

流れは以下のような感じ

  1. ModelA hasMany ModelB
  2. ModelBはModelAのキーであるmodel_a_idをもつ(外部キーがmodel_a_id)
  3. ModelAのシーケンス名はmodel_a_id_seq
  4. ModelBはModelAのデータに従って保存されるのでシーケンス無し
  5. ModelA->saveAll()を使ってModelAとModelBの両方を同時に保存したい

ModelAを書くとこんな感じ

<?php
class ModelA extends AppModel
{
    public $useTable = 'model_a';
    public $primaryKey = 'model_a_id';
    public $sequence = 'model_a_id_seq';
}

ModelBはこんな感じ

<?php
class ModelB extends AppModel
{
    public $useTable = 'model_b';
}

この状態で以下のようにすると失敗する(保存データは適当)

<?php
// ModelA と ModelB に保存するデータ
$data = array(
    'ModelA' => array(
        'field' => 'value',
    ),
    'ModelB' => array(
        array(
            'field1' => 'value01',
            'field2' => 'value02',
        ),
        array(
            'field1' => 'value11',
            'field2' => 'value12',
        ),
    ),
);

// ModelAにhasManyでModelBを関連づけ
$modelA = ClassRegistry::init('ModelA');
$modelA->bindModel(array(
    'hasMany' => array(
        'ModelB' => array(
            'foreignKey' => 'model_a_id',
        ),
    ),
));

// ModelA と ModelB に保存
// $modelA->id をセットしていないためinsert処理
$modelA->saveAll($data, array('validate' => 'first'));

画面のエラーを見るとmodelBのシーケンスを使おうとして失敗している様子

SELECT currval('model_b_id_seq') as max


で、どうしたらいいのかなぁ?と少し考えたんですが、ModelBはModelAのmodel_a_idに従うので
同じシーケンス名を指定してあげればいいみたい。




ModelB(改)

<?php
class ModelB extends AppModel
{
    public $useTable = 'model_b';
    public $sequence = 'model_a_id_seq';  // ModelAのシーケンス名と同じもの
}


これで上手くいきました。ModelBだけ更新するってこともないのでこれでOKなはず。