LoginSignup
0
0

`length` `size` `count`の使い分け【後編:Rails】

Posted at

はじめに

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クエリを発行し、データベースから要素数を取得する。
    条件に合わせて、 lengthcountを使い分けるイメージです。
  • 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

  1. petsの「すべての要素'dog', 'cat', 'rabbit'」をデータベースからメモリに呼び出す
  2. メモリ上の、呼び出した要素の数を取得する。この場合は3です。

いったんメモリにすべて持ってきます。
すでにメモリ上にあれば、1のステップをスキップして2のみを行います。

count

  1. SQLクエリを発行し、petsの「要素数」を取得する。つまり3を取得します。

要素をひとつひとつ持ってくる、なんてことはしません。

size

  1. メモリ上に、対象のコレクションがあるか確認する
    1. メモリ上にある場合は、length2と同じ挙動をする
    2. メモリ上にない場合は、countと同じ挙動をする

どっちにしろ、3のみを取得します。

lengthはコレクションをまるまる呼び出す

lengthの特徴的な点は、「唯一、コレクションをまるまるメモリに呼び出す」ということです。
countsizeは「コレクションの要素数」を呼び出します。

これの何が良いのでしょうか?
それは、

  • 「すでにコレクションがある場合、再度コレクションを呼び出さなくて良い」

  • 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.lengthcountもしくは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クエリを発行し、データベースから要素数を取得する。
    条件に合わせて、 lengthcountを使い分けるイメージです。
  • 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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0