はじめに
length
size
count
の使い分け【前編:Ruby】の続きですが、この記事だけでも完結した内容になっています。
Rubyのlength
size
count
を調べてる間に気づいた、「Railsだと定義が違うぞ・・・・・・」
ということで、Railsでもlength
size
count
について調べていきます。
※注記しない限り、count
は引数を与えないものとします。
この記事のまとめです
Rails (ActiveRecord
) のlength
size
count
正確にはAcriveRecord::Associations::CollectionProxy
です。
コレクションとは、配列やハッシュなど要素の集まりのことです。
-
length
:コレクションをメモリにロードし、要素数を取得します。
すでにメモリ上にあれば、その要素数を取得します。 -
size
:メモリ上にコレクションがあればその要素数を取得する。
メモリにない場合は、SQLクエリを発行し、データベースから要素数を取得する。
条件に合わせて、length
とcount
を使い分けるイメージです。 -
count
:どんな時でもSQLクエリを発行し、データベースから要素数を取得する。
使用シナリオ
若干正確性に欠けますが、平易な言い方で使用シナリオを示します。
まず、count
とそれ以外では性質が違います。
count
:(データベースの負荷を考えず)常に最新のデータを取得したい場合です。
length
/ size
はケースによって最適が違います。
対象データの扱い |
|||
---|---|---|---|
直前 | 直前で呼び出している | 直前で呼び出していない | |
直後 | どちらでも | 直後に呼び出す | 直後に呼び出さない |
適したメソッド |
size / length (同じ) |
length |
size |
本編
それぞれのメソッドのイメージ
以下の簡単な例を用いて、それぞれのメソッドのイメージをしましょう。
pets = Pet.all # dog, cat, rabbit
pets.length # 3
pets.size # 3
pets.count # 3
length
-
pets
の「すべての要素'dog', 'cat', 'rabbit'
」をデータベースからメモリに呼び出す - メモリ上の、呼び出した要素の数を取得する。この場合は
3
です。
いったんメモリにすべて持ってきます。
すでにメモリ上にあれば、1
のステップをスキップして2
のみを行います。
count
- SQLクエリを発行し、
pets
の「要素数」を取得する。つまり3
を取得します。
要素をひとつひとつ持ってくる、なんてことはしません。
size
- メモリ上に、対象のコレクションがあるか確認する
- メモリ上にある場合は、
length
の2
と同じ挙動をする - メモリ上にない場合は、
count
と同じ挙動をする
- メモリ上にある場合は、
どっちにしろ、3
のみを取得します。
length
はコレクションをまるまる呼び出す
length
の特徴的な点は、「唯一、コレクションをまるまるメモリに呼び出す」ということです。
count
・size
は「コレクションの要素数」を呼び出します。
これの何が良いのでしょうか?
それは、
-
「すでにコレクションがある場合、再度コレクションを呼び出さなくて良い」
-
「
length
の後にもコレクションを使用する場合、再度コレクションを呼び出さなくて良い」ということです。これにより、データベースの負荷を下げることができます。
# 最初にPetのコレクションのlengthを取得
# これにより、petsコレクションがメモリにロードされる
pets = Pet.all
pets_length = pets.length
# データベースへの追加的なクエリなしに、petsコレクションをフィルタリング
# 例: 名前が5文字以上のペットを選択
long_named_pets = pets.select { |pet| pet.name.length >= 5 }
# 結果の表示
puts "Total number of pets: #{pets_length}"
puts "Number of pets with long names: #{long_named_pets.count}"
この例のpets.length
をcount
もしくはsize
に置き換えると、データベースへの負荷が高くなります。
count
は常に最新のデータを取得する
count
の特徴的な点は、「唯一、常にデータベースにアクセスする」ということです。
これの何が良いのでしょうか?
それは、「常に最新のデータを取得できる」ということです。
# すべてのPetレコードを取得
pets = Pet.all
# この時点でのpetsコレクションのlengthを取得(メモリ上のデータの数)
length_before = pets.length
# 他のユーザーが新しいPet(例: 'bird')をデータベースに追加
# Pet.create(name: 'bird')
# 再度countを使用してデータベースの現在のレコード数を取得
count_after = Pet.count
# 結果の表示
puts "Length before: #{length_before}" # 初期のメモリ上のデータ数=3
puts "Count after: #{count_after}" # データベース上の最新のデータ数=4
この例は、length
が、他のユーザーによってbird
が追加されたことを感知できず3
を返すこと、
count
であれば他のユーザーの操作を感知し、4
を返すことができることを示しています。
一方で、メモリにコレクションがある場合、常にSQLクエリを行うcount
は非効率なこともあります。
size
はいいとこどりだが、中途半端になるリスクも
size
は、既にメモリにコレクションがある場合、length
の挙動を行い、それ以外ではcount
(引数なし)の挙動をします。
これは「常に負荷の低くなる挙動を取れる」というメリットがあります。
しかし、
-
length
の「コレクションを再使用する場合再クエリが不要」 -
count
の「常に最新のデータを取得する」
というメリットを得られません。この点が中途半端です。
終わりに
以上のことから、一概にこのメソッドが優れている、ということはありません。
ただ、個人的にはcount
のメリット(常に最新のデータを取得する)は大きいと感じます。
とはいえ、処理が重くなってから変えるんじゃ遅いんだよなあ・・・・・・
使い分けは、経験を積みながら考えていこうと思います。
この記事のまとめです ※再掲
Rails (ActiveRecord
) のlength
size
count
正確にはAcriveRecord::Associations::CollectionProxy
です。
コレクションとは、配列やハッシュなど要素の集まりのことです。
-
length
:コレクションをメモリにロードし、要素数を取得します。
すでにメモリ上にあれば、その要素数を取得します。 -
size
:メモリ上にコレクションがあればその要素数を取得する。
メモリにない場合は、SQLクエリを発行し、データベースから要素数を取得する。
条件に合わせて、length
とcount
を使い分けるイメージです。 -
count
:どんな時でもSQLクエリを発行し、データベースから要素数を取得する。
使用シナリオ
若干正確性に欠けますが、平易な言い方で使用シナリオを示します。
まず、count
とそれ以外では性質が違います。
count
:(データベースの負荷を考えず)常に最新のデータを取得したい場合です。
length
/ size
はケースによって最適が違います。
対象データの扱い |
|||
---|---|---|---|
直前 | 直前で呼び出している | 直前で呼び出していない | |
直後 | どちらでも | 直後に呼び出す | 直後に呼び出さない |
適したメソッド |
size / length (同じ) |
length |
size |
参考
Rails Github collection_proxy.rb
Rails API reference size
Rails API reference length
Rails API reference count