スポンサーリンク

2015年7月1日

[Phoenix]Ectoのvirtualオプションを検証する

目的

Ectoのフィールドに付加するvirtualオプションを把握する。

実行環境

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

始める前に

先日の記事を元に実施しますので、
プロジェクトの作成とマイグレーションは終わらせておいて下さい。
よければ参考に・・・
[Elixir+Phoenix]EctoModelsの機能を使う
準備良し。
以降、この記事でのプロジェクトと言えば、
“ecto_models_sample”を指し示す。

目次

  1. virtualオプションって何?
  2. 検証してみよう!

1. virtualオプションって何?

スキーマに定義するフィールドのオプションです。
Ectoのドキュメントには以下のように書かれています。
引用:
:virtual - When true, the field is not persisted to the database.
Notice virtual fields do not support :autogenerate nor :read_after_writes.
※nor = or(?)
翻訳:
:virtual - フィールドが正しいとき、データベースまで持続しません。
仮想フィールドがサポートしないのは、:autogenerateまたは:read_after_writesです
つまり、フィールド(値)でエラーが出なければデータベースまで保存しませんよ。
っと言うことですね。
これ何に使うのかと疑問に思っている方もいると思います。
私が想定している内容ですが、あるデータモデルに
password_digest、password、password_confirmationというフィールドがあったとします。
password、password_confirmationは入力と検証は必須にしたいですが、
DBへ格納するデータは暗号化したパスワードが入るpassword_digestだけあれば良いのです。
そこでVirtualオプションを使うわけですね。

2. 検証してみよう!

では、実際にpassword、password_digestフィールドを追加して試してみましょう。
マイグレーションは各人でお願いします。
対象テーブル: users
追加カラム1: password :string
追加カラム2: password_digest :string
良ければ参考にどうぞ。
再マイグレーション: [Phoenix]Ectoを使って再マイグレーションする
マイグレーションできたら、次はUserモデルを修正します。
web/models/user.exを開き、以下のように編集して下さい。
defmodule EctoModelsSample.User do
  use EctoModelsSample.Web, :model
  use Ecto.Model.Callbacks

  before_insert :set_password_digest

  schema "users" do
    field :name, :string
    field :email, :string
    field :password_digest, :string
    field :password, :string, virtual: true

    timestamps
  end

  @required_fields ~w(name email password)
  @optional_fields ~w()

  @doc """
  Creates a changeset based on the `model` and `params`.

  If `params` are nil, an invalid changeset is returned
  with no validation performed.
  """
  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)
    |> validate_unique(:name, on: EctoModelsSample.Repo)
    |> validate_unique(:email, on: EctoModelsSample.Repo)
    |> validate_length(:name, min: 1)
    |> validate_length(:name, max: 50)
    |> validate_length(:password, min: 8)
    |> validate_length(:password, max: 100)
  end

  def set_password_digest(changeset) do
    password = Ecto.Changeset.get_field(changeset, :password)
    change(changeset, %{password_digest: password})
  end
end
※注意1
本当はset_password_digest/1の関数内で
暗号化しないといけないのですが、
今回は割愛して生のまま入れています。
(ライブラリを入れるのが面倒くさかったとかそんな理由ではありません!)
※注意2
上記のソースコードだと、
password_digestを表示させようとすると実行時エラーで落ちます。
多分、@require_fieldに入ってないからだと思います。(未検証)
画面で確認したい方はテンプレートも修正して下さい。
:passwordを入力&表示できるようにします。
対象テンプレートは以下。
web/templates/user/index.html.eex
web/templates/user/show.html.eex
web/templates/user/form.html.eex
※indexかshowのどちらかだけでも確認はできます。
index.html.eex
<h2>Listing users</h2>

<table class="table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Password</th>

      <th></th>
    </tr>
  </thead>
  <tbody>
<%= for user <- @users do %>
    <tr>
      <td><%= user.name %></td>
      <td><%= user.email %></td>
      <td><%= user.password %></td>

      <td class="text-right">
        <%= link "Show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs" %>
        <%= link "Edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs" %>
        <%= link "Delete", to: user_path(@conn, :delete, user), method: :delete, class: "btn btn-danger btn-xs" %>
      </td>
    </tr>
<% end %>
  </tbody>
</table>

<%= link "New user", to: user_path(@conn, :new) %>
show.html.eex
<h2>Show user</h2>

<ul>

  <li>
    <strong>Name:</strong>
    <%= @user.name %>
  </li>

  <li>
    <strong>Email:</strong>
    <%= @user.email %>
  </li>

  <li>
    <strong>Password:</strong>
    <%= @user.password %>
  </li>

</ul>

<%= link "Back", to: user_path(@conn, :index) %>
form.html.eex
<%= form_for @changeset, @action, fn f -> %>
  <%= if f.errors != [] do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below:</p>
      <ul>
        <%= for {attr, message} <- f.errors do %>
          <li><%= humanize(attr) %> <%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="form-group">
    <label>Name</label>
    <%= text_input f, :name, class: "form-control" %>
  </div>

  <div class="form-group">
    <label>Email</label>
    <%= text_input f, :email, class: "form-control" %>
  </div>

  <div class="form-group">
    <label>Password</label>
    <%= text_input f, :password, class: "form-control" %>
  </div>

  <div class="form-group">
    <%= submit "Submit", class: "btn btn-primary" %>
  </div>
<% end %>
後は、普通にユーザを作成してみれば・・・
あら不思議!password_digestには値が入っていますが、passwordの値は空(?)です。

管理人の独り言~

今回はピンポイントで非常に参考になる情報が見つかった。
参考になった方のソースコードをGithubからクローンしたが、
俺から見て宝の山だった。本当に感謝!!
いや~ドキュメント見つからなかったら、
ここら辺飛ばしてたから良かった良かった。
今回のソースコードをGistで表示しようと思ったのですけど・・・
デザインが崩れる・・・何でだよう!もう!!

参考文献

人気の投稿