has_manyのhas_manyのcountで効率よくパフォーマンスを出す
counter_cacheはできるだけ使いたくないんですよ。
無駄なカラムを持ちたくないということで、頑張りました。
対策としては、最初で全てのcountを手にするのが一番いいですね。
countは重たいので、先に何とかして値を入手したい・・・。
今回のサンプルでは、Blogがあって、Commentをたくさん持っていて、さらにCommentに対して複数のReactionをつけれるようなものです。 それのgroup化したReactionの数が欲しいという状況でした。
サンプル
class Blog < ActiveRecord::Base has_many :comments, dependent: :destroy has_many :comment_reactions, through: :comments def comment_reactions_count_map comment_reactions.group(:comment_id, :reaction_id).count←これが今回の肝 => {[1, 1]=>7, [1, 315]=>1, [1, 332]=>1, [1, 708]=>1, [1, 825]=>1, [1, 864]=>1, [1, 1038]=>1, [1, 1044]=>1, [1, 1182]=>1, [1, 1229]=>1, [1, 1288]=>1, [2, 57]=>1, [2, 82]=>1, [2, 220]=>1, end end
class Comment < ActiveRecord::Base belongs_to :blog has_many :comment_reactions has_many :reactions, through: :comment_reactions end
class CommentReaction < ActiveRecord::Base belongs_to :comment belongs_to :reaction end
class Reaction < ActiveRecord::Base has_many :comment_reactions has_many :comments, through: :comment_reactions end
controller側
def show @blog = Blog.find(params[:id]) @count_map = @blog.comment_reactions_count_map←これでIDをセットする。 end
view側はこんな感じになりますね。
サンプルなんで、適当です。
show.html.slim
= render partial: "blog/comments", collection: @blog.comments.includes(:reactions), as: :comment
_blog/comments.html.slim
- if comment.reactions.present? - comment.reactions.group(:id).each do |reaction| = image_tag reaction.image.url = @count_map[[comment.id, reaction.id]]←これでcountの値をもらう
こんな感じでどうでしょうか。
パフォーマンスはexplainでコツコツ攻略するのも大事ですが、可能ならば、sqlのクエリを減らすのがいいですね。