На странице регистрации моего приложения я пытаюсь использовать вложенные формы (используя form_for / 3 и input_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 %>
Проблема: Если все идет хорошо, организация и пользователь успешно создаются и подтверждают .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
Я считаю это потому, что Ecto.Changeset, который передается в new.html, является автономным пользователем%{} changeset:
#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
>
Как лучше всего решить эту проблему?