Rails find_or_create_by : 查找具有给定属性的第一条记录,或创建一条记录
def find_or_create_by(attributes, &block)
find_by(attributes) || create(attributes, &block)
end
但请注意,此方法不是原子方法,不是线程安全的。它首先运行SELECT,如果没有结果,则尝试INSERT。如果还有其他线程或进程,则这两个调用之间存在竞争状态,并且最终可能会有两个相似的记录。
是否存在问题取决于应用程序的逻辑,但是在某些行具有UNIQUE约束的特定情况下,可能会引发异常,
即增加数据库字段唯一索引,通过以下代码重试:
begin
CreditAccount.transaction(requires_new: true) do
CreditAccount.find_or_create_by(user_id: user.id)
end
rescue ActiveRecord::RecordNotUnique
retry
end
在Rails 6中引入了 create_or_find_by, create_or_find_by! 方法,当然你也可以在你的Rails 5 + 中自己实现该逻辑
def create_or_find_by(attributes, &block)
transaction(requires_new: true) { create(attributes, &block) }
rescue ActiveRecord::RecordNotUnique
find_by!(attributes)
end
# Like #create_or_find_by, but calls
# {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
# is raised if the created record is invalid.
def create_or_find_by!(attributes, &block)
transaction(requires_new: true) { create!(attributes, &block) }
rescue ActiveRecord::RecordNotUnique
find_by!(attributes)
end
尝试在具有唯一约束的表中创建给定属性的记录。如果数据库已经存在唯一约束的记录,则会引起插入异常,并捕获ActiveRecord::RecordNotUnique,并使用 #find_by 查找具有这些属性的记录。
这与 #find_or_create_by 相似,但是避免了过时读取的问题,因为find_or_create_by需要首先查询表,然后在找不到行的情况下尝试插入行。SELECT和INSERT语句之间留有时间间隔,这可能会在高吞吐量应用程序中引起问题。 不过,#create_or_find_by有几个缺点:如果主键类型是int,可能会出现主键用尽的问题(但是从Rails 5.0 开始就使用bigint
,即使您每天调用此方法1000万次,用完ID也要花费25亿年。),一定程度上,您不需要担心此问题