ฉันมีคลาสที่เรียกCachedObject
ว่าเก็บอ็อบเจ็กต์อนุกรมทั่วไปที่จัดทำดัชนีด้วยคีย์ ฉันต้องการให้คลาสนี้ใช้create_or_update
วิธีการ หากพบวัตถุจะทำการอัปเดตมิฉะนั้นจะสร้างวัตถุใหม่
มีวิธีทำใน Rails หรือไม่หรือต้องเขียน method ของตัวเอง?
ฉันมีคลาสที่เรียกCachedObject
ว่าเก็บอ็อบเจ็กต์อนุกรมทั่วไปที่จัดทำดัชนีด้วยคีย์ ฉันต้องการให้คลาสนี้ใช้create_or_update
วิธีการ หากพบวัตถุจะทำการอัปเดตมิฉะนั้นจะสร้างวัตถุใหม่
มีวิธีทำใน Rails หรือไม่หรือต้องเขียน method ของตัวเอง?
คำตอบ:
Rails 6 ได้เพิ่มupsert
และupsert_all
วิธีการที่ให้ฟังก์ชันนี้
Model.upsert(column_name: value)
[upsert] มันไม่ได้สร้างอินสแตนซ์โมเดลใด ๆ และไม่ทริกเกอร์การเรียกกลับหรือการตรวจสอบ Active Record
ไม่ใช่ถ้าคุณกำลังมองหาคำสั่งประเภท "upsert" (ที่ฐานข้อมูลเรียกใช้การอัปเดตหรือคำสั่งแทรกในการดำเนินการเดียวกัน) Rails และ ActiveRecord นอกกรอบไม่มีคุณสมบัติดังกล่าว อย่างไรก็ตามคุณสามารถใช้อัญมณีเสริมดวงได้
มิฉะนั้นคุณสามารถใช้: 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")
หรือหากคุณต้องการเพิ่มวิธีการเหล่านี้ให้กับทุกรุ่นที่ใส่ใน initializer:
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
จะคืนค่าศูนย์หากไม่พบ ตรวจสอบให้แน่ใจว่าคุณมี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
คุณสามารถทำได้ในคำสั่งเดียวดังนี้:
CachedObject.where(key: "the given key").first_or_create! do |cached|
cached.attribute1 = 'attribute value'
cached.attribute2 = 'attribute value'
end
ผลสืบเนื่องอัญมณีเพิ่มupdate_or_createวิธีการซึ่งดูเหมือนว่าจะทำในสิ่งที่คุณกำลังมองหา