设置 Phoenix Framework 和 Ecto 以使用 UUID:如何插入生成的值?

Setting up Phoenix Framework and Ecto to use UUIDs: how to insert the generated value?

几天前,我开始将 Elixir 和 Phoenix Framework (v 0.12.0) 与 Postgres 数据库一起使用。我正在尝试创建一个 table ,它有一个 UUID 主键,我更喜欢顺序默认值。

在使用 mix phoenix.gen.html 生成模型和迁移文件并遵循 Phoenix 文档中的其他步骤后,我更改了

def model do
    quote do
      use Ecto.Model
    end
  end

web.ex

def model do
  quote do
    use Ecto.Model
    @primary_key {:id, :uuid, []}
    @foreign_key_type :uuid
  end
end

如 Ecto 文档中所述。我也将迁移更改为

create table(:tblname, primary_key: false) do
  add :id, :uuid, primary_key: true
  [other columns]
end

不幸的是,当我尝试从自动生成的表单向 table 添加条目时,我收到错误消息,因为 id 为空。如果我手动将 id 列添加到模型中,我会收到一条错误消息,指出该列已存在。如果我忽略在 table/2 中将 primary_key 设置为 false 并删除 id 列,则 table 会生成一个顺序 id 列。

我是否需要在变更集中手动设置 id,或者我在将我的应用程序设置为使用 UUID 时犯了错误?提前致谢

编辑:我已将此答案更新为 Ecto v2.0。你可以在最后阅读之前的答案。

Ecto v2

自最初的答案以来,在 Ecto 中处理 UUID 变得更加直接。 Ecto 有两种 ID::id:binary_id。第一个是我们从数据库中知道的整数 ID,第二个是特定于数据库的二进制文件。对于 Postgres,它是一个 UUID。

要将 UUID 作为主键,首先在迁移中指定它们:

create table(:posts, primary_key: false) do
  add :id, :binary_id, primary_key: true
end

然后在你的模型模块中(schema 块外):

@primary_key {:id, :binary_id, autogenerate: true}

当您为 :binary_id 指定 :autogenerate 选项时,Ecto 将保证适配器或数据库会为您生成它。但是,如果您愿意,您仍然可以手动生成它。顺便说一句,您可以在迁移中使用 :uuid 而在架构中使用 Ecto.UUID 而不是 :binary_id:binary_id 的好处是它可以跨数据库移植。

Ecto v1

您需要告诉您的数据库如何自动为您生成UUID。或者您需要从应用程序端生成一个。就看你喜欢哪一个了。

在我们继续之前,重要的是要说明您正在使用 :uuid 它将 return 二进制文件而不是人类可读的 UUID。您很可能想使用 Ecto.UUID 将其格式化为字符串 (aaaa-bbb-ccc-...),这就是我将在下面使用的内容。

正在数据库中生成

在您的迁移中,为字段定义默认值:

add :id, :uuid, primary_key: true, default: fragment("uuid_generate_v4()")

我假设您 运行 使用 PostgreSQL。您需要在 pgAdmin 中使用 CREATE EXTENSION "uuid-ossp" 安装 uuid-ossp 扩展,或者在迁移中添加 execute "CREATE EXTENSION \"uuid-ossp\""。有关 the UUID generator can be found here.

的更多信息

回到 Ecto,在你的模型中,让 Ecto 在 insert/update:

之后从数据库中读取字段
@primary_key {:id, Ecto.UUID, read_after_writes: true}

现在,当你插入时,数据库会生成一个默认值,Ecto 会读回它。

正在应用程序中生成

您需要定义一个为您插入 UUID 的模块:

defmodule MyApp.UUID do
  def put_uuid(changeset) do
    Ecto.Changeset.put_change(changeset, :id, Ecto.UUID.generate())
  end
end

并将其用作回调:

def model do
  quote do
    use Ecto.Model
    @primary_key {:id, Ecto.UUID, []}
    @foreign_key_type Ecto.UUID
    before_insert MyApp.UUID, :put_uuid, []
  end
end

before_insert 是一个回调,它将使用给定的参数在给定的函数中调用给定的模块,其中一个代表正在插入的内容的变更集作为第一个参数给出。

应该就这些了。顺便说一句,将来这可能会更加精简。 :)

同样在创建新项目时通过选项--binary-id使用UUID作为默认主键。(开始Ecto v2

mix phx.new project_name --binary-id