如何在Ecto迁移中动态更新字段值?

时间:2017-05-21 14:32:01

标签: elixir ecto

我有一个用户表,如:

     email     | username
---------------+----------
 123@321.com   |
 123@123.com   |
 haha@haha.com |

我希望按username字段更新email字段,只需在email之前对@进行切片。

     email     | username
---------------+----------
 123@321.com   | 123
 123@123.com   | 123
 haha@haha.com | haha

我尝试使用以下迁移:

defmodule MyApp.Repo.Migrations.AddDefaultUsernameForUsers do
  use Ecto.Migration
  import Ecto.Query

  def up do
      from(u in MyApp.User, update: [set: [username: String.split(u.email, "@") |> List.first ]])
        |> MyApp.Repo.update_all([])
  end

  def down do
      MyApp.Repo.update_all(MyApp.User, set: [username: nil])
  end
end

但是在运行迁移时,我收到以下错误:

$ mix ecto.migrate
** (Ecto.Query.CompileError) `List.first(String.split(u.email(), "@"))` is not a valid query expression

我该如何解决这个问题?

2 个答案:

答案 0 :(得分:2)

您将要进行两次单独的查询。一个查询来获取数据,执行您想要的任何更改,然后是第二个查询来更新该数据。

的内容
Repo.all(MyApp.User)
|> Enum.map(fn u ->
  username = 
    u.email
    |> String.split("@")
    |> List.first()

  Ecto.Changeset.cast(u, %{username: username})
end)
|> Repo.update_all()

有一些事情正在发生,为什么你不能做你想做的事情。

如果要在Ecto查询中使用Elixir函数或值,通常必须使用pin运算符(^)。因此,如果您想查询特定ID,可以使用from(u in MyApp.User, where: u.id == ^12)。所以你的反应可能是尝试使用^List.first(String.split(u.email, "@"))。但是,这不起作用,因为......

u中的from(u in MyApp.User)是数据库中的记录。您无法在Elixir代码中访问它。可能会使用fragment/1来执行您要执行的操作,但是您不能使用常规Elixir函数操作该值,直到您使用上面的示例实际将其从数据库中拉出来。

答案 1 :(得分:2)

@Justin Wood解释了为什么你不能在更新查询中使用Elixir函数,所以我不再重复了。在PostgreSQL中,您可以使用带有正则表达式的@函数在substring之前提取文本,这将适用于更新查询。这将比加载记录然后逐个更新它们更快,但如果不调整SQL片段,它将无法与其他数据库引擎一起使用:

from(u in MyApp.User,
  update: [set: [username: fragment("substring(? from '^(.*?)@')", u.email)]])
|> MyApp.Repo.update_all([])
postgres=# select substring('123@321.com' from '^(.*?)@');
 substring
-----------
 123
(1 row)