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

use*_*095 4 elixir ecto

我有一个用户表,如:

     email     | username
---------------+----------
 123@321.com   |
 123@123.com   |
 haha@haha.com |
Run Code Online (Sandbox Code Playgroud)

我想username逐个email字段更新,只需将emailbefore切片@。

     email     | username
---------------+----------
 123@321.com   | 123
 123@123.com   | 123
 haha@haha.com | haha
Run Code Online (Sandbox Code Playgroud)

我尝试使用以下迁移:

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
Run Code Online (Sandbox Code Playgroud)

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

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

我该如何解决这个问题?

Dog*_*ert 5

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

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