Rails ビューからデータベース クエリを抽出する


序章



Rails ビューの一部のロジックは避けられませんが、最近、特にデータベース クエリの場合、ビュー内の不要なロジックをモデルまたはコントローラーに移動しようとしています.この問題は、Rails Facebook クローンで Friendship の「Accept」ボタンをコーディングしているときに見えてきました.

Notifications#index ページから友達リクエストを承認または拒否できるように設定しました.問題は、Notifications#index 内からそれぞれのフレンドシップ オブジェクトを見つけなければならないことでした.これには、データベース クエリが必要でした.通知に関連付けられた Friendship オブジェクトを見つけるための正しいデータベース クエリをビューに書き込んだ後、ビューからそれを削除する方法を考え始めました.以下は、私が問題にどのようにアプローチしたかです.

すべての通知は senderreceiver を参照し、どちらも User クラスに属しています.同様に、 Friendship オブジェクトは senderreceiver で開始されます.ユーザーが通知ページに多くの友達リクエストを持っている場合、受信者がそれぞれ「承認」または「拒否」をクリックしたときに、どの特定の友達リクエストが承認または拒否されているかを判断する必要があります.

したがって、通知コレクションをレンダリングすると、notification.sender との友情がすぐに受け入れられるか、すぐに辞退されるかを見つけることができます.notification.receiver. 「同意する」ボタンは次のとおりです.

最初の「同意する」ボタン



  <% friendship = Friendship.find_by(sender_id: notification.sender.id,
                                     receiver_id: notification.receiver.id) %>
<%= button_to "Accept",
              friendship_path(friendship),
              method: :put,
              params: { friendship: { status: 'accepted' } } %>
sender_id にマッピングされた、現在のユーザーに送信されたすべてのフレンド リクエストのハッシュを作成することで、ローカル変数の必要性をなくすことができます.この状況では、現在のユーザーが常に受信者になり、現在のユーザーに送信されたフレンドシップ リクエストを見つけるには、 sender_id のみが必要です.このメソッドを User のインスタンス メソッドにするのは理にかなっていると思いましたが、おそらくこれを行うより良い方法があります.コメントで教えてください!関連するモデルと関連付けは次のとおりです.

関連モデル


ユーザー、フレンドシップ、通知



  class User < ApplicationRecord
    has_many :sent_notifications,
            class_name: 'Notification',
            foreign_key: 'sender_id',
            dependent: :destroy
    has_many :received_notifications,
            class_name: 'Notification',
            foreign_key: 'receiver_id',
            dependent: :destroy

    has_many :sent_pending_requests, -> { friendship_pending },
            class_name: 'Friendship',
            foreign_key: 'sender_id',
            dependent: :destroy
    has_many :received_pending_requests, -> { friendship_pending },
            class_name: 'Friendship',
            foreign_key: 'receiver_id',
            dependent: :destroy
    # ...
  end



class Notification < ApplicationRecord
  belongs_to :sender, class_name: 'User'
  belongs_to :receiver, class_name: 'User'
  #...
end



class Friendship < ApplicationRecord
  enum status: %i[pending accepted declined]

  belongs_to :sender, class_name: 'User'
  belongs_to :receiver, class_name: 'User'

  scope :friendship_pending, -> { where(status: :pending) }
  #...
end

次に、送信者 ID を Friendship オブジェクトにマップするメソッド:

  class User
  #...associations, etc.
    def requests_via_sender_id
      requests = received_pending_requests
      sender_ids = requests.pluck(:sender_id)
      sender_ids.to_h do |id|
        [id, requests.find_by(sender_id: id)]
      end
    end
  end


現時点では received_pending_requests.count == 1 としましょう. ID 3 のユーザーが current_user リクエストを送信しました.

current_user.requests_via_sender_id =>
{3=>
  #<Friendship:0x000055f71022ec20
   id: 16,
   status: "pending",
   sender_id: 3,
   receiver_id: 1 }


このようにすることの良い点は、コントローラーが Notifications#index ビューをレンダリングする前に User モデルからこの情報を要求できるようになったことです.

  class NotificationsController < ApplicationController
    def index
      @notifications = current_user.received_notifications.includes(%i[sender receiver])
      @friendships = current_user.requests_via_sender_id # => returns a hash
    end
  end


最後に、レンダリング中の現在の通知にアクセスできる [Accept] ボタンに戻ります.friendship ローカル変数がないことに注意してください.

最後の「同意する」ボタン




<%= button_to "Accept",
              friendship_path(@friendships[notification.sender.id]),
              method: :put,
              params: { friendship: { status: 'accepted' } }%>


結論



このアプローチのもう 1 つの利点は、読みやすさの向上です.どの友情を更新していますか?通知の送信者が要求したもの -->friendship_path(@friendships[notification.sender.id]).これは、 Friendship オブジェクトを検索するコンテキストを提供するため、 friendship_path(friendship) よりも表現力が高いことは間違いありません.

モデル、ビュー、およびコントローラーの間で責任のバランスをとるのは難しい場合もありますが、少しずつソリューションに向かって進むことは、非常に教育的でやりがいのあることです.

これが私の最初のブログ投稿でした.読んでいただきありがとうございます.それを終えた後、私はそれが理解に関して持っている利点を理解しています.近いうちにもっと記事を書きたいと思っています.

Lawrence Hookhamの写真
Unsplash