Каков правильный способ обработки вложенных форм /ecto изменений в 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
-
Таблица соединений org_staff_users
и соответствующая схема Ecto с
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
и таблица соединений).
Как я могу проверить изменения для такой комбинированной формы, учитывая, что каждый
модель/схема имеет свои собственные определенные проверки?
Параметры, которые я получаю после отправки формы, находятся в пределах %{"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.
Приветствуются любые советы.
Ответы
Ответ 1
Прямо сейчас у вас нет другого выбора, кроме всего, что делалось в транзакции. Вы собираетесь создать организацию внутри транзакции, которая имеет свой собственный набор изменений, и если он работает, вы создаете каждого сотрудника. Что-то вроде этого:
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
(без bang !
).
Имейте в виду, что на Ecto 2.0 это будет намного проще. В Ecto-хозяине мы уже поддерживаем assign_to через changeets, что означает, что вы сможете сделать это явно, создав как промежуточную, так и конечную ассоциации:
<%= 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, который сделает его полностью прямым.
Ответ 2
Используйте встроенные схемы, как описано здесь