update がしたいんです
CakePHP を使っていて、 SQL で言うところの update 文を使いたいときどうするのか?と問われたら、 save() や saveAll() を使う、と答えてしまう。
それが楽だし、それが普通だ。
しかし、以下のようなケースだと save 系のメソッドだと面倒くさいことになる。
たとえば、メールの受信ボックスのようなシステムがあって、メール一覧のところで件名の左にチェックボックスがあり、チェックしたものを一括で既読にするというような処理を行うこととする。
複数を保存するのだから saveMany() や、もしくはそれのラッパー関数である saveAll() を使ってしまうと大変なことになる。
$arr['Model'] = array(
array('id' => 1, 'be_read' => 1),
array('id' => 3, 'be_read' => 1),
array('id' => 5, 'be_read' => 1),
);
$this->Model->saveMany($arr);
フォーム側で工夫してやれば、この形式で配列を受け取れるのでコントローラー側で整形する必要はない。
が、これで更新を掛けると、1行ずつクエリーが走ることになり非常にリソースが無駄である。
じゃぁ、どうするのか。
updateAll() を使おう!
こちらだと
$datas = array('be_read' => 1);
$conditions = array(
'id' => array(1, 3, 5),
);
$this->Model->updateAll($datas, $conditions);
はい!すっきり!
が、そんな updateAll() さんにも不便なところがある。
アソシエーションしている場合、 join して update するのだ。
これは非常に気持ち悪い。
しかも $recursive = -1; を指定してあげても、無視されて join するという、最悪っぷり。
回避する手段はないのかと言うと、実はある。
unbindModel() を使えばいい。
第1引数に解除したいモデル名を列挙した配列、第2引数には boolean を指定する。
第2引数は省略すると true で、これを true にすると一時的なアソシエーション解除となり、 false にすると処理が完了するまで継続的に解除される。
ここで問題になってくるのが「一時的」の範囲だ。
予備知識なしで考えると、次の「動作」の完了までが「一時的」の範囲で、今の例で行くと updateAll() が終わったら解除されると思うのではないだろうか。
残念ながら、違うのだ。
この「一時的」と言うのは次の「 find() 系メソッドが使われて完了するまで」のことだ。
何が問題なのかと言うと、今回の例の場合、メールを既読にした後、普通はまたメール一覧が表示される。
となると
$this->Model->unbindModel(array('AssoModel');
$this->Model->updateAll($datas, $conditions);
$this->Model->find();
と続けて使うことになり、 updateAll() の前に unbindModel() をしてしまうと、アソシエーションを解除したまま find() を実行することになり、本来は find() でとってくるはずのデータがない!という事態になりうる。
(なお、paginater() メソッドも find と同様の結果になる。)
もちろんこれも回避手段がある。
$this->Model->resetAssociations();
としてやると、bindModel() の「一時的」な連携や、 unbindModel() の「一時的」な連携の解除、を解除することが出来る。
普通知らないよね。こんなメソッド。
「一時的」が find() 実行で解除される、という記述をみたので、コアの Model の find 周りに解除するための処理があるはずだ!と、目を皿のようにして探して見つけた。
で、今回の例は以下のようになる。
$this->Model->unbindModel(array('AssoModel');
$this->Model->updateAll($datas, $conditions);
$this->Model->resetAssociations();
$this->Model->find();
今回の肝。
- updateAll() は join される
- recursive = -1 で join を解除できない
- join 解除するには unbindModel() を使う
- unbindModel() は次の find() 系メソッドに影響を与える
- unbindModel() の「一時的」を解除するには resetAssociations() を使う