【发布时间】:2013-09-15 19:47:04
【问题描述】:
我有一个名为CachedObject 的类,它存储由键索引的通用序列化对象。我希望这个类实现一个create_or_update 方法。如果找到一个对象,它将更新它,否则它将创建一个新对象。
有没有办法在 Rails 中做到这一点,还是我必须编写自己的方法?
【问题讨论】:
标签: ruby-on-rails activerecord
我有一个名为CachedObject 的类,它存储由键索引的通用序列化对象。我希望这个类实现一个create_or_update 方法。如果找到一个对象,它将更新它,否则它将创建一个新对象。
有没有办法在 Rails 中做到这一点,还是我必须编写自己的方法?
【问题讨论】:
标签: ruby-on-rails activerecord
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
您可以使用块,但只有在记录是新记录时才会运行块。
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
现在您可以upsert(更新或插入)。
对于单条记录使用:
Model.upsert(column_name: value)
对于多条记录,请使用upsert_all:
Model.upsert_all(column_name: value, unique_by: :column_name)
注意:
【讨论】:
[#<Model id: 1, other_model_fk: 33, some_property: 'foo'>] 然后我们调用 Model.upsert(other_model_fk: 33, some_property: 'bar') 然后添加一个新行。 (顺便说一下,FK 列被索引了)。我们期望的行为“如果找到一个对象,它将更新它,否则它将创建一个新对象。” 似乎不起作用。它正在创建一个新对象。我在这里错过了什么?
老问题,但为了完整起见,我将我的解决方案扔进了环中。 当我需要一个特定的查找但不存在时,我需要它。
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
【讨论】:
sequel gem 添加了一个update_or_create 方法,似乎可以满足您的需求。
【讨论】: