#結論
countメソッド
はその都度SQLクエリを実行する。
N+1問題となるため、不要なときはsizeメソッド
を使用する。
#sizeメソッド is 何
以下のActive Record公式GitHubで定義を確認します。
[https://github.com/rails/rails/blob/94b5cd3a20edadd6f6b8cf0bdf1a4d4919df86cb/activerecord/lib/active_record/relation.rb#L210](Active Record)
def size
loaded? ? @records.length : count(:all)
end
未ロード時にはcountされて、
キャッシュがあるときはレコードのlengthを取得しているだけのようです。
#countメソッドとの比較
投稿のコメント数を取得することを例にとって説明します。
##countメソッドを使用するとき
<%= micropost.commented_users.count %>
このとき実行されるSQLは以下になります。
SELECT COUNT(*) FROM `users` INNER JOIN `comments` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = 4
SELECT COUNT(*) FROM `users` INNER JOIN `comments` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = 3
SELECT COUNT(*) FROM `users` INNER JOIN `comments` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = 2
SELECT COUNT(*) FROM `users` INNER JOIN `comments` ON `users`.`id` = `comments`.`user_id` WHERE `comments`.`post_id` = 1
「ユーザーID」と、「コメントのユーザーID」が一致している投稿を全て取得しています。
コメントテーブルに対して何度もアクセスしているため、N+1問題
が発生中。
##sizeメソッドを使用するとき
- <%= micropost.commented_users.count %>
+ <%= micropost.commented_users.size %>
この状態でログを見ると先程のSQLは実行されていません。
未ロード時にはCOUNT
を実行し、
キャッシュがあれば実行しないのがsize
メソッドの挙動です。
#最後に
N+1問題は自動で検出してくれるbulletのようなgemもありますが、
自分のコードにどんな無駄があるのか考えると勉強になります。
特にActive Recordは便利で何が起きているのか深く考えてこなかったので
今回調べてみてこのように記事にしました。
#参考
https://scoutapm.com/blog/three-activerecord-mistakes-ja
https://pikawaka.com/rails/includes#N%20+%201%E5%95%8F%E9%A1%8C%E3%81%A8%E3%81%AF%EF%BC%9F
[https://github.com/rails/rails/blob/94b5cd3a20edadd6f6b8cf0bdf1a4d4919df86cb/activerecord/lib/active_record/relation.rb#L210](active record)