将多个可链接的update_all调用减少为单个UPDATE语句

时间:2017-05-21 10:58:22

标签: elixir ecto

所以,让我们说我有一个用户模块,它有2个接受变更集并添加更改的功能,如下所示 - >

defmodule MyApp.User do

  def confirm(changeset_or_struct) do
    changeset_or_struct
    |> Ecto.Changeset.change(confirmed: true, confirmed_at: Timex.now())
  end

  def update_session(changeset_or_struct, ip_address) do
    changeset_or_struct
    |> Ecto.Changeset.change(session_token: "token", ip_address: ip_address)
  end
end

因此,如果我需要将这两个更改应用于特定用户并保存,我可以轻松链接这些函数调用

some_user
|> User.confirm()
|> User.update_session("ip_address")
|> Repo.update!()

所以这一切都很好。

现在,让我们说由于某种原因,我需要以原子方式同时确认和更新许多用户的会话。显然,获取所有这些用户,锁定它们然后在事务中逐个更新并不是一个非常好的选择,因此我们需要利用update_all函数只发送一个UPDATE语句。

所以我们需要的是这个 - >

User
|> Repo.update_all([set: confirmed: true, confirmed_at: Timex.now(), session_token: "token", ip_address: "ip_address"])

但是在User模块之外调用此代码并不是一个好主意,因为我只希望User模块知道如何处理确认和会话更新。所以下一个明显的决定是在User模块中创建一个函数并将该代码放在那里,但问题在于它不灵活,因为这两个动作(确认和会话更新)耦合在一起现在。但我们可以把它分成两个函数

def confirm_many(query) do
  query
  |> Repo.update_all([set: confirmed: true, confirmed_at: Timex.now()])
end


def update_session_many(query, ip_address) do
  query
  |> Repo.update_all([set: session_token: "token", ip_address: ip_address])
end

即使现在它们现在没有耦合,但它们都不是可链接的,所以尽管我可以单独使用这两个,但最有可能的是,我最终会得到一段丑陋的代码和一堆UPDATE语句。它可以很容易地减少到只有一个。所以现在问题是:

我该怎么做?有没有办法将一堆独立的更新链接起来,最终可以简化为update_all次呼叫?

所以最重要的是我希望能够做到这样的事情:

User
|> where([u], u.id in bunch_of_ids)
|> User.confirm_many()
|> User.update_session_many("ip_address")
|> Repo.update_all()

1 个答案:

答案 0 :(得分:1)

您可以使用Ecto.Query.update逐步构建更新查询,并在结尾处仅将其传递给Repo.update_all

defmodule MyApp.User do
  ...
  def confirm_many(query) do
    query
    |> update([set: [confirmed: true, confirmed_at: ^Ecto.DateTime.utc()]])
  end


  def update_session_many(query, ip_address) do
    query
    |> update([set: [session_token: "token", ip_address: ^ip_address]])
  end
end

有了这个,下面的代码:

import Ecto.Query
alias MyApp.{Repo, User}

User
|> where([u], u.id in [1, 2, 3])
|> User.confirm_many()
|> User.update_session_many("ip_address")
|> Repo.update_all([])

执行查询:

UPDATE "users" AS u0
  SET "confirmed" = TRUE,
      "confirmed_at" = $1,
      "session_token" = 'token',
      "ip_address" = $2
  WHERE (u0."id" IN (1,2,3))
[{{2017, 5, 21}, {11, 13, 43, 0}}, "ip_address"]