我有一个名为的类CachedObject
,用于存储按键索引的通用序列化对象。我希望此类实现一个create_or_update
方法。如果找到一个对象,它将对其进行更新,否则它将创建一个新对象。
在Rails中有没有办法做到这一点,还是我必须编写自己的方法?
Answers:
Rails 6添加了提供此功能的upsert
和upsert_all
方法。
Model.upsert(column_name: value)
[upsert]它不会实例化任何模型,也不会触发Active Record回调或验证。
如果要查找“ upsert”(数据库在同一操作中执行更新或插入语句)类型的语句,则不会。开箱即用,Rails和ActiveRecord没有这种功能。但是,您可以使用upsert gem。
否则,您可以使用:find_or_initialize_by
或find_or_create_by
,它们提供类似的功能,尽管要付出额外的数据库命中代价,在大多数情况下,这根本就不是问题。因此,除非您有严重的性能问题,否则我不会使用gem。
例如,如果未找到名称为“ 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
您可以使用一个块,但是该块仅在记录为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
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
您可以在以下一条语句中完成此操作:
CachedObject.where(key: "the given key").first_or_create! do |cached|
cached.attribute1 = 'attribute value'
cached.attribute2 = 'attribute value'
end