起こっていた問題
Railsでインスタグラムを模したアプリを作っています。
データベース構造はこんな感じです。
このアプリのトップページ(投稿一覧)でこんなメソットを実装していたところ、
- if post.images.count >= 2
- # 処理
クエリがこんなことになってしまいました^^;
imagesのcountが大量に回っている...^^;
そういえば、Railsにはcounter_cache
という機能がデフォルトであって、こういうときに役立つんだよ、と聞いたことがあったので、調べてみたのが今回の始まりです。
なお、実行環境は以下の通りです。
Rails 5.2.3
Ruby 2.6.0
counter_cacheについて
まず、上記の問題を解決する手段として、
- counter_cache(Railsのデフォルトの機能)を使う
- counter_culture(gem)を使う
- ゴリゴリコードを書く
の3種類があることがわかりました。(3つ目は教えていただきました!ありがとうございます!!)
まず、Railsのデフォルトの機能のcounter_cache
なのですが、以下のように実装します。
class Image < ApplicationRecord
belongs_to :post, counter_cache: true
end
従属している(belongs_to)がついている方のモデルに、counter_cache: true
を記載します。
class Post < ApplicationRecord
has_many :images
end
has_manyの方のモデルはそのままなのですが、こちらには、子テーブルの名前_count
というカラムを作ります。今回の場合はimages_count
というカラムをposts
テーブルに作ります。
$ rails g migration AddColumnToPost images_count:integer
こうすることで、images_count
カラムには、postが持っているimageの数が記録される様になります。
ビューでは、こんなふうに書けば、いちいちimages
テーブルを呼び出さずにクエリ数も減るはず...
- if post.images_count >= 2
-# 処理
...と、ここまで書いて気がついた。すでにpostのデータがある場合はどうすればいいんだ。。。
はい、counter_cache
の弱点はまさにそれで、すでにposts
テーブルにデータがある場合、images_count
の値は自動更新されません。
先人たちが色々苦労されて修正された様子も見つけたのですが、構造が謎すぎて自分には使いこなせそうにありませんでした。。。
そこで、さらに調べていく中で出会ったのが次に紹介するcounter_culture
です。
counter_cache補足
なお、counter_cache
については、デフォルトのカラム名を変更したりなど、色々なオプションがありましたので、詳しくはこちらをご覧ください。
Railsガイド - 4.1.2.3 :counter_cache
counter_cultureについて
counter_culture
は上記のcounter_cache
の弱点も補強してくれるような高機能のgemです。
▶︎公式ドキュメントはこちら
導入方法
gemfile・migration
導入方法は以下の通りです。まず、Gemfile
に以下を記載しbundle install
します。
gem 'counter_culture'
その後、以下のコマンドでマイグレーションを生成します。
$ rails generate counter_culture Post images_count
こちらのコマンドの結果、以下のようなマイグレーションが作成されます。
class AddImagesCountToPosts < ActiveRecord::Migration[5.2]
def self.up
add_column :posts, :images_count, :integer, null: false, default: 0
end
def self.down
remove_column :posts, :images_count
end
end
同様の構造を持つマイグレーションファイルであれば、必ずコマンドで作成しないといけない、というわけではないようです^^
rails db:migrate
します。
model
モデルファイルには、それぞれ以下の様に記載します。
class Image < ApplicationRecord
belongs_to :post
counter_culture :post
end
class Post < ApplicationRecord
has_many :images
end
その他
そして、すでにposts
テーブルにたくさんデータがあるなどの場合、コンソールで以下の様に実行します。
$ rails c
pry(main)> Image.counter_culture_fix_counts
こうすることで、posts
テーブルのimages_count
のカラムの中身がアップデートされます。
最後に、ビューでこの様に書けば...
- if post.images_count >= 2
-# 処理
無事、クエリ数が減りました!!
補足・参考資料等
counter_culture
にはオプションが色々ある様でしたので、詳しく知りたい方は公式ドキュメントをご覧ください。
また、今回参考にさせていただいた記事は以下の通りです。
その他・感想等
今回の実装前に、N+1問題も発生していたので、その解決方法も参考までにご共有いたします。
クエリ数を減らしたいが今年の目標の一つだったのですが、SQLをゴリゴリ勉強する前に、手早く減らせる方法が見つかってよかったです^^
今後は、SQLの勉強を頑張っていきたいです。