CachedObject
キーによってインデックス付けされた一般的なシリアル化されたオブジェクトを格納するというクラスがあります。このクラスにメソッドを実装させたいcreate_or_update
。オブジェクトが見つかった場合はそれを更新し、そうでない場合は新しいオブジェクトを作成します。
Rails でこれを行う方法はありますか、それとも独自のメソッドを作成する必要がありますか?
CachedObject
キーによってインデックス付けされた一般的なシリアル化されたオブジェクトを格納するというクラスがあります。このクラスにメソッドを実装させたいcreate_or_update
。オブジェクトが見つかった場合はそれを更新し、そうでない場合は新しいオブジェクトを作成します。
Rails でこれを行う方法はありますか、それとも独自のメソッドを作成する必要がありますか?
Rails 6 では、この機能を提供するupsert
andupsert_all
メソッドが追加されました。
Model.upsert(column_name: value)
[アップサート] モデルをインスタンス化することも、Active Record コールバックや検証をトリガーすることもありません。
「アップサート」(データベースが同じ操作で更新または挿入ステートメントを実行する)タイプのステートメントを探している場合はそうではありません。Rails と ActiveRecord には、すぐに使用できる機能はありません。ただし、 upsert gemを使用することはできます。
それ以外の場合は、 :find_or_initialize_by
またはを使用できます。find_or_create_by
これらは同様の機能を提供しますが、追加のデータベース ヒットが発生しますが、ほとんどの場合、ほとんど問題にはなりません。したがって、重大なパフォーマンス上の問題がない限り、私はこの gem を使用しません。
たとえば、「Roger」という名前のユーザーが見つからない場合、「Roger」にname
設定された新しいユーザー インスタンスがインスタンス化されます。
user = User.where(name: "Roger").first_or_initialize
user.email = "email@example.com"
user.save
または、 を使用することもできますfind_or_initialize_by
。
user = User.find_or_initialize_by(name: "Roger")
Rails 3で。
user = User.find_or_initialize_by_name("Roger")
user.email = "email@example.com"
user.save
ブロックを使用できますが、ブロックはレコードが new の場合にのみ実行されます。
User.where(name: "Roger").first_or_initialize do |user|
# this won't run if a user with name "Roger" is found
user.save
end
User.find_or_initialize_by(name: "Roger") do |user|
# this also won't run if a user with name "Roger" is found
user.save
end
レコードの永続性に関係なくブロックを使用する場合はtap
、結果に対して次を使用します。
User.where(name: "Roger").first_or_initialize.tap do |user|
user.email = "email@example.com"
user.save
end
Rails 4 では、特定のモデルに追加できます。
def self.update_or_create(attributes)
assign_or_new(attributes).save
end
def self.assign_or_new(attributes)
obj = first || new
obj.assign_attributes(attributes)
obj
end
そしてそれを次のように使用します
User.where(email: "a@b.com").update_or_create(name: "Mr A Bbb")
または、これらのメソッドを初期化子に入れられたすべてのモデルに追加したい場合:
module ActiveRecordExtras
module Relation
extend ActiveSupport::Concern
module ClassMethods
def update_or_create(attributes)
assign_or_new(attributes).save
end
def update_or_create!(attributes)
assign_or_new(attributes).save!
end
def assign_or_new(attributes)
obj = first || new
obj.assign_attributes(attributes)
obj
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecordExtras::Relation
これをモデルに追加します。
def self.update_or_create_by(args, attributes)
obj = self.find_or_create_by(args)
obj.update(attributes)
return obj
end
これにより、次のことができます。
User.update_or_create_by({name: 'Joe'}, attributes)
古い質問ですが、完全を期すために私の解決策をリングに投げ込みます。特定の検索が必要なときにこれが必要でしたが、存在しない場合は別の作成が必要でした。
def self.find_by_or_create_with(args, attributes) # READ CAREFULLY! args for finding, attributes for creating!
obj = self.find_or_initialize_by(args)
return obj if obj.persisted?
return obj if obj.update_attributes(attributes)
end
続編の宝石は、探していることを行うように見えるupdate_or_createメソッドを追加します。