LoginSignup
16
10

More than 5 years have passed since last update.

Elixirで値のバリデーションをEctoで行う

Last updated at Posted at 2017-02-02

Elixirで値の定義やバリデーションをするためにライブラリを探した.
awesome-elixir の validations を探すといくつか見つかる.
hex.pm で validation 検索してもいくつか見つかる.
これらの中では vex というのが汎用的かつ使いやすそうだった.

ここには含まれていないが,それを行えるライブラリがある.ectoだ.

ectoは永続化層(主にDB)への入出力モジュールとして Ecto.RepoEcto.Query を持っており DB アクセスするためのミドルウェアとして捉えられがちではあるが,それ以外にも,スキーマ定義のための Ecto.Schema とバリデーション(など)のための Ecto.Changeset があるので,単なるバリデーションのためのライブラリとして利用することもできる.

もしectoが使えるなら

  • 広く使われているのでバグが少ないことが期待できる
  • (使ったことがあれば)新しいAPIを覚えずにすむ

そこで DB を使わない Ecto の利用について調査する.

コードは https://gist.github.com/niku/42edf138d299b7239d872705079b3dd2 に置いたので clone してすぐに試せる.

準備

依存関係に {:ecto, "~> 2.1"} を追加するだけで OK だ.
DB を利用するときには,config に adapter を定義していたが,今回は不要だ.

mix.exs
defmodule EctoSandbox.Mixfile do
  use Mix.Project

  def project do
    [app: :ecto_sandbox,
     version: "0.1.0",
     elixir: "~> 1.4",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     elixirc_paths: ["."],
     deps: deps()]
  end

  def application do
    [extra_applications: [:logger]]
  end

  defp deps do
    [{:ecto, "~> 2.1"}]
  end
end

定義

Ecto.Schema を使う.

use Ecto.Schema するとよい.

user.ex`
defmodule User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :age, :integer, default: 0
  end
end
% git clone https://gist.github.com/niku/42edf138d299b7239d872705079b3dd2
% cd 42edf138d299b7239d872705079b3dd2/
42edf138d299b7239d872705079b3dd2% mix deps.get && iex -S mix
iex(1)> %User{}
%User{__meta__: #Ecto.Schema.Metadata<:built, "users">, age: 0, id: nil,
 name: nil}
iex(2)> user = %User{name: "niku", age: 29}
%User{__meta__: #Ecto.Schema.Metadata<:built, "users">, age: 29, id: nil,
 name: "niku"}
iex(3)> user.name
"niku"

バリデーション

Ecto.Changeset

The first one (訳註: cast/3) is used to cast and validate external parameters, such as parameters sent through a form, API, command line, etc.
The second one (訳註: change/2) is used to change data directly from your application.

と書いてあるので Ecto.Changeset.change/2 を使うとよい.

Changeset を取得するにはモジュールへ changeset/2 という function を準備するのが慣例になっているようだ.

ここでは city temp_lo temp_hi それぞれが必須,prcp が 0.0 以上というバリデーションをかけた.

weather.ex
defmodule Weather do
  use Ecto.Schema

  schema "weather" do
    field :city
    field :temp_lo, :integer
    field :temp_hi, :integer
    field :prcp,    :float, default: 0.0 # 降水量
  end

  def changeset(weather, params \\ %{}) do
    weather
    |> Ecto.Changeset.change(params)
    |> Ecto.Changeset.validate_required([:city, :temp_lo, :temp_hi])
    |> Ecto.Changeset.validate_number(:prcp, greater_than_or_equal_to: 0.0)
  end
end
42edf138d299b7239d872705079b3dd2% iex -S mix
iex(1)> # validなデータを与える
iex(2)> weather = Weather.changeset(%Weather{}, %{city: "sapporo", temp_hi: -5, temp_lo: -10, prcp: 2.6})
#Ecto.Changeset<action: nil,
 changes: %{city: "sapporo", prcp: 2.6, temp_hi: -5, temp_lo: -10}, errors: [],
 data: #Weather<>, valid?: true>
iex(3)> weather.valid?
true
iex(4)> # invalidなデータを与える
iex(5)> weather = Weather.changeset(%Weather{}, %{temp_lo: -5, prcp: -1.0})
#Ecto.Changeset<action: nil, changes: %{prcp: -1.0, temp_lo: -5},
 errors: [prcp: {"must be greater than or equal to %{number}",
   [validation: :number, number: 0.0]},
  city: {"can't be blank", [validation: :required]},
  temp_hi: {"can't be blank", [validation: :required]}], data: #Weather<>,
 valid?: false>
iex(6)> weather.valid?
false

Weacher.changeset の結果に valid? という値があり,バリデーションの結果により truefalse が切り替わていることがわかるだろう.

エラーは traverse_errors/2 を使うと表示が容易になる.

iex(7)> Ecto.Changeset.traverse_errors(weather, fn {msg, opts} ->
...(7)>   Enum.reduce(opts, msg, fn {key, value}, acc ->
...(7)>     String.replace(acc, "%{#{key}}", to_string(value))
...(7)>   end)
...(7)> end)
%{city: ["can't be blank"], prcp: ["must be greater than or equal to 0.0"],
  temp_hi: ["can't be blank"]}

まとめ

  1. 既に広く使われていてテストが充分になされている
  2. RDB へのアクセスで使ったことがあるなら,新しく覚えることが少ない

という 2 点から,Elixir で扱うデータのバリデーションには Ecto を使うと便利である.

16
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
10