给出一个定义如下的模型:
class Foo < ActiveRecord::Base
validates_uniqueness_of :bar
end
以及对它们执行特定操作的服务:
class FooService
class << self
def run(bars_by_foo_id)
foos = Foo.find(bars_by_foo_id.keys).index_by(&:id)
ActiveRecord::Base.transaction do
bars_by_foo_id.each do |foo_id, bar|
foo = foos[foo_id]
foo.bar = bar
foo.save!
end
end
end
end
end
当我尝试这样做时:
# Works
FooService.run({ 1: "a", 2: "b" })
# Breaks
FooService.run({ 1: "b", 2: "a" })
对.run
的第一次调用将正确的bar
分配给每个Foo
,但是显然第二个中断了,因为尝试将"b"
分配给第一个{ {1}},此值不再唯一。
我尝试过:
Foo
但是显然,这只会进行几次单独的更新,但仍然会中断。
实现此目标的正确方法是什么?我可以在一个SQL语句中做到吗?我的数据库中没有唯一的约束,只有模型中有。
答案 0 :(得分:0)
我想出的最丑陋的方法是:
def run(bars_by_foo_id)
ActiveRecord::Base.transaction do
ActiveRecord::Base.connection.execute(sql_update(bars_by_foo_id))
foos = Foo.find(bars_by_foo_id.keys)
foos.each(&:validate!)
end
end
private
def sql_update(bars_by_foo_id)
<<-SQL
UPDATE foos SET
bar = bars_by_foo_id.bar
FROM (VALUES
#{update_values(bars_by_foo_id)}
) AS bars_by_foo_id(id, bar)
WHERE bars_by_foo_id.id = foos.id;
SQL
end
def update_values(bars_by_foo_id)
bars_by_foo_id.map do |id, bar|
"(#{id}, #{bar})"
end.join(",")
end