0.はじめに
railsでN+1問題が発生した場合、とりあえずincludesで先にデータを読み込んでおけばいいんでしょ?と思っていた。
が、上記の方法でどうしても解決できないシチュエーションに陥った。
それは、countメソッドでデータの個数を数えたいときだ。
こういう場合、counter cultureというgemを導入して解決するといいっぽい。
1.countメソッドでN+1クエリ問題が発生する具体的な例
例えば以下のように、UserとGroupがUserGroupを中間モデルとして、多対多の関係にあるとする。
class User
has_many :groups, through: :users_groups
end
class Group
has_many :users, through: :users_groups
end
class UsersGroup
belongs_to :user
belongs_to :group
end
次に、以下のようにcountでそれぞれのユーザーが所属するグループ数を取得しようとする。
<% @users.each do |user| %>
<%= user.groups.count %>
<% end %>
こうすると、当然のように大量のSQLクエリが発行されるが、includesで解決することはできなかった。
2.counter cultureを導入しよう
user.groups.countでN+1クエリ問題に引っかかる。。。
この問題を解決するために、counter cultureというgemを導入します。
gem 'counter_culture'
bundle install
このgemを使用すると、countで毎回数え上げるのではなく、カラムに数値を保存して取り出せるようになります。
まずは、以下のコマンドを叩いて、親であるUserモデルにusers_groups_countというカラムを追加します。
rails g counter_culture User users_groups_count
rails db:migrate
続いて、中間モデルのUsersGroupに1行追記します。
class UsersGroup
belongs_to :user
counter_culture :user, column_name: "users_groups_count"
belongs_to :group
end
以上で準備完了です。。
あとはuser.groups.countの代わりに、user.users_groups_countを使うことで、N+1クエリ問題を回避できます。
3.注意点
既存データに対して使用したい場合は、counter_culture_fix_countsメソッドを使って値を更新する必要があります。
User.counter_culture_fix_countsを流しておけばOKです。
4.最後に
備忘録的な意味合いが強いので、だいぶ雑に書いてしまいましたが、参考になれば幸いです。