以下のツイートが気になったの調べた。
「create_or_find_by のコメントを読もう」で済む世界は最高
— Takafumi ONAKA (@onk) 2020年10月27日
環境
$ bin/rails -v Rails 6.0.3.1
一覧
メソッド名 | 説明 |
---|---|
find_or_create_by |
対象を検索して、ない場合はCREATE(INSERT文の発行)をする |
find_or_initialize_by |
対象を検索して、ない場合はオブジェクトを作成(.new 相当)をする |
create_or_find_by |
新規作成を試みて、 ActiveRecord::RecordNotUnique の場合は既にあるのでfindをする |
find_or
の create
と initialize
の違い
実装を見てもらってもわかりやすい話だが、実際にcreateまで実行してしまうか、オブジェクト生成のみ留めるかの違い。
def find_or_create_by(attributes, &block) find_by(attributes) || create(attributes, &block) end def find_or_create_by!(attributes, &block) find_by(attributes) || create!(attributes, &block) end
create_or_find_by
について
このメソッドに関しては実態のソースに以下のようなコメントがある
1つまたは複数の列に一意の制約を持つテーブル内に、指定された属性を持つレコードを作成しようとします。これらの属性を持つ行が既に存在する場合は のような一意の制約がある場合、そのような挿入が通常発生するであろう例外が捕捉され、それらの属性を持つ既存のレコードがfind_byを使用して発見されます。
これはfind_or_create_byに似ていますが、SELECTとINSERTの間の古い読み込みの問題を回避します。
しかし、create_or_find_byにはいくつかの欠点があります。
- 基底となるテーブルは、一意の制約で関連する列を定義していなければなりません。
- 一意な制約違反は、与えられた属性のうち1つだけ、または少なくともすべての属性よりも少ない属性によってトリガされる可能性があります。これは,後続のfind_byが一致するレコードの検索に失敗する可能性があることを意味し,与えられた属性を持つレコードではなく,ActiveRecord::RecordNotFoundの例外を発生させます.
- find_or_create_byからSELECT -> INSERTの間の競合状態を回避していますが、実際にはINSERT -> SELECTの間に別の競合状態があり、これら2つのステートメントの間のDELETEが別のクライアントによって実行された場合にトリガーされます。しかし、ほとんどのアプリケーションでは、これはヒットする可能性がかなり低い条件です。
- このメソッドは、制御フローを処理するために例外処理に依存しており、多少遅くなるかもしれません。
このメソッドは、すべての与えられた属性が一意の制約によってカバーされている場合(INSERT -> DELETE -> SELECTの競合条件がトリガされない限り)、レコードを返しますが、作成が試みられ、検証エラーのために失敗した場合、それは永続化されません、あなたはそのような状況でcreateが返すものを取得します。
というように、既存レコードが存在することが設定された値からユニーク成約で発覚するケースにおいては利用することができる。