EloquentでDELETE-INSERTしようと思ったらcreated_atが更新されるのを防いだ話

## ぼくはただDELETE-INSERTをしたかっただけなんだ

- とあるコードを見ているとなぜか `sortFoo` みたいな名前なのに、配列長が二倍になるというものを見つけた
- ヒアリング開始、どうやらソート順を管理するフィールドにユニーク制約がある
- その制約を回避するために、範囲外の数字で更新して空きを作った後で更新しているだとか
- いや、もうそれDELETE-INSERTした方がマシでしょ(DELETEのコストの話は一旦置いて)

## なんか去年の自分が作ってたメソッドに感謝する

- 去年のうちにupsertするためのエンティティを生成するメソッドを作っていた
 - [このあたり参照](http://qiita.com/s-age/items/cda69175ac8f8a128e21)
- 内容は単に`firstOrNew`のラッパー
    - 第一引数が検索条件
    - 第二引数が新規に作成するレコードの値
- 作ってて本当によかった！

## と、思ったのもつかの間

- あるぇー？(・3・)
- なんか、created_atまで更新されてるじゃないですかー！やだー！！
- ということで、念のためにcreated_atは変わって欲しくないですよね？とヒアリング
    - 変わっていいなら楽ではある
    - 個人的には変わって欲しくない
    - ヒアリングした結果 => created_atは変わって欲しくない

## ということで調べる(先はだいたいこのへん)

- どうせ、Modelでしょ？

```
$ grep created_at laravel/framework/src/Illuminate/Database/Eloquent/Model.php
    const CREATED_AT = 'created_at';
```

- 定数出てきたからGREPし直しましょ

```
$ grep CREATED_AT laravel/framework/src/Illuminate/Database/Eloquent/Model.php
    const CREATED_AT = 'created_at';
        if (! $this->exists && ! $this->isDirty(static::CREATED_AT)) {
        $this->{static::CREATED_AT} = $value;
        return static::CREATED_AT;
        $defaults = [static::CREATED_AT, static::UPDATED_AT];
```

- これが怪しそうですね

```
$this->{static::CREATED_AT} = $value;
```

- 内容を確認

```
    public function setCreatedAt($value)
    {
        $this->{static::CREATED_AT} = $value;

        return $this;
    }
```

- すごく好感が持てるGREP結果にニヤニヤしつつ、メソッド名でGREP

```
$ grep setCreatedAt laravel/framework/src/Illuminate/Database/Eloquent/Model.php
            $this->setCreatedAt($time);
    public function setCreatedAt($value)
```

- 一箇所だけなのでビンゴであることを祈りつつ、内容確認

```
    protected function updateTimestamps()
    {
        $time = $this->freshTimestamp();

        if (! $this->isDirty(static::UPDATED_AT)) {
            $this->setUpdatedAt($time);
        }

        if (! $this->exists && ! $this->isDirty(static::CREATED_AT)) {
            $this->setCreatedAt($time);
        }
    }
```

- どうやら、ここを呼び出している箇所を見てみて、ダメそうならDirtyの定義を調べねば・・

```
$ grep updateTimestamps laravel/framework/src/Illuminate/Database/Eloquent/Model.php
            $this->updateTimestamps();
            $this->updateTimestamps();
        $this->updateTimestamps();
    protected function updateTimestamps()
```

- いくつかあるなあ。。個別に見ていきましょう

```
    protected function performUpdate(Builder $query, array $options = [])
    {
        // If the updating event returns false, we will cancel the update operation so
        // developers can hook Validation systems into their models and cancel this
        // operation if the model does not pass validation. Otherwise, we update.
        if ($this->fireModelEvent('updating') === false) {
            return false;
        }

        // First we need to create a fresh query instance and touch the creation and
        // update timestamp on the model which are maintained by us for developer
        // convenience. Then we will just continue saving the model instances.
        if ($this->timestamps && Arr::get($options, 'touch', true)) {
            $this->updateTimestamps();
        }
```

これはINSERTでなく、UPDATEの時にキックされる処理なので、いったんスキップ

```
    protected function performInsert(Builder $query)
    {
        if ($this->fireModelEvent('creating') === false) {
            return false;
        }

        // First we'll need to create a fresh query instance and touch the creation and
        // update timestamps on this model, which are maintained by us for developer
        // convenience. After, we will just continue saving these model instances.
        if ($this->timestamps) {
            $this->updateTimestamps();
        }
```

おや？
これがINSERTの時にキックされるメソッドですよ。
2つ目にして当たりを引きましたね。

## Model::timestampのスコープを確認

```
    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    public $timestamps = true;
```

- これはおもむろに変更できますね・・
- ここまで来たら、あとはupdated_atが更新されないか、実際に動かす方が早い！！(手抜き)
    - => updated_atは更新されません


- ということで、モデルとコレクションの基底クラスにそれぞれreplace/replaceAllメソッドを仕込み

## 所感

- Laravelはドキュメントが豊富そうだけど、突っ込んだ内容はあまり出てこない
- コードはすんなりと入ってくるので、下手にググるよりもちゃんとソース読む方が早い