ЭКТО-актуализация вложенных (полиморфных) ассоциаций
Как обновить модель с вложенной ассоциацией (используя [Elixir, Phoenix, Ecto])?
Я попытался сделать следующее, чтобы рассматривать его как часть родительского обновления, но безуспешно (используя блог platformatec в качестве вдохновения).
Модели:
schema "user" do
has_one :address, {"users_addresses", MyApp.Address}, foreign_key: :assoc_id
end
@required_fields ~w(address)
------
# Materialized in users_addresses table
schema "abstract table: addresses" do
field :assoc_id, :integer
field :street_address, :string
end
Запрос (патч):
{
"user" => {
"address" => {
"street_address" => "1234"
}
}
}
Контроллер:
def update(conn, %{"id" => id, "user" => params}) do
user = MyApp.Repo.get(User, id)
|> MyApp.Repo.preload [:address]
if is_nil(user) do
send_resp(conn, 404, "")
else
changeset = User.changeset(user, params)
if changeset.valid? do
case MyApp.Repo.update(changeset) do
{:ok, model} -> send_resp(conn, 204, "")
{:error, changeset} -> conn
|> put_status(:unprocessable_entity)
|> render(MyApp.ChangesetView, "error.json", changeset: changeset)
end
else
conn
|> put_status(:unprocessable_entity)
|> render(MyApp.ChangesetView, "error.json", changeset: changeset)
end
end
end
Набор изменений (из журналов):
%Ecto.Changeset{action: nil,
changes: %{address: %Ecto.Changeset{action: :insert, changes: %{street_address: "1234"},
constraints: [],
....
model: %MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded>,
address: %MyApp.Address{__meta__: #Ecto.Schema.Metadata<:loaded>,
assoc_id: 1229, id: 308,
street_address: "41423 Jakubowski Village"
....
}
}
}
ошибка: исправлено с Ecto v1. 0. 3 или более поздней версии
** (exit) an exception was raised:
** (Postgrex.Error) ERROR (undefined_table): relation "abstract table: addresses" does not exist
(ecto) lib/ecto/adapters/sql.ex:479: Ecto.Adapters.SQL.model/6
(ecto) lib/ecto/repo/model.ex:219: Ecto.Repo.Model.apply/4
(ecto) lib/ecto/repo/model.ex:71: anonymous fn/10 in Ecto.Repo.Model.insert/4
(ecto) lib/ecto/repo/model.ex:340: anonymous fn/3 in Ecto.Repo.Model.wrap_in_transaction/8
(ecto) lib/ecto/adapters/sql.ex:531: anonymous fn/10 in Ecto.Adapters.SQL.transaction/3
(ecto) lib/ecto/pool.ex:262: Ecto.Pool.inner_transaction/3
(ecto) lib/ecto/adapters/sql.ex:534: Ecto.Adapters.SQL.transaction/3
(ecto) lib/ecto/association.ex:368: Ecto.Association.Has.on_repo_action/7
1 ответ:
(Postgrex.Error) ERROR (undefined_table): relation "abstract table: addresses" does not exist
это произошло из-за ошибки, которая должна быть исправлена вEcto v1.0.3
или позже.
В приведенном выше коде отсутствует идентификатор адреса, без которого Ecto вставит новый ресурс вместо обновления существующего.Запрос (патч):
{ "user" => { "address" => { "id" => 4, "street_address" => "1234" } } }
Новый код контроллера, включая некоторые из предложенных улучшений JoseValims:
def update(conn, %{"id" => id, "user" => params}) do user = MyApp.Repo.get!(User, id) |> MyApp.Repo.preload [:address changeset = User.changeset(user, params) case MyApp.Repo.update(changeset) do {:ok, model} -> send_resp(conn, 204, "") {:error, changeset} -> conn |> put_status(:unprocessable_entity) |> render(MyApp.ChangesetView, "error.json", changeset: changeset) end end
Или, в этой ситуации, поскольку адрес является одновременно обязательным и
has_one
, идентификатор может быть добавлен на стороне сервера:def update(conn, %{"id" => id, "user" => params}) do user = MyApp.Repo.get!(User, id) |> MyApp.Repo.preload [:address] address = params["address"] address = Map.put address, "id", user.address.id params = Map.put params, "address", address changeset = User.changeset(user, params) case MyApp.Repo.update(changeset) do {:ok, model} -> send_resp(conn, 204, "") {:error, changeset} -> conn |> put_status(:unprocessable_entity) |> render(MyApp.ChangesetView, "error.json", changeset: changeset) end end