CachedObject
キーでインデックス付けされた一般的なシリアル化されたオブジェクトを格納するというクラスがあります。このクラスにcreate_or_update
メソッドを実装してほしい。オブジェクトが見つかった場合は更新され、それ以外の場合は新しいオブジェクトが作成されます。
Railsでこれを行う方法はありますか、または独自のメソッドを記述する必要がありますか?
回答:
Rails 6では、この機能を提供するupsert
およびupsert_all
メソッドが追加されました。
Model.upsert(column_name: value)
[upsert]モデルをインスタンス化したり、アクティブレコードのコールバックや検証をトリガーしたりすることはありません。
「アップサート」(データベースが同じ操作で更新または挿入ステートメントを実行する)タイプのステートメントを探している場合は除きます。そのままでは、RailsとActiveRecordにはそのような機能はありません。ただし、upsert gem を使用できます。
それ以外の場合は、以下を使用できます。find_or_initialize_by
またはfind_or_create_by
、これは同様の機能を提供しますが、追加のデータベースヒットを犠牲にして行われますが、ほとんどの場合、問題はほとんどありません。したがって、パフォーマンスに深刻な懸念がない限り、私はこの宝石を使用しません。
たとえば、「Roger」という名前のユーザーが見つからない場合、新しいユーザーインスタンスがname
「Roger」に設定されてインスタンス化されます。
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
ブロックを使用できますが、ブロックは、レコードが新しい場合にのみ実行されます。
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
find_or_initialize_by
をfind_or_create_by
受け入れます レコードが存在するかどうかに関係なく、更新を行うために、レコードオブジェクトを引数としてブロックが渡されることを意味していると思いました。
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
assign_or_new
テーブルの最初の行が存在する場合はそれを返さず、その行が更新されますか?私にはそうしているようです。
User.where(email: "a@b.com").first
見つからない場合はnilを返します。where
スコープがあることを確認してください
updated_at
ので触れられないというだけのメモassign_attributes
これをモデルに追加します。
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)
あなたが探してきた魔法が追加されていRails 6
ますができます今すぐアップサート(更新または挿入)。単一レコード使用の場合:
Model.upsert(column_name: value)
複数のレコードの場合はupsert_allを使用します。
Model.upsert_all(column_name: value, unique_by: :column_name)
注:
古い質問ですが、完全性のために私の解決策をリングに投げ込みます。特定の検索が必要なときにこれが必要でしたが、存在しない場合は別の作成が必要でした。
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
次のような1つのステートメントで実行できます。
CachedObject.where(key: "the given key").first_or_create! do |cached|
cached.attribute1 = 'attribute value'
cached.attribute2 = 'attribute value'
end