[Rails]パーシャル内パーシャルのrender繰り返しを防ぐには


ハマった点

投稿一覧ページに「いいねボタン」を設置する際、各投稿のパーシャルをrenderする際の繰り返しは、collectionオプションにて解決できた。

しかし、各投稿のパーシャルの中でrenderしているパーシャルにおいては、投稿の分だけrenderの繰り返しが発生してしまう。

 ↓実際のログ

このrenderのせいで、たった25件の投稿表示に1800ms以上の時間がかかってしまう。しかも、Ajaxによる非同期処理でいいねボタンの切り替えを実装しているため(DRYの観点でも)、いいねボタンをパーシャル化しなければならない。(他に方法があれば知りたいです!) 同じ状況の方も多いのでは?

解決策

renderのlayoutオプションyieldメソッドを使うことで解決した。

ER図

[NGパターン] renderの繰り返しが発生するrenderの使い方

コントローラー

posts_controller.rb
def index
  @posts = Post.All        # 今回はN+1問題などは考慮しません
end

View

views/posts/index.html
%div
  = render partial: 'posts/shared/post', collection: @posts, as: :post

views/posts/shared/_post.html
%div
  = post.id
  = post.content
  .post-likes{ id: "post-#{post.id}-likes" }
      = render 'likes/like', post: post
views/likes/_like.html
- if post.already_liked?(current_user) # 「いいね」済みならtrueを返すメソッド
  = link_to post_like_path(post, post.likes), mehod: :delete, remote: true do
    %font
      .fas.fa-heart.likes-heart-already
- else
  = link_to post_likes_path(post), method: :post, remote: true do
    %font
      .far.fa-heart
%span{ id: "post-#{post.id}-likes-count" }
  = post.likes.count

ログ

terminal
web_1              |   ↳ app/controllers/application_controller.rb:36
web_1              |   Rendering posts/index.html.haml within layouts/application
web_1              |   Rendered likes/_like.html.haml (7.7ms)
web_1              |   Rendered likes/_like.html.haml (0.6ms)
web_1              |   Rendered likes/_like.html.haml (0.6ms)
web_1              |   Rendered likes/_like.html.haml (0.5ms)
                                                 〜省略〜
web_1              |   Rendered likes/_like.html.haml (0.6ms)
web_1              |   Rendered likes/_like.html.haml (0.4ms)
web_1              |   Rendered likes/_like.html.haml (0.4ms)
web_1              |   Rendered collection of posts/shared/_post.html.haml [25 times] (663.9ms)
web_1              |   Rendered posts/index.html.haml within layouts/application (690.5ms)
web_1              | Completed 200 OK in 1847ms (Views: 1791.0ms | ActiveRecord: 3.4ms)

[OKパターン] renderの繰り返しが発生しないrenderの使い方

コントローラー

posts_controller.rb
# コントローラは改善前と同じです。

def index
  @posts = Post.All        # 今回はN+1問題などは考慮しません
end

View

↓ renderメソッドのlayoutオプションを使い、パーシャルに_like.htmlを指定。さらに、collectionにはpostsを指定する。

views/posts/index.html
.posts-wrap
  %div{ id: 'post-items' }
    = render partial: 'likes/like', layout: 'posts/post_layout', collection: @posts, as: :post

/ _post_layoutパーシャルは新たに作成します。

↓ yieldメソッドにて_like.htmlパーシャルを表示する。

views/posts/post_layout.html
%div{ id: "post-#{post.id}" }
  %div
    = post.id
    = post.content
    .post-likes{ id: "post-#{post.id}-likes" }
      = yield

/ このファイルはは新たに作成しています。

ログ

terminal
web_1              |   ↳ app/controllers/application_controller.rb:36
web_1              |   Rendering posts/index.html.haml within layouts/application
web_1              |   Rendered collection of likes/_like.html.haml [25 times] (33.6ms)
web_1              |   Rendered posts/index.html.haml within layouts/application (61.2ms)
web_1              | Completed 200 OK in 343ms (Views: 295.7ms | ActiveRecord: 2.9ms)

1847msも要していたアクセス時間を343msまで短縮することができました!(正確には短縮ではないですが)

おわり

なんとなく理解した気でいたrenderメソッドとyieldメソッドについて理解を深めることができました。
解決までに時間がかかってしまったので、解決できた時は本当に感動しました。

初めての投稿で、わかりづらい点があるかもしれません。
意見、アドバイス等よろしくお願いします!

今後も学びのアウトプットをしていきたいと思います。

参考にした記事