设置 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
几天前,我开始将 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