8

Model.find_or_create_by(X)実際には次のことがよく知られています。

  1. X で選択
  2. 何も見つからない場合 -> X で作成
  3. レコードを返す (見つかった、または作成された)

ステップ 1 と 2 の間で競合状態が発生する可能性があります。データベースで X が重複するのを避けるには、X のフィールドのセットに一意のインデックスを使用する必要があります。ただし、一意のインデックスを適用すると、競合するトランザクションの 1 つが次のエラーで失敗します。例外 (X のコピーを作成しようとした場合)。

#find_or_create_by例外が発生せず、常に期待どおりに動作する「安全なバージョン」を実装するにはどうすればよいですか?

4

3 に答える 3

8

答えはドキュメントにあります

それが問題であるかどうかは、アプリケーションのロジックによって異なりますが、行に UNIQUE 制約がある特定のケースでは、例外が発生する可能性があります。再試行してください。

begin
  CreditAccount.find_or_create_by(user_id: user.id)
rescue ActiveRecord::RecordNotUnique
  retry
end

解決策 1

モデルに以下を実装するか、DRY を維持する必要がある場合は懸念に実装できます。

def self.find_or_create_by(*)
  super
rescue ActiveRecord::RecordNotUnique
  retry
end

使用法:Model.find_or_create_by(X)


解決策 2

または、上書きしたくない場合はfind_or_create_by、モデルに以下を追加できます

def self.safe_find_or_create_by(*args, &block)
  find_or_create_by *args, &block
rescue ActiveRecord::RecordNotUnique
  retry
end

使用法:Model.safe_find_or_create_by(X)

于 2015-10-07T09:59:31.067 に答える
3

これは、よくある問題と密接に関連する、「SELECT-or-INSERT」の繰り返しの問題UPSERTです。今後のPostgres 9.5 ではINSERT .. ON CONFLICT DO NOTHING | UPDATE、それぞれにクリーンなソリューションを提供するための新しい機能が提供されます。

Postgres 9.4 の実装

今のところ、サーバー側の plpgsql 関数を 2 つ使用するこの防弾実装をお勧めします。のヘルパー関数だけがINSERTより高価なエラー トラップを実装し、それSELECTが成功しない場合にのみ呼び出されます。

これにより、一意の違反が原因で例外が発生することはなく、常に行が返されます。

仮定:

  • データ型tblの列で名前が付けられたテーブルを想定しています。それに応じてケースに適応します。xtext

  • xUNIQUEまたはが定義されてPRIMARY KEYいます。

  • 基礎となるテーブル ( ) から行全体を返す必要がありますreturn a record (found or created)

  • 多くの場合、行はすでに存在します。(大部分のケースである必要はありませSELECTん。INSERTINSERT

ヘルパー関数:

CREATE OR REPLACE FUNCTION f_insert_x(_x text)
  RETURNS SETOF tbl AS
$func$
BEGIN
   RETURN QUERY
   INSERT INTO tbl(x) VALUES (_x) RETURNING *;

EXCEPTION WHEN UNIQUE_VIOLATION THEN  -- catch exception, no row is returned
   -- do nothing
END
$func$ LANGUAGE plpgsql;

主な機能:

CREATE OR REPLACE FUNCTION f_x(_x text)
  RETURNS SETOF tbl AS
$func$
BEGIN
   LOOP
      RETURN QUERY
      SELECT * FROM tbl WHERE x = _x
      UNION  ALL
      SELECT * FROM f_insert_x(_x)  -- only executed if x not found
      LIMIT  1;

      EXIT WHEN FOUND;       -- else keep looping
   END LOOP;
END
$func$ LANGUAGE plpgsql;

電話:

SELECT * FROM f_x('foo');

SQL フィドルのデモ。

この関数は、この関連する回答で私が解決したことに基づいています:

詳細な説明とリンクがあります。

また、ポリモーフィックな戻り値の型と動的 SQL を使用して汎用関数を作成し、特定の列とテーブルで機能することもできます (ただし、それはこの質問の範囲を超えています)

UPSERTCraig Ringerによるこの関連する回答の基本:

于 2015-10-07T03:44:27.653 に答える
1

find_or_create_byRailsで呼ばれるメソッドがあります

このリンクは、それをよりよく理解するのに役立ちます

しかし、個人的には、最初に検索を行い、何も見つからない場合は作成することを好みます (より詳細に制御できると思います)。

Ex:

user = User.find(params[:id])
#User.create(#attributes) unless user

HTH

于 2013-01-15T06:30:21.877 に答える