在 Phoenix 中处理嵌套表单/ecto 变更集的正确方法是什么?
What is the proper way of handling nested forms / ecto changesets in Phoenix?
我正在 Phoenix 中编写一个简单的 CRUD 应用程序,管理员在创建新组织时可以为其提供初始员工帐户。
组织和用户之间的关系实际上是多对多的。
我得出以下结论:
用户架构:
defmodule MyApp.User do
use MyApp.Web, :model
schema "users" do
field :name, :string
field :email, :string
field :password, :string, virtual: true
field :password_hash, :string
end
def changeset(...) # validate email, password confirmation etc.
组织架构:
defmodule MyApp.Org do
use MyApp.Web, :model
schema "orgs" do
field :official_name, :string
field :common_name, :string
has_many :org_staff_users, MyApp.OrgStaffUser
has_many :users, through: [:org_staff_users, :user]
end
def changeset(model, params \ :empty) do
model
|> cast(params, ~w(official_name common_name), [])
end
def provisioning_changeset(model, params \ :empty) do
model
|> changeset(params)
|> cast_assoc(:org_staff_users, required: true)
end
Junction table org_staff_users
和相应的 Ecto Schema with
user_id
和 org_id
具有以下 new
操作的控制器:
def new(conn, _params) do
data = %Org{org_staff_users: [%User{}]}
changeset = Org.provisioning_changeset(data)
render(conn, "new.html", changeset: changeset)
end
模板摘录如下:
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= text_input f, :official_name, class: "form-control" %>
<%= text_input f, :common_name, class: "form-control" %>
<%= inputs_for f, :org_staff_users, fn i -> %>
<%= text_input f, :email, class: "form-control" %>
<%= text_input f, :password, class: "form-control" %>
<%= text_input f, :password_confirmation, class: "form-control" %>
<% end %>
<%= submit "Submit", class: "btn btn-primary" %>
<% end %>
到目前为止一切顺利,表格显示得很好。
问题是,我真的不明白构建我将要插入 create
的变更集的规范方式应该是什么,同时能够
在验证错误时再次将其传递给视图。
不清楚我应该使用一个变更集(以及如何使用?)还是明确
每个实体三个变更集(User
、Org
和连接 table)。
如何验证这种组合形式的变化,因为每个
模型/模式定义了自己的特定验证?
我提交表单时收到的参数都在%{"org" => ...}
之内
地图,包括实际上与 user
相关的地图。我该怎么办
正确创建表单?
我已阅读最近更新的 http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/
但无论如何我仍然感到困惑。
FWIW,我在 Phoenix 1.0.4、Phoenix Ecto 2.0 和 Phoenix HTML 2.3.0.
如有任何提示,我们将不胜感激。
现在,除了在事务中完成所有操作之外,您别无选择。您将在事务内部创建一个组织,它有自己的变更集,如果可行,您将创建每个员工。像这样:
if organization_changeset.valid? and Enum.all?(staff_changesets, & &1.valid?) do
Repo.transaction fn ->
Repo.insert!(organization_changeset)
Enum.each staff_changesets, &Repo.insert!/1)
end
end
请注意,我正在对不理想的变更集进行 valid?
检查,因为它没有考虑约束。但是,如果变更集中存在约束,则需要使用 Repo.insert
(没有爆炸 !
)。
请记住,这在 Ecto 2.0 上会容易得多。在 Ecto master 上,我们已经通过变更集支持 belongs_to,这意味着您可以通过生成中间关联和最终关联来明确地做到这一点:
<%= inputs_for f, :org_staff_users, fn org_staff -> %>
<%= inputs_for org_staff, :user, fn user -> %>
# Your user form here
<% end %>
<% end %>
但是,我们也将支持 many_to_many,这将使它变得非常简单。
按照描述使用嵌入式模式here
我正在 Phoenix 中编写一个简单的 CRUD 应用程序,管理员在创建新组织时可以为其提供初始员工帐户。
组织和用户之间的关系实际上是多对多的。
我得出以下结论:
用户架构:
defmodule MyApp.User do use MyApp.Web, :model schema "users" do field :name, :string field :email, :string field :password, :string, virtual: true field :password_hash, :string end def changeset(...) # validate email, password confirmation etc.
组织架构:
defmodule MyApp.Org do use MyApp.Web, :model schema "orgs" do field :official_name, :string field :common_name, :string has_many :org_staff_users, MyApp.OrgStaffUser has_many :users, through: [:org_staff_users, :user] end def changeset(model, params \ :empty) do model |> cast(params, ~w(official_name common_name), []) end def provisioning_changeset(model, params \ :empty) do model |> changeset(params) |> cast_assoc(:org_staff_users, required: true) end
Junction table
org_staff_users
和相应的 Ecto Schema withuser_id
和org_id
具有以下
new
操作的控制器:def new(conn, _params) do data = %Org{org_staff_users: [%User{}]} changeset = Org.provisioning_changeset(data) render(conn, "new.html", changeset: changeset) end
模板摘录如下:
<%= form_for @changeset, @action, fn f -> %> <%= if @changeset.action do %> <div class="alert alert-danger"> <p>Oops, something went wrong! Please check the errors below:</p> <ul> <%= for {attr, message} <- f.errors do %> <li><%= humanize(attr) %> <%= message %></li> <% end %> </ul> </div> <% end %> <%= text_input f, :official_name, class: "form-control" %> <%= text_input f, :common_name, class: "form-control" %> <%= inputs_for f, :org_staff_users, fn i -> %> <%= text_input f, :email, class: "form-control" %> <%= text_input f, :password, class: "form-control" %> <%= text_input f, :password_confirmation, class: "form-control" %> <% end %> <%= submit "Submit", class: "btn btn-primary" %> <% end %>
到目前为止一切顺利,表格显示得很好。
问题是,我真的不明白构建我将要插入 create
的变更集的规范方式应该是什么,同时能够
在验证错误时再次将其传递给视图。
不清楚我应该使用一个变更集(以及如何使用?)还是明确
每个实体三个变更集(User
、Org
和连接 table)。
如何验证这种组合形式的变化,因为每个 模型/模式定义了自己的特定验证?
我提交表单时收到的参数都在%{"org" => ...}
之内
地图,包括实际上与 user
相关的地图。我该怎么办
正确创建表单?
我已阅读最近更新的 http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/ 但无论如何我仍然感到困惑。
FWIW,我在 Phoenix 1.0.4、Phoenix Ecto 2.0 和 Phoenix HTML 2.3.0.
如有任何提示,我们将不胜感激。
现在,除了在事务中完成所有操作之外,您别无选择。您将在事务内部创建一个组织,它有自己的变更集,如果可行,您将创建每个员工。像这样:
if organization_changeset.valid? and Enum.all?(staff_changesets, & &1.valid?) do
Repo.transaction fn ->
Repo.insert!(organization_changeset)
Enum.each staff_changesets, &Repo.insert!/1)
end
end
请注意,我正在对不理想的变更集进行 valid?
检查,因为它没有考虑约束。但是,如果变更集中存在约束,则需要使用 Repo.insert
(没有爆炸 !
)。
请记住,这在 Ecto 2.0 上会容易得多。在 Ecto master 上,我们已经通过变更集支持 belongs_to,这意味着您可以通过生成中间关联和最终关联来明确地做到这一点:
<%= inputs_for f, :org_staff_users, fn org_staff -> %>
<%= inputs_for org_staff, :user, fn user -> %>
# Your user form here
<% end %>
<% end %>
但是,我们也将支持 many_to_many,这将使它变得非常简单。
按照描述使用嵌入式模式here