我有两个对象,日志和块。日志属于“块”-如果“日志”和“块”共享一个标签,则它们将被链接在一起。链接存储在日志中。
更新日志非常简单。但是,如果您在块中编辑主题标签,则必须更新链接的日志并(可能)更新将要链接的日志
为了解决此批量更新,我将通过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
询问数据库(并假定该块已被更新)。它最终会调用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
(因为记录已在事务中提交)?
答案 0 :(得分:0)
基于original post中对此问题的回答。 Ecto.Multi.merge
允许您在事务内追加到Multi
:
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