Railsで引いてきたリレーションには、Enumerableとして使うこともできます。状況によって、どちらがいいのか考えてみましょう。
概論
リレーションとして使う
Model.where(条件)としてもその場でSQLが実行されるわけではなく、必要になるギリギリまで遅延されます。データとして欲しい最終形によって、
- モデルの配列のように使う
-
countやsumでDBレベルの集計をかけて、結果を取る -
pluckで必要な列だけ抽出する
のように、多様なアクセスができます。集計はDBレベルで行われますので、SQL文を投げることとなります。
Enumerableとして使う
上で「モデルの配列のように使う」とありましたが、.eachもありますし、Enumerableとして.mapなどの集計メソッドを使うことも可能です。これはいったんテーブルを引いて、結果を全部展開してから実行します。
ケーススタディ
巨大なデータセットの場合→リレーションとして処理
処理すべきデータが数十万件あるような場合には、Enumerableとして処理しようとすれば、そのデータを全件転送する必要がありますが、転送だけでも明らかに負担となります。このような場合は、リレーションのまま集計関数をDBに投げて、結果だけ取得したほうが効率的です。
件数取得→sizeがよさげ
リレーションの長さを取得するには、count、length、sizeの3つのメソッドがあります。
-
count→DBにCOUNTクエリを投げる -
length→(データがなければ全件取得して)数を数える -
size→データが取得済みならlength、なければcount(ソース)
ということで、前後でのリレーションの使用状況が不明ならsizeを使うのが無難な気がします。
リストのhas_many→Eager Load+Enumerableで処理する
リストからさらにリレーションをたどるような場合、N+1クエリが問題となりますが、リレーションのままmaximumなどを投げると、これもN+1クエリとなります。includesなどで子リレーションをロードしておいた上で、データアクセスはEnumerableとしてその集合内に対して行う、というようにすれば、DBアクセスの回数は最小限となります。
一部の列のみの取得→Rails 5ではpluckで問題なし
Rails 4までは、pluckは必ずデータベースにアクセスするようになっていましたが、Rails 5では、すでにリレーションを読み込み済みの場合、そちらを参照するようになります。なので、リレーションが読み込み済みかどうかにかかわらずpluckを使って問題ありません。