シュッと開発日記

学んだことのアウトプット。ポートフォリオ作ってます。

Active StorageのN+1問題に対処する

まず最初に最近データベースとかのあたりを勉強していると見かけた、 N+1問題というのを知って調べてみた。

N+1問題

繰り返し処理の中で関連があるときに普通にeachなどを使うと、 その関連を探しにデータベースにその関連がある回数だけアクセスしてしまう。 ⇨(SQLが実行されまくる) というような理解でいいのかな?

対策

関連のデータベースをいちいち参照しに行くのではなく、 includesを使ってデータベースをくっつけてしまうことで、 最小の回数データベースをみに行くようにする ってな感じでしょうか。

ここまではこの記事をみて理解しました。

N+1問題

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を使用するといいということが下の記事からわかりました。

Active StorageのN+1問題を解決する

よってこれを使って

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)に書き換えて

下の記事にあるように実装してしまうだけです。

Rails で includes して N+1 問題対策

# app/controllers/posts_controller.rb

  def index
    @posts = Post.page(params[:page]).includes(user: { avatar_attachment: :blob }).per(9)
  end

これでActiveStorageのN+1問題を解決することができました。