まとめ
- validateはクエリ実行前のチェック、constraintは実行後のチェック
- validate,constraintに書かれている知識ならectoはエラーを返すが、それ以外は例外になる
- unique_constraintはindexの名前が規則通りじゃないとエラーにしてくれない
- 規則から外れる場合はnameオプションで指定
- primaryキーのconstraintnの書き方はこちら
実験
普通にinsertする
CREATE TABLE `people` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`first_name` varchar(255) NOT NULL,
`last_name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `people_first_name_index` (`first_name`),
UNIQUE KEY `last_name_index` (`last_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
defmodule Friends.Person do
use Ecto.Schema
schema "people" do
field :first_name, :string
field :last_name, :string
field :age, :integer
end
end
iex(1)> {:ok, row} = Friends.Repo.insert(%Friends.Person{})
01:32:25.191 [debug] QUERY OK db=2.1ms queue=5.7ms
INSERT INTO `people` () VALUES () []
{:ok,
%Friends.Person{__meta__: #Ecto.Schema.Metadata<:loaded, "people">, age: nil,
first_name: nil, id: 37, last_name: nil}}
validation
castはpersonとparamsの指定のキーワードを比較して差分だけchangesetにする。
validate_requiredはnilと空文字の禁止。デフォでtrimもされるので注意。
def changeset(person, params \\ %{}) do
person
|> Ecto.Changeset.cast(params, [:first_name, :last_name, :age])
|> Ecto.Changeset.validate_required([:first_name, :last_name])
end
first_name,last_nameが空のchangesetを作成
iex(1)> iex(1)> changeset = Friends.Person.changeset(%Friends.Person{})
#Ecto.Changeset<action: nil, changes: %{},
errors: [first_name: {"can't be blank", [validation: :required]},
last_name: {"can't be blank", [validation: :required]}],
data: #Friends.Person<>, valid?: false>
- changeset.valid?はfalse
- そのままinsertするとクエリ実行前にエラー
- validate_requiredが存在しないとクエリが実行される
- schemaがNOT NULLなので例外発生
iex(2)> {:error, _changest} = Friends.Repo.insert(changeset)
{:error,
#Ecto.Changeset<action: :insert, changes: %{},
errors: [first_name: {"can't be blank", [validation: :required]},
last_name: {"can't be blank", [validation: :required]}],
data: #Friends.Person<>, valid?: false>}
constraint
- クエリ実行時にDB側でエラーになると、Elixirでは例外が発生するが、事前にconstraintで定義していた事項は例外ではなくエラーにすることができる。
- unique_constraintではunique indexの名前をectoが知っている必要があるので、nameオプションで指定
- デフォルトnameは
#{table名}_#{field名}_index
- デフォルトnameは
def changeset(person, params \\ %{}) do
person
|> Ecto.Changeset.cast(params, [:first_name, :last_name, :age])
|> Ecto.Changeset.validate_required([:first_name, :last_name])
|> Ecto.Changeset.unique_constraint(:first_name)
|> Ecto.Changeset.unique_constraint(:last_name, name: "last_name_index")
end
ecto経由のmigrationでDBに追加したindexならデフォルトで#{table名}_#{field名}_index
という名前のindex名になっている
defmodule Friends.Repo.Migrations.AddIndexPeople do
use Ecto.Migration
def change do
create unique_index(:people, [:first_name])
end
end