0

ユーザーが別のユーザーの友情を持つユーザーモデルがあります。フレンドシップ モデルは、ユーザーの class_name を持つフレンドを使用します。すべてが正常に機能しているようです。ただし、機能にパッチを当てようとしているだけで、最善の手順に従っていないと思います。

私のコントローラー、友情コントローラーには、 current_user が友達を追加できる場所があります。ただし、同じ友達を 2 回追加することは望ましくありません。

user = current_user.id
friend = params[:friend_id]
temp_friendship = Friendship.where('(user_id = ? AND friend_id = ?) OR (user_id = ? AND friend_id = ?)', user,friend,friend,user)
if !temp_friendship.present?
  @friendship = current_user.friendships.build(:friend_id => params[:friend_id])
  if @friendship.save
    redirect_to current_user, :notice => "Added friend."
  else
    redirect_to current_user, :alert => "Unable to add friend."
  end
else
  redirect_to current_user, :alert => "Already a friend."
end

このコードはすべてうまく機能します。ただし、データベースに不要な呼び出しを行っているようです。モデルによる検証など、このコントローラー呼び出しを最適化する方法はありますか?

これを試してみましたが、既に友達を開始した場合にのみ検証エラーが返されます。誰かが私を友達として追加した場合 (ここで、friend_id は私のユーザー ID になります)、エラーは発生しません。

validates_uniqueness_of :user_id, :scope => :friend_id
validates_uniqueness_of :friend_id, :scope => :user_id
4

3 に答える 3

1

パフォーマンスを最適化することはできません

ここで行うことは基本的に次のとおりです。

  1. 同じ ID を持つレコードが存在するかどうかを確認するリクエストを作成する
  2. そうでない場合は、新しいレコードを書き込みます

それはおなじみのように聞こえたので、一意性の検証としてそれを実装する手段を試すことにしました。しかし、それは解決策ではありません。実際に行っていること#validates_uniquessは、すべての ID を確認してから保存することです。

その時点で、これ以上のことはできません。すでに問題を最小のステップに減らしています。したがって、これを対称スコープの一意性ルールに変換できたとしても、2 つのデータベース クエリが起動されます (実際には、 two を使用して 3 つ起動されます#validates_uniqueness_of)。

読みやすさを最適化できます

その点で、できることがいくつかあります。これは冗談ではありません。コントローラを記述した後で、コントローラ全体をすばやく読み取る必要がある場合に、時間を節約できます。

まず、temp_friendship クエリをスコープとモデル メソッドにすることができます。それは彼らの場所であり、おそらく役立つでしょう。

第二に、友情が存在する場合のリダイレクトは、アクションの方法をより読みやすくする前のフィルターにすることができます:

class User < ActiveRecord::Base
  has_many :friends, through: :friendship

  scope :friend_with, ->( other ) do
    other = other.id if other.is_a?( User )
    where( '(friendships.user_id = users.id AND friendships.friend_id = ?) OR (friendships.user_id = ? AND friendships.friend_id = users.id)', other, other ).includes( :frienships )
  end

  def friend_with?( other )
    User.where( id: id ).friend_with( other ).any?
  end
end


class FriendshipsController < ApplicationController
  before_filter :check_friendship, only: :create

  def create
    @friendship = current_user.friendships.build( friend_id: params[:friend_id] )

    if @friendship.save
      redirect_to current_user, notice: 'Added friend.'
    else
      redirect_to current_user, alert: 'Unable to add friend.'
    end
  end

  private

  def check_friendship
    redirect_to( current_user, alert: 'Already a friend' ) if current_user.friend_with?( params[ :friend_id ] )
  end
end
于 2013-09-21T08:16:02.760 に答える
1

ここでの別のオプションは、Rails アプリから検証を削除し、データベースで一意性を強制することです。

これは型破りですが、余分なクエリの必要性を取り除き、データベース外のアプリケーションによって強制される行内検証が決して行われない方法であれば、完全に安全です。(したがって、ActiveRecord 検証をデータベース検証でバックアップすることに関する Rails ガイドのコメント。

モデルの保存にレスキュー ステップを追加して、RDBMS によってスローされた一意性エラーを処理し、それらを検証の失敗として扱います。ここにデータベースエラーのレスキューに関する良い情報があります: Rails 3 ignore Postgres unique constraint exception

私はこれを別のオプションとして提起したので、それを評価して、型にはまらないコードのトレードオフがあなたにとって価値があるかどうかを確認してください.

この種の方法論がアクティブレコードにカプセル化されることを本当に望んでいます。袖をまくり上げた方がいいのかな…

于 2013-09-21T08:35:56.743 に答える
0

カスタムバリデーターを使用して、「友情がすでに存在する」場合として、「A は B の友人である」または「B は A の友人である」のいずれかを確認できます。ただし、データベース ヒットの総数はおそらく減りません。バリデーターは、既に記述したものと同様のチェックを実行するだけです。コントローラーからロジックを移動するので、おそらくまだより良い方法ですが、パフォーマンスの向上は期待できません。

于 2013-09-21T08:15:09.897 に答える