25

几天前,我开始在 Postgres 数据库中使用 Elixir 和 Phoenix Framework (v 0.12.0)。我正在尝试创建一个具有 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

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

我需要id在变更集中手动设置,还是在设置我的应用程序以使用 UUID 时出错?提前致谢

4

2 回答 2

51

编辑:我已将此答案更新为 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}

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

Ecto v1

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

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

在数据库中生成

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

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

我假设您在 PostgreSQL 上运行。您需要CREATE EXTENSION "uuid-ossp"在 pgAdmin 中安装 uuid-ossp 扩展或添加execute "CREATE EXTENSION \"uuid-ossp\""迁移。有关UUID 生成器的更多信息,请参见此处。

回到 Ecto,在您的模型中,让 Ecto 在插入/更新后从数据库中读取字段:

@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是一个回调,它将使用给定的参数在给定的函数处调用给定的模块,其中一个变更集表示正在插入的内容作为第一个参数给出。

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

于 2015-05-02T16:41:39.360 回答
13

此外,在创建新项目传递选项--binary-id以使用 UUID 作为默认主键时。(启动Ecto v2

mix phx.new project_name --binary-id
于 2017-09-26T06:01:15.813 回答