使用凤凰中已有表之间的多对多关系
Working with many to many relationship between already existing tables in phoenix
我有一个 web-application 是在 elixir/phoenix 中制作的。
我将把我的解释集中在标题上。
所以我在系统中有一个叫做 roles
的东西,还有一个叫做 permissions
的东西。
roles
通过中间 table role_permissions
.
与 permissions
有 many-to-many
关系
三者的模式大致如下:
角色
schema "roles" do
# associations
has_many(:users, User)
many_to_many(:permissions, Permission, join_through: "role_permissions")
field(:name, :string)
field(:description, :string)
timestamps()
end
@required_params ~w(name)a
@optional_params ~w(description)a
@create_params @required_params ++ @optional_params
@update_params @required_params ++ @optional_params
@doc """
Returns a changeset to create a new `role`.
"""
@spec create_changeset(t, map) :: Ecto.Changeset.t()
def create_changeset(%__MODULE__{} = role, params) do
role
|> cast(params, @create_params)
|> common_changeset()
end
权限
schema "permissions" do
field(:code, :string)
field(:description, :string)
timestamps()
end
role_permissions
schema "permissions" do
belongs_to(:role, Role)
belongs_to(:permission, Permission)
timestamps()
end
permissions
可以与角色分开创建,所以我有 CRUD 函数来创建它们。
但是,在创建 roles
时,它们应该与 permissions
相关联。
为此,我使用了如下所示的创建函数
def create(%{permissions: permissions} = params) when permissions != [] do
Multi.new()
|> Multi.run(:role, fn _ ->
QH.create(Role, params, Repo)
end)
|> permissions_multi(params)
|> persist()
end
defp permissions_multi(multi, params) do
Multi.run(multi, :permission, fn %{role: role} ->
role_permissions = associate_role_permissions(role, params[:permissions])
{count, _} = Repo.insert_all(RolePermission, role_permissions)
{:ok, count}
end)
end
defp associate_role_permissions(role, permissions) do
Enum.map(permissions, fn permission ->
[permission_id: permission,
role_id: role.id,
inserted_at: DateTime.utc_now,
updated_at: DateTime.utc_now]
end)
end
# Run the accumulated multi struct
defp persist(multi) do
case Repo.transaction(multi) do
{:ok, %{role: role}} ->
{:ok, role}
{:error, _, _, _} = error ->
error
end
end
create/1
采用 name
、description
和 permissions
,这是 permission_ids 的列表。
首先将角色插入到数据库中。插入后,我收到 {:ok, role}
我使用 associate_role_permission
将 role_id 与所有权限 ID 配对,并使用 Repo.insert_all
在中间插入 table。
一切正常,我已经为此编写了测试并且它有效。
当我和 phoenix 一起搬到 UI 时,问题出现了 。
控制器
def new(conn, _params) do
changeset = RoleSchema.create_changeset(%RoleSchema{}, %{permissions: nil})
render(conn, "new.html", changeset: changeset)
end
"new.html"如下:
<%= form_for @changeset, @action, [as: :role], fn f -> %>
<%= input f, :name %>
<%= input f, :description %>
<%= multiple_select(f, :permissions, formatted_list(:permissions))%>
<%= submit "Submit", class: "btn btn-primary submit-btn" %>
<%= link("Cancel", to: role_path(@conn, :index), class: "btn btn-primary") %>
<% end %>
我在这里要做的是为 multi-select 创建一个下拉菜单
我以 [{:code, :id}] 格式加载的权限列表,效果很好。
但我不断收到错误消息
protocol Phoenix.HTML.Safe not implemented for #Ecto.Association.NotLoaded<association :permissions is not loaded>.
我知道这个错误是因为在变更集中数据字段有
未加载权限。
解决此问题的方法是什么?
有没有办法修改 changeset.data
字段中的特定字段,这种方法是否正确?
您必须显式 Ecto.Query.preload/3
each association, Ecto
won’t do it silently. Your RoleSchema
has an association there in :permissions
and since it’s not preloaded in the newly created changeset, it returns the Ecto.Association.NotLoaded
结构,它会临时替换实际值,直到加载关联。
因为你在这里创建一个changeset
,只需显式更新它的数据,如:
changeset = RoleSchema.create_changeset(%RoleSchema{})
changeset = update_in(changeset.data, &Repo.preload(&1, :permissions))
...
我有一个 web-application 是在 elixir/phoenix 中制作的。
我将把我的解释集中在标题上。
所以我在系统中有一个叫做 roles
的东西,还有一个叫做 permissions
的东西。
roles
通过中间 table role_permissions
.
permissions
有 many-to-many
关系
三者的模式大致如下:
角色
schema "roles" do
# associations
has_many(:users, User)
many_to_many(:permissions, Permission, join_through: "role_permissions")
field(:name, :string)
field(:description, :string)
timestamps()
end
@required_params ~w(name)a
@optional_params ~w(description)a
@create_params @required_params ++ @optional_params
@update_params @required_params ++ @optional_params
@doc """
Returns a changeset to create a new `role`.
"""
@spec create_changeset(t, map) :: Ecto.Changeset.t()
def create_changeset(%__MODULE__{} = role, params) do
role
|> cast(params, @create_params)
|> common_changeset()
end
权限
schema "permissions" do
field(:code, :string)
field(:description, :string)
timestamps()
end
role_permissions
schema "permissions" do
belongs_to(:role, Role)
belongs_to(:permission, Permission)
timestamps()
end
permissions
可以与角色分开创建,所以我有 CRUD 函数来创建它们。
但是,在创建 roles
时,它们应该与 permissions
相关联。
为此,我使用了如下所示的创建函数
def create(%{permissions: permissions} = params) when permissions != [] do
Multi.new()
|> Multi.run(:role, fn _ ->
QH.create(Role, params, Repo)
end)
|> permissions_multi(params)
|> persist()
end
defp permissions_multi(multi, params) do
Multi.run(multi, :permission, fn %{role: role} ->
role_permissions = associate_role_permissions(role, params[:permissions])
{count, _} = Repo.insert_all(RolePermission, role_permissions)
{:ok, count}
end)
end
defp associate_role_permissions(role, permissions) do
Enum.map(permissions, fn permission ->
[permission_id: permission,
role_id: role.id,
inserted_at: DateTime.utc_now,
updated_at: DateTime.utc_now]
end)
end
# Run the accumulated multi struct
defp persist(multi) do
case Repo.transaction(multi) do
{:ok, %{role: role}} ->
{:ok, role}
{:error, _, _, _} = error ->
error
end
end
create/1
采用 name
、description
和 permissions
,这是 permission_ids 的列表。
首先将角色插入到数据库中。插入后,我收到 {:ok, role}
我使用 associate_role_permission
将 role_id 与所有权限 ID 配对,并使用 Repo.insert_all
在中间插入 table。
一切正常,我已经为此编写了测试并且它有效。
当我和 phoenix 一起搬到 UI 时,问题出现了 。
控制器
def new(conn, _params) do
changeset = RoleSchema.create_changeset(%RoleSchema{}, %{permissions: nil})
render(conn, "new.html", changeset: changeset)
end
"new.html"如下:
<%= form_for @changeset, @action, [as: :role], fn f -> %>
<%= input f, :name %>
<%= input f, :description %>
<%= multiple_select(f, :permissions, formatted_list(:permissions))%>
<%= submit "Submit", class: "btn btn-primary submit-btn" %>
<%= link("Cancel", to: role_path(@conn, :index), class: "btn btn-primary") %>
<% end %>
我在这里要做的是为 multi-select 创建一个下拉菜单 我以 [{:code, :id}] 格式加载的权限列表,效果很好。
但我不断收到错误消息
protocol Phoenix.HTML.Safe not implemented for #Ecto.Association.NotLoaded<association :permissions is not loaded>.
我知道这个错误是因为在变更集中数据字段有 未加载权限。
解决此问题的方法是什么?
有没有办法修改 changeset.data
字段中的特定字段,这种方法是否正确?
您必须显式 Ecto.Query.preload/3
each association, Ecto
won’t do it silently. Your RoleSchema
has an association there in :permissions
and since it’s not preloaded in the newly created changeset, it returns the Ecto.Association.NotLoaded
结构,它会临时替换实际值,直到加载关联。
因为你在这里创建一个changeset
,只需显式更新它的数据,如:
changeset = RoleSchema.create_changeset(%RoleSchema{})
changeset = update_in(changeset.data, &Repo.preload(&1, :permissions))
...