スポンサーリンク

2015年6月29日

[Elixir+Phoenix]Ecto.ChangesetのValidate関数を使う

目的

Ecto.ChangesetのValidate関数を利用する。

実行環境

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
PostgreSQL: postgres (PostgreSQL) 9.4.4

始める前に

Ectoを使ってchangeset内でvalidationを行います。
以前のTutorial記事内でも少しだけやる機会がありました。
あまり説明もしていなかったのでまとめます。
先日の記事を元に実施しますので、
プロジェクトの作成とマイグレーションは終わらせておいて下さい。
よければ参考に・・・
[Elixir+Phoenix]EctoModelsの機能を使う
準備良し。
以降、この記事でのプロジェクトと言えば、
“ecto_models_sample”を指し示す。

目次

  1. お前を検証してやる
  2. length(長さ)
  3. format(フォーマット)
  4. unique(一意性)
  5. confirmation(確証)
  6. presence(存在性)

1. お前を検証してやる

検証の仕方は色々とあると思う。
その中でもEcto.Changesetを利用した検証を利用する。
ある程度、使いそうな5つの項目を紹介する。
使い方は非常に簡単です。
これにメソッドチェーンで検証の関数を追加していくだけ。
def changeset(model, params \\ :empty) do
  model
  |> cast(params, @required_fields, @optional_fields)
end
プロジェクトには既に、Userモデル(web/models/user.ex)を作成してますね。
そちらを利用して実施していきます。
お前の:nameと:emailを検証してやる!!

2. length(長さ)

validate_length/3の関数を利用します。
:nameフィールドに対して、最低でも入力されなければならない長さを設定します。
こんな感じになりました。
def changeset(model, params \\ :empty) do
  model
  |> cast(params, @required_fields, @optional_fields)
  |> validate_length(:name, min: 8)
end
今回だけ実行して確認する方法を書いておきます。
以降、実行確認したい場合は、ご自身で実施願います。
>iex -S mix
iex(1)> alias EctoModelsSample.User
iex(2)> params = %{name: "hoge", email: "hoge"}
iex(3)> changeset = User.changeset(%User{}, params)
%Ecto.Changeset{changes: %{email: "hoge", name: "hoge"},
 errors: [name: {"should be at least %{count} characters", 8}], filters: %{},
 model: %EctoModelsSample.User{__meta__: %Ecto.Schema.Metadata{source: "users",
   state: :built}, email: nil, id: nil, inserted_at: nil, name: nil,
  updated_at: nil}, optional: [],
 params: %{"email" => "hoge", "name" => "hoge"}, repo: nil,
 required: [:name, :email], valid?: false,
 validations: [name: {:length, [min: 8]}]}
iex(4)> changeset.valid?()
false
minの値は適当に修正して下さい。
あくまで今回の検証を失敗させるために設定した値です。
(名前が最低でも8文字以上って・・・)

3. format(フォーマット)

validate_format/4の関数を利用します。
:emailフィールドに対して、
アドレスのフォーマットを伴ってなければエラーが出るようにします。
こんな感じになりました。
def changeset(model, params \\ :empty) do
  model
  |> cast(params, @required_fields, @optional_fields)
  |> validate_format(:email, ~r/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
end
さっきの実行確認を流すだけでも失敗してくれます。

4. unique(一意性)

validate_unique/3の関数を利用します。
:nameフィールドに対して一意性を追加します。
こんな感じになりました。
def changeset(model, params \\ :empty) do
  model
  |> cast(params, @required_fields, @optional_fields)
  |> validate_unique(:name, on: EctoModelsSample.Repo)
end
scope:を使うと検証に別のフィールドを追加することもできるみたいです。
例えば、:user_idに紐づく:post_idがあれば、:user_idかつ:post_idと言ったように。
ここではやりませんが、
その内使うことがあると思いますので頭の片隅にでも置いておいて下さい。
(1対多の関係をするときとか使えそうな気がする・・・)

5. confirmation(確証)

validate_confirmation/3の関数を利用します。
パスワードのフィールドがないので、:emailフィールドに対して行います。
def changeset(model, params \\ :empty) do
  model
  |> cast(params, @required_fields, @optional_fields)
  |> validate_confirmation(:email)
end
さて困った(その1)、これで検証動作してくれてるのか?
別にスキーマにemail_confirmationのフィールドがあるわけでもなく・・・
ちょっと不安が残る。
説明では下記のように書かれているのだが・・・
引用:
Validates that the given field matches the confirmation parameter of that field.
By calling validate_confirmation(changeset, :email), this validation will check if both “email” and “email_confirmation” in the parameter map matches.
Note that this does not add a validation error if the confirmation field is nil.
Note “email_confirmation” does not need to be added as a virtual field in your schema.
翻訳:
渡されたフィールドが確証パラメータにマッチすることを確認する。
validate_confirmation(changeset, :email)を呼ぶと、このValidationは”email”と”email_confirmation”がマッチするか調べます。
検証するフィールドが無であるならば、これが確認エラーを加えないことに注意すべきです。
“email_confirmation”がスキーマの仮想フィールドとして加えられる必要はない点に注意して下さい。
Note that this does not add a validation error if the confirmation field is nil.
これ!!
あれ?でもオプションは:messageしかないけど、
どうやってemail_confirmationを指定するの?
スキーマに作ればいいのだろうか?でもこうも言っている。
Note “email_confirmation” does not need to be added as a virtual field in your schema.
申し訳ないですが調査不足ですね。
動作自体はするんだが・・・

6. presence(存在性)

さて困った(その2)・・・presenceに相当するものが分からなかった。
一応、情報っぽいのを見つけたのだが・・・実行できなかった。
Github - Validations in Ecto #94
Github - Validations in Ecto #95
追記(20150629-2125)
友人のエンジニアが調べてくれました。
Ectoのv0.6.0で非推奨になったため上記の機能はなくなったみたいですね。
誤った情報を書いてしまい申し訳ないです。
仕方ないから自分で作るか・・・
幸いなことにこちらは参考になりそうなQA記事が見つかったからね。
しかし、やっぱりと言うべきか・・・見つかったのはStackOverflow。
Ecto - validate presence of associated model
こんな感じに使いたい。
キャストした値の取り方が分からなかったので、直接paramsから取得している。
(あんまり良いやり方じゃないな・・・)
def changeset(model, params \\ :empty) do
  model
  |> cast(params, @required_fields, @optional_fields)
  |> validate_name_presence()
end
先ほどの記事を元にして自作で検証の関数を作成する。
汎用性とか考えないでシンプルに作成する。
def validate_name_presence(changeset) do
  name = Ecto.Changeset.get_field(changeset, :name)
  cond do
    name == nil ->
      add_error changeset, :name, "Name is nil"
    name == "" ->
      add_error changeset, :name, "No Name"
    true ->
      changeset
  end
end
文字列だからnilと空文字のチェックだけしている。
(ハッシュとかきたらどうするんでしょうね?)
後は、実際に使ってみる。
自作してしまったので一応、実行確認を載せておきます。
>iex -S mix
iex(1)> alias EctoModelsSample.User
nil
iex(2)> params = %{name: "hoge", email: "hoge@hoge.com"}
%{email: "hoge@hoge.com", name: "hoge"}
iex(3)> changeset = User.changeset(%User{}, params)
hoge
%Ecto.Changeset{changes: %{email: "hoge@hoge.com", name: "hoge"}, errors: [],
 filters: %{},
 model: %EctoModelsSample.User{__meta__: %Ecto.Schema.Metadata{source: "users",
   state: :built}, email: nil, id: nil, inserted_at: nil, name: nil,
  updated_at: nil}, optional: [],
 params: %{"email" => "hoge@hoge.com", "name" => "hoge"}, repo: nil,
 required: [:name, :email], valid?: true, validations: []}

iex(4)> params = %{name: "", email: "hoge@hoge.com"}
%{email: "hoge@hoge.com", name: ""}
iex(5)> changeset = User.changeset(%User{}, params)
%Ecto.Changeset{changes: %{email: "hoge@hoge.com", name: ""},
 errors: [name: "No Name"], filters: %{},
 model: %EctoModelsSample.User{__meta__: %Ecto.Schema.Metadata{source: "users",
   state: :built}, email: nil, id: nil, inserted_at: nil, name: nil,
  updated_at: nil}, optional: [],
 params: %{"email" => "hoge@hoge.com", "name" => ""}, repo: nil,
 required: [:name, :email], valid?: false, validations: []}

iex(6)> params = %{name: nil, email: "hoge@hoge.com"}
%{email: "hoge@hoge.com", name: nil}
iex(7)> changeset = User.changeset(%User{}, params)
%Ecto.Changeset{changes: %{email: "hoge@hoge.com"},
 errors: [name: "Name is nil", name: "can't be blank"], filters: %{},
 model: %EctoModelsSample.User{__meta__: %Ecto.Schema.Metadata{source: "users",
   state: :built}, email: nil, id: nil, inserted_at: nil, name: nil,
  updated_at: nil}, optional: [],
 params: %{"email" => "hoge@hoge.com", "name" => nil}, repo: nil,
 required: [:name, :email], valid?: false, validations: []}
iex(8)>
うん、動作も大丈夫そうだ。

管理人の独り言~

ちょっと、”validate_confirmation”が怪しいけど、概ね問題はなさそうだ。
そういえば、前にSherrifでValidationうんたら言ってた気がするけど、
あれの機能って本当にValidationなのか・・・う~む、また恥を晒したのだろうか・・・

参考文献

人気の投稿