ruby-on-rails - 使用 ActiveRecord 交换唯一性验证值
问题描述
给定一个这样定义的模型:
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
给 each Foo
,但第二次调用显然会中断,因为当尝试分配"b"
给 firstFoo
时,该值不再是唯一的。
我试过了:
def run(bars_by_foo_id)
Foo.update(
bars_by_foo_id.keys,
bars_by_foo_id.values.map do |bar|
{ bar: bar }
end
)
end
但显然这只会进行几次单独的更新并且仍然会中断。
实现这一目标的正确方法是什么?我可以在单个 SQL 语句中进行此操作吗?我的数据库中没有唯一约束,只有我的模型中。
解决方案
我能想出的最不丑的方法是:
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
推荐阅读
- python - 如何让不和谐机器人对过去的私人消息做出反应?
- swagger - Swagger - 如何在响应组件中定义 byte[][]
- c - 检测是否存在任何其他线程的方法(例如,在 fork 之前)
- javascript - 客户端的打字稿模块-最好的方法是什么?
- spring - java.lang.IllegalArgumentException:参数值 [232cb466-6af9-4d7e-85ec-cc72c310d7fd] 与预期类型 [java.util.UUID] 不匹配
- c++ - 构造函数的 C++ 直接和复制初始化问题
- python - python安装后看不到ActiveTcl
- cplex - CPLEX OPL IP 间隙在主块中并在模型中执行
- java - 根据 JFrame 的大小更改 JPanel 组件的大小
- javascript - 单击时如何将搜索结果添加到文本框(如多选框(药盒))