【Rails 4.2】find_each と find_in_batches の違いと使い分け＆【Rails 5.0~】in_batches

## 前置き
未だにRails 4.2を使っている方向けです。
最後にRails 5.0以降についても補足しております。

## 出発点
数十万いる全ユーザーに対し、カラムを新規追加処理を、1Gしかないサーバーで実行したらメモリ不足で落ちました。
初めに書いていたのは以下のようなコードです。


```
User.all.each do |user|
  user.update_new_column
end
```
数十万人のユーザーがメモリにドーンとくるので、そりゃ落ちます。

## find_eachとfind_in_batchesとの出会い
先輩に「find_eachってのがあるから使ってみな」というアドバイスを頂き、調べてみました。
どうやら少しずつデータを取ってこれるようです。
defaultは1000レコードです。
すると似たようなfind_in_batchesもHitしました。
Rails ドキュメントを読んだのですが、違いはすぐにはわかりません。
http://railsdoc.com/references/find_each
http://railsdoc.com/references/find_in_batches


## 本題の挙動の違いと使い分け
「出どころの怪しいブログ記事ではなく、一次情報に当たれ！」と日頃よく言われているのでみてみます。
### find_each

```
    def find_each(options = {})
      if block_given?
        find_in_batches(options) do |records|
          records.each { |record| yield record }
        end
      else
        enum_for :find_each, options do
          options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
        end
      end
    end
```

### find_in_batches

```
 def find_in_batches(options = {})
      options.assert_valid_keys(:start, :batch_size)

      relation = self
      start = options[:start]
      batch_size = options[:batch_size] || 1000

      unless block_given?
        return to_enum(:find_in_batches, options) do
          total = start ? where(table[primary_key].gteq(start)).size : size
          (total - 1).div(batch_size) + 1
        end
      end
```

なんとfind_eachの中でfind_in_batchesが呼ばれておる・・・。
違いは、find_in_batchesはまとめて取ってきたデータを一旦配列に入れるのに対し、
find_eachはfind_in_batchesで取ってきた配列をeachでかけているということでした。

なので使い分けとしては、
find_in_batches: 取ってきた1000件に対し、何か処理をしてからeachにかけたい時。（super coolな用途例が思い浮かばない。。）
find_each: ただただ全てを単純にeachにかけたいとき
だと思います。
今回で言えば、find_eachでよし。

## 補足 (Rails5以降)
Rails5以降ではin_batchesというメソッドがあるらしいです。
これはfind_in_batchesがArrayで返すのに対し、ActiveRecord::Relation objectsで返すようです。
だからこんなこともできちゃう。(以下[ここ](https://github.com/rails/rails/pull/20933)から抜粋)

```
People.in_batches(of: 100) do |people|
  people.where('id % 2 = 0').update_all(sleep: true)
  people.where('id % 2 = 1').each(&:party_all_night!)
end
```

find_in_batchesはすぐには良い用途が思い浮かばなかったですが、こっちは色々と使えそうですね。

これにより、find_eachは中でfind_in_batchesを呼んでいて、

```
    def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
      if block_given?
        find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
          records.each { |record| yield record }
        end
      else
        enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
          relation = self
          apply_limits(relation, start, finish).size
        end
      end
    end
```


find_in_batchesは、中でin_batchesを呼んでいるという構造に。

```
    def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
      relation = self
      unless block_given?
        return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
          total = apply_limits(relation, start, finish).size
          (total - 1).div(batch_size) + 1
        end
      end

      in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
        yield batch.to_a
      end
    end
```

美しい〜

## 参考
http://zdogma.hatenablog.com/entry/2015/06/16/221617
https://qiita.com/nikadon/items/9e6431f5a7b2b8798113
https://github.com/rails/rails/pull/20933
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/batches.rb
https://raw.githubusercontent.com/rails/rails/4-2-stable/activerecord/lib/active_record/relation/batches.rb