phoenix-framework - 使用 Ecto.Multi 更新父记录和动态子记录
问题描述
我有两个对象,日志和块。日志属于块 - 如果一个日志和一个块共享一个标签,那么它们将被链接在一起。该链接存储在日志中。
更新日志相当简单。但是,如果您在块中编辑主题标签,则必须更新链接的日志并(可能)更新将链接的日志
为了解决这个批量更新,我将通过Ecto.Multi
. 但是,我不确定执行此操作的最佳方法。这是我当前的版本:
def update_block_then_logs( %Block{} = block, attrs, for: user ) do
Ecto.Multi.new()
|> Ecto.Multi.update( :block, Blocks.update_block(block, attrs) )
|> Ecto.Multi.run( :logs, fn _repo, %{block: block} ->
tags = block.tags |> String.split
# Returns logs that are currently set to this block and logs that will be set
log_changesets = list_logs_affected_by( block: block, tags: tags, for: user )
|> Enum.map( fn log ->
regenerate_blocks_for_log( log, for: user )
end)
errors = log_changesets
|> Enum.filter( fn {status, _changeset} -> status == :error end )
|> Enum.map( fn {_status, changeset} -> changeset end )
if errors != nil && Enum.count(errors) > 0 do
{ :error, errors }
else
{ :ok, log_changesets }
end
end)
|> Repo.transaction
end
regenerate_blocks_for_log
询问数据库(并假设 Block 已经更新)。它最终调用Repo.update( log_changeset )
而不是返回变更集本身。因为我正在使用Ecto.Multi.run
我必须创建一些错误捕获。我写的代码看起来不错,但后来我认为如果我将每个代码分离Repo.update
到对象中自己的操作中,可能会更好地捕获错误Ecto.Multi
。
所以我创建了这个版本:
def update_block_then_logs( %Block{} = block, attrs, for: user ) do
block_changeset = Blocks.update_block( block, attrs )
multi = Ecto.Multi.new()
|> Ecto.Multi.update( :block, block_changeset )
tags = block_changeset
|> Ecto.Changeset.get_field( :tags )
|> String.split
# Returns logs that are currently set to this block and logs that will be set
multi_log = list_logs_affected_by( block: block, tags: tags, for: user )
|> Enum.map( fn log ->
op_name = String.to_atom("log_#{log.id}")
log_changeset = regenerate_blocks_for_log( log, for: user )
Ecto.Multi.new
|> Ecto.Multi.update( op_name, log_changeset )
end)
|> Enum.reduce( Ecto.Multi.new(), &Ecto.Multi.append/2 )
Ecto.Multi.append( multi, multi_log )
|> Repo.transaction()
end
在这个版本中,regenerate_blocks_for_log
返回一个变更集。但是,我意识到这regenerate_blocks_for_log
是行不通的——因为它是为了读取事务中提交的数据而编写的(直到最后一条语句才会发生)。
我可以进行调整regenerate_blocks_for_log
,以便将尚未提交的 Block 更改考虑在内。但后来我想知道,有没有办法Multi
使用第一种技术动态改变(因为记录已在事务中提交)?
解决方案
基于此问题的原始帖子中的答案。Ecto.Multi.merge
允许您附加到Multi
事务中的 while:
defp update_logs( block, for: user ) do
tags = if block.tags == nil do [] else block.tags |> String.split end
# Returns logs that are currently set to this block and logs that will be set
list_logs_affected_by( block, tags, for: user )
|> Enum.map( fn log ->
op_name = String.to_atom("log_#{log.id}")
log_changeset = regenerate_blocks_for_log( log, for: user )
Ecto.Multi.new
|> Ecto.Multi.update( op_name, log_changeset )
end)
|> Enum.reduce( Ecto.Multi.new(), &Ecto.Multi.append/2 )
end
def update_block_then_logs( %Block{} = block, attrs, for: user ) do
Ecto.Multi.new()
|> Ecto.Multi.update( :block, Blocks.update_block(block, attrs) )
|> Ecto.Multi.merge( fn %{block: block} -> update_logs(block, for: user) end )
|> Repo.transaction
end
推荐阅读
- android - 如何查询解释 RAW_SENSOR 图像所需的信息?
- neural-network - Resnet18第一层输出维度
- c# - 大文件的 HttpContent.CopyToAsync
- amazon-web-services - 为出站 UDP 流量设置 ELB IP 地址
- javascript - 我如何将这段代码注入到对象值中?
- mysql - MySQL组合查询/从另一个中减去1
- git - Visual Studio 上的 GIT 远程扩展 - 显示大量未知更改
- python - AttributeError:“str”对象没有属性“weekday”
- ssl - 使用 Elastic Beanstalk 设置 SSL:如何修复 ERR_CERT_COMMON_NAME_INVALID?
- javascript - 使用 javascript 将 css 样式转换为内联样式,保持样式单元