elixir - 将嵌套表单与 Ecto.Multis 和变更集结合使用
问题描述
在我的应用程序的注册页面上,我正在尝试将嵌套表单(使用form_for/3和inputs_for/4)与 Ecto.Multis 和变更集结合使用。
目标:如果一个或多个服务器端验证失败,则使用先前输入的信息重新呈现表单,以便用户可以进行更正,而不是从头开始。
该应用程序有一个组织架构,其中有一个用户是创建者/所有者:
schema "organizations" do
field :owner_id, :integer
has_one :owner, MyApp.Accounts.User, references: :owner_id, foreign_key: :id
end
组织上下文具有以下create_organization
功能:
def create_organization(org_attrs, user_attrs) do
Ecto.Multi.new()
|> Ecto.Multi.insert(:organization, organization_changeset(%Organization{}, org_attrs))
|> Ecto.Multi.run(:user, fn _repo, %{organization: organization} ->
%User{organization_id: organization.id}
|> Accounts.register_user_changeset(user_attrs)
|> Repo.insert()
end)
|> Ecto.Multi.run(:update_organization, fn(_repo, %{user: user, organization: organization}) ->
organization
|> organization_creator_changeset(%{owner_id: user.id})
|> Repo.update()
end)
|> Repo.transaction()
end
(我为此使用 Ecto.Multi 的原因是,例如,如果用户插入失败,我希望回滚整个操作。)
我的控制器动作:
def new(conn, _params) do
changeset = Organizations.create_organization_changeset(%Organization{owner: %User{}})
conn
|> render("new.html", changeset: changeset)
end
def create(conn, %{"organization" => %{"owner" => user_params}} = org_params) do
case Organizations.create_organization(org_params, user_params) do
{:ok, %{user: user}} ->
conn
|> put_status(:created)
|> put_view(MyAppWeb.Accounts.AccountView)
|> render("confirm.html", email: user.email)
{:error, _resource, changeset, _changes} ->
conn
|> put_status(:unprocessable_entity)
|> put_flash(:error, MyAppWeb.ErrorHelpers.transform_errors(changeset))
|> render("new.html", changeset: changeset)
end
end
我的表格:
<%= form_for @changeset, account_path(@conn, :create), fn f -> %>
<div class="row">
<%= text_input :organization, :name, class: "form-control", required: true %>
<%= label f, :organization_name %>
<%= error_tag f, :name %>
</div>
<%= inputs_for f, :owner, fn u -> %>
<div class="row">
<%= email_input u, :email, class: "form-control", required: true %>
<%= label u, :email %>
<%= error_tag u, :email %>
</div>
<div class="row">
<%= password_input u, :password, class: "form-control", pattern: ".{8,}", title: "Password must have 8 or more characters.", required: true %>
<%= label u, :password %>
<%= error_tag u, :password %>
</div>
<div class="row">
<%= password_input u, :password_confirmation, class: "form-control", required: true %>
<%= label u, :password_confirmation %>
<%= error_tag u, :password_confirmation %>
</div>
<% end %>
<% end %>
问题:如果一切顺利,则组织和用户创建成功并呈现confirm.html。但是,如果有变更集错误,我会得到:
could not generate inputs for :owner from MyApp.Accounts.User. Check the field exists and it is one of embeds_one, embeds_many, has_one, has_many, belongs_to or many_to_many
我相信这是因为传递给 new.html 的 Ecto.Changeset 是一个独立的 %User{} 变更集:
#Ecto.Changeset<
action: :insert,
changes: %{
email: "test@test.com",
organization: #Ecto.Changeset<action: :update, changes: %{}, errors: [],
data: #MyApp.Organizations.Organization<>, valid?: true>,
password: "test",
password_confirmation: "test",
password_hash: "bla"
},
errors: [
email: {"has already been taken",
[constraint: :unique, constraint_name: "accounts_users_email_index"]}
],
data: #MyApp.Accounts.User<>,
valid?: false
>
解决此问题的最佳方法是什么?
解决方案
与其引入自己的重新实现来将多个嵌套/相关记录插入到不同的表中,不如使用Ecto
开箱即用的方法:Ecto.Changeset.put_assoc/4
inUser
的变更集。
它将为您执行所有验证,并返回无效changeset
是否出现问题。
推荐阅读
- django - 正确的 DRF 过滤器列表视图端点以按 id 返回多个对象
- jquery - 在页面中初始化许多光滑的轮播
- asp.net - 更新面板影响菜单元素
- c# - 使用 While 循环返回程序的起点
- java - FragmentStatePagerAdapter 无法处理大量片段
- .htaccess - 匹配目录名和变量的 Htaccess 重定向 [F]
- java - java 8持续时间“ofSeconds”与“withSeconds”
- polymorphism - 如何要求抽象类的子类作为方法中的参数
- angular - p-dropdown 不显示表单中的值
- facebook - 有什么方法可以在“实时模式”下提交 FB 应用程序以供审核?