Active StorageのN+1問題に対処する
まず最初に最近データベースとかのあたりを勉強していると見かけた、 N+1問題というのを知って調べてみた。
N+1問題
繰り返し処理の中で関連があるときに普通にeachなどを使うと、 その関連を探しにデータベースにその関連がある回数だけアクセスしてしまう。 ⇨(SQLが実行されまくる) というような理解でいいのかな?
対策
関連のデータベースをいちいち参照しに行くのではなく、 includesを使ってデータベースをくっつけてしまうことで、 最小の回数データベースをみに行くようにする ってな感じでしょうか。
ここまではこの記事をみて理解しました。
Active Storageの対策
というわけでここまでで自分のプロジェクトを確認すると、 私の場合では、プロフィール画像のところでN+1問題がおきていました。
失敗
# app/models/user.rb has_one_attached :avatar # app/views/users/index.html.erb <% @users.each do |user| %> --------------------- <%= image_tag user.user_icon, class: "icon_users" %> # user_iconはuser.avatarを使ってユーザーのプロフィール画像を返す自作メソッド ---------------------- # app/controllers/users_controller.rb def index @users = User.all end
というようなことをしていたため、
def index @users = User.all.includes(:avatar) end
としたんですが、user.avatar
はカラムを参照しているわけではなく、
単なる便利メソッドだということをにエラーが出て気づきました。
成功パターン
というわけで、ActiveStorageを使用する時にどうやってincludesしたらいいか調べてみると
with_attached_attachment_name
を使用するといいということが下の記事からわかりました。
よってこれを使って
def index @users = User.with_attached_avatar end
として解決しました。
関連の関連がActiveStorageを使用していた時
問題発生
ここまではよかったんですが、 他のところでもN+1問題おきてないかなと探していると、 もう一つ見つけました。
# app/models/post.rb belongs_to :user
postがuserを繰り返しの中で使っていて、 そのuserがまたしても プロフィール画像を使用していたため、 そこでもN+1問題がおきていました。
「あ、また、with_attached_avatar
使えばいいのか」
と思っていましたが、
コントローラーをみてみると
# app/controllers/posts_controller.rb def index @posts = Post.page(params[:page]).per(9) # kaminariのページネーションを使用 end
となっていて、どう書けばいいかわからなくなってしまいました。
問題解決
with_attached_avatar
をなんとか書ければできるなと考えて、
何をしているかソースを調べてみると
# activestorage/lib/active_storage/attached/macro.rb scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
というのがありました…!!!
あとはこれにしたがってwith_attached_avatar
を includes({avatar_attachment: :blob)
に書き換えて
下の記事にあるように実装してしまうだけです。
# app/controllers/posts_controller.rb def index @posts = Post.page(params[:page]).includes(user: { avatar_attachment: :blob }).per(9) end
これでActiveStorageのN+1問題を解決することができました。