アップサートを行うことは私のアプリでは一般的であり、アップサートを実装するための最もクリーンでシンプルな方法を実装したいと思います。
Ecto.Repo.insert_or_update/2 を使用できます。これを機能させるには、データベースから既存のモデルをロードする必要があることに注意してください。
model = %Post{id: 'existing_id', ...}
MyRepo.insert_or_update changeset
# => {:error, "id already exists"}
例:
result =
case MyRepo.get(Post, id) do
nil -> %Post{id: id} # Post not found, we build one
post -> post # Post exists, let's use it
end
|> Post.changeset(changes)
|> MyRepo.insert_or_update
case result do
{:ok, model} -> # Inserted or updated with success
{:error, changeset} -> # Something went wrong
end
id
以外の方法でアップサートを探している場合は、次のようにget_by
をget
に交換できます。
model = %User{email: "[email protected]", name: "Cat", ...}
model |> User.upsert_by(:email)
# => {:found, %User{...}} || {:ok, %User{...}}
defmodule App.User do
alias App.{Repo, User}
def upsert_by(%User{} = record_struct, selector) do
case User |> Repo.get_by({selector, record_struct |> Map.get(selector)}) do
nil -> %User{} # build new user struct
user -> user # pass through existing user struct
end
|> User.changeset(record_struct |> Map.from_struct)
|> Repo.insert_or_update
end
end
偶然にも、モデル間で機能する柔軟なアプローチを探していて、複数のセレクター(つまり、国+パスポート番号)を探している場合は、私の16進パッケージをチェックしてください EctoConditionals !
私の場合、insert_or_update
は一意のインデックス制約のためにエラーを発生させました????
私のために働いたのはPostgres v9.5upsert through on_conflict
パラメーター:
(一意の列を考慮するとuser_id
と呼ばれます)
changeset
|> MyRepo.insert(
on_conflict: :replace_all,
conflict_target: :user_id
)