質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.05%

Rails N+1問題

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 647

amedama

score 37

ブログアプリを製作中です。
機能の一部としてユーザーが記事をストックしたり、ライクしたりができます。

表示速度を改善するためにN+1問題を解決しようとしています。
いくつかのN+1問題は解決できたのですが、検索、検討しても解決できない問題が出てきました。

現在、ユーザーのステータスに応じて、ストック機能、ライク機能のアイコン表示を変えています。
その際、ストックされているか(liked?)、ライクされているか(stocked?)を検証する過程でN+1問題が発生しているようです。

Userモデル

  has_many :articles, dependent: :destroy
  has_many :likes, dependent: :destroy
  has_many :article_stocks, dependent: :destroy
Articleモデル

  belongs_to :user
  has_many :likes, dependent: :destroy
  has_many :liked_users, through: :likes, source: :user
  has_many :article_stocks, dependent: :destroy
  has_many :stocked_user, through: :article_stocks, source: :user

  def liked?(user)
    liked_users.include?(user)
  end

  def stocked?(user)
    stocked_user.include?(user)
  end
Likeモデル

  belongs_to :user
  belongs_to :article
  counter_culture :article
Article_stockモデル

  belongs_to :user
  belongs_to :article
  counter_culture :article
aritcles_controller

@q = Article.with_attached_image.includes(:tags, user: { image_attachment: :blob }).includes(:liked_users, :stocked_user).ransack(params[:q])

@articles = @q.result(distinct: true).page(params[:page]

**.includes(:liked_users, :stocked_user)←この部分を正したいです。**
article/index.html.erb

<% @articles.each do |article| %>

 <span id="like-of-<%= article.id %>">
  <%= render "likes/like", article: article %>
 </span>

 <span id="stock-of-<%= article.id %> ">
  <%= render "article_stocks/stock", article: article %>
 </span

<% end %>
likes/like

  <% if current_user != article.user && user_signed_in? %>
  <span class="like">
    **<% if article.liked?(current_user) %> ←この部分でN+1が発生しています。**

      <%= link_to "", like_path(article.likes.find_by(user_id: current_user.id)), method: :delete,
                                                    class:"fas fa-heart", remote: true  %>
      <%= link_to "#{article.likes_count}", likes_path(article_id: article.id)%>
    <% else %>

      <%= link_to "", likes_path(article_id: article.id), method: :post,
                                                    class:"far fa-heart", remote: true %>
      <%= link_to "#{article.likes_count}", likes_path(article_id: article.id)%>
    <% end %>
  </span>
<% else %>

    <span class="far fa-heart" style="color:gray;"></span>
    <%= link_to "#{article.likes_count}", likes_path(article_id: article.id), :style=> "color: gray;" %>

<% end %>
article_stocks/stock

  <% if user_signed_in? %>
    <span class="stock">
      **<% if article.stocked?(current_user) %> ←この部分でN+1がおきています。**


      <%= link_to article_stock_path(article.article_stocks.find_by(user_id: current_user.id)), method: :delete , remote: true, data: { confirm: 'ストックをはずしますか?' } do %>
        <%= content_tag :i, "", class:"fas fa-folder", :style=>"color: brown;" %>
        <%= article.article_stocks_count %>
      <% end %>

      <% else %>

      <%= link_to article_stocks_path(article_id: article.id), method: :post , remote: true , data: { confirm: 'ストックしますか?' }, :style=>"color: gray;" do %>
        <%= content_tag :i, "",  class:"far fa-folder-open" %>
        <%= article.article_stocks_count %>
      <% end %>

    </span>
  <% end %>

<% else %>
  <span style="color:gray;">
    <%= content_tag :span, nil, class:"far fa-folder-open" %>
    <%= article.article_stocks_count %>
  </span>

<% end %>

他に必要な情報がありましたら、ご指摘お願いします。

解決策わかる方ご教授よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

私もN+1では悩まされ続けている身ですので、正解には程遠いかもですが、、、
拝見した一見ではなぜこれでだめ? なのですが。

bulletはお使いですか?もし使っていなかったでしたら試してみて下さい。
余分なincludesや足りないものを指摘してくれます。時々??もありますが。

includes(:tags, user: { image_attachment: :blob })部分ですが、viewをみたところ tags、user が使われていないように見えます。
もしそうでしたらここを削除してみたらどうなるでしょう?

複雑なincludesを作った時の経験なのですが
includes(:tags, user: { image_attachment: :blob }, :liked_users, :stocked_user)
の様になっていると user: { } の後ろがうまく反映しないことがありました。
user: {} を一番後ろに移すとどうなりますか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/09/16 09:52

    ありがとうございます。
    速度改善されているのでさらなる機能改善としては一回保留して、こちらの質問は解決にします。
    ご助言ありがとうございました。
    お出かけお気をつけください!

    キャンセル

  • 2019/09/16 17:16

    N+1と直接関係は無いのですが
    liked_users.include?(user) ですとUserモデルを取り込みますね。Userって結構大きいのでは? 格好わるいけど
    likes.any?{|like| like.user_id == user.id}
    likes.map(&:user_id).include?(user.id)
    とかは?
    うえのほうが負荷軽いかな

    キャンセル

  • 2019/09/17 18:12

    おお!!ありがとうございます。
    そういう書き方もありますね!
    まだひよっこなので負荷のことに関してまではそこまで考えが至ってなかったので、非常にありがたいです。
    winterboumさんのおかげで勉強になりました。
    重ね重ねありがとうございます。
    負荷を減らすRubyの書き方も意識して行きたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.05%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る