まとめ
- 複合primaryキーでも単数でもindex名としては:PRIMARYなので書き方は同じ
- primaryキーにunique_constraintを使ったときのエラー表示が分かりにくいので、ラッパーを書いた
調査
調査用スキーマ
@primary_key false
schema "profiles" do
field :user_id, :integer, primary_key: true
field :email, :string, primary_key: true
field :token, :string, primary_key: true
end
Primaryキーのconstraintの書き方
単数でも複合でもindex名は:PRIMAYなので書き方は同じです。
changeset |> Changeset.unique_constraint(:user_id, [name: :PRIMARY])
調査結果
エラーメッセージがわかりにくい
changeset.errors #=> [user_id: {"has already been taken", []}]
primaryキーはuser_id, email, tokenなのにuser_idについてしか言及されていない。
エラーメッセージを変更する
Changeset.unique_constraintの第2引数に渡すfield名は、デフォルトindex名の生成とerrorsのキー名にしか使われていないように見えるため、nameオプションでindex名を指定している場合は好きな名前を指定できそうです。
changeset |> Changeset.unique_constraint(:primary_keys, [name: :PRIMARY])
changeset.errors #=> [primary_keys: {"has already been taken", []}]
しかし慣例として、field名を入れたほうが良さそうに見えます。
documentには以下のように記載されており、実装も変数名はfieldとなっています。
Notice that the first param is just one of the unique index fields, this will be used as the error key to the changeset errors keyword list.
このため、ライブラリ側が想定していない使い方は控え、messageオプションに全ての対象field名を記載することにしました。
primary_constraintの実装
目標
- 調査用スキーマを使って、以下のエラーメッセージを得る
- errorsのキー名は複合キーの先頭field
# changeset.errors => [user_id: {"Duplicate primary key [:user_id, :email, :token]", []}]
# 使い方
def changeset(struct, params \\ %{}) do
struct
|> Changeset.cast(params, @required_fields)
|> primary_constraint()
end
実装
defmacro primary_constraint(changeset) do
quote do
alias Ecto.Changeset
pkeys = __MODULE__.__schema__(:primary_key)
first_field = hd pkeys
Changeset.unique_constraint(unquote(changeset), first_field, [name: :PRIMARY, message: "Duplicate primary key #{inspect(pkeys)}"])
end
end
以上です。プロダクトに投入して使っています。