0
Help us understand the problem. What are the problem?
Organization

RailsのActiveRecordのcount、length、sizeメソッドの違い

はじめに

いよいよダットのターンです!たまにはRailsについて書かずHTMLかJavascriptばかりの記事を投稿したので今回はRailsのメソッドについて説明させていただきます。:pray:
開発者としてはこれらのメソッドを触ったことがあるはずですね。風通はこの三つの中に勝手に選んでどっちでも使って無事で計算されるので大丈夫と思いましたが、実はそうでしょうか?実際は場合によって返った結果も変わって実行する時間も異なる可能性があります。さっそくですが、始めましょう!

概要

この記事はこれらのメソッドがRubyに使われる時を気にしないでActiveRecordに使用する時だけ中心してください。結局、アソシエーションの中に幾つのレコードがあるか教えてくれますが、内部の処理がちょっと違います。
例えば、このモーデルとリレーションがあります。

app/models/post.rb
class Post
  has_many :comments, dependent: :destroy
end

app/models/comment.rb
class Post
  belongs_to :post
end

post.comments.count

SQLのCOUNTのクエリを実行して結果からエレメントを数えます。このcountの前に条件を絞ってサブセットのエレメント数を数えることもできます。

$ post.comments.count
(1.0ms)  SELECT COUNT(*) FROM `comments` WHERE `comments`.`post_id` = 1
=> 3

$ post.comments.where(author: 'dat').count
(1.0ms)  SELECT COUNT(*) FROM `comments` WHERE `comments`.`post_id` = 1 AND `comments`.`author` = `dat`
=> 1

尚、リレーションにcounter_cache: trueを使用したらcountは新しいクエリを実行せず、すぐにキャッシュの値を返るようになります。
カウンターキャッシュはこの記事に書いてありませんがこれは参考になるかと思っています。:rolling_eyes:

post.comments.length

この場合はアソシエーションのコンテンツをロードしてロードされたエレメントを数えます。このメソッドはアソシエーションが既にロードされたらもう一度クエリを実行しません。

$ post.comments.length # クエリが実行される
Comment Load (1.1ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 1
=> 5

$ post.comments.length # もう一度実行するとクエリが実行されなくてすぐに返す
=> 5

post.comment.size

これは以上の二つのメソッドを合わせるものです。もしコレクションが既にロードされたらlengthメソッドのようにすぐに結果を返します。逆だったらcountメソッドのようにクエリを実行して結果を返します。ザッと見るとこのメソッドが全部自動的に適当な方法を選んで実行するのでもっとも良いメソッドかなと思う人もいますね。しかし、特別なケースによって意外と間違った結果を返す場合もあります。具体的な例を出そうと思ってます。

$ posts = Post.all
Post Load (1.7ms)  SELECT `posts`.* FROM `posts`
$ posts.length
=> 3
$ posts.size
=> 3
$ post.count
(1.0ms)  SELECT COUNT(*) FROM `posts`
=> 3

以上の例を見るとコレクションが既にロードされたらcountは無駄なクエリを実行しまったとわかります。だが、ロードされたコレクションが変更したらどうなりますか。:thinking:

$ posts = Post.all
Post Load (1.7ms)  SELECT `posts`.* FROM `posts`
$ Post.last.destroy!
=> # 消されたエレメントを返す
$ posts.length
=> 3
$ posts.size
=> 3
$ post.count
(1.0ms)  SELECT COUNT(*) FROM `posts`
=> 2

つまり、新しいクエリを実行しないと変更があるかどうか分からなくて間違った結果を返してしまいました。なので、この場合はcountしか使えません。

結論(TLDR)

  • クエリから取ったデータをもう使わなかったら、countを利用すると良い
  • クエリから取ったデータを既に使ったか使う可能性があったら、lengthを利用すると良い
  • Railsに任せたかったらsizeを利用することもできる。しかし、意外と間違った結果が返される可能性があるのでよく分析してからsizeを代わりに適当なcountlengthを使用したら良い :ok_hand:

終わりに

これは自分が実装したときどっちの方がいいのか悩んで調べた内容からまとめておいたのです。具体的な説明とか他の例を見たい方は以下の記事でも参考になると思います。ここまで読んで頂いて感謝いたします。:bow:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
0
Help us understand the problem. What are the problem?