Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
11
Help us understand the problem. What is going on with this article?
@niku

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

More than 3 years have passed since last update.

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 を使うと便利である.

11
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
niku

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
11
Help us understand the problem. What is going on with this article?