5

如何使用嵌套关联更新模型(使用 [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
4

1 回答 1

1

(Postgrex.Error) ERROR (undefined_table): relation "abstract table: addresses" does not exist是由于一个错误,应该在Ecto v1.0.3或以后修复。


上面的代码缺少地址的 id,没有这个,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的,因此可以在服务器端添加 id:

 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
于 2015-09-14T21:09:27.563 回答