スポンサーリンク

2015年7月2日

[Rails Tutorial for Phoenix]Modeling users

Goal

ユーザのモデルを実装する。

Wait a minute

ユーザのモデルを実装していきます。
今回から、必要がない限りはサーバ起動を促すことはしません。
適宜実行してみて下さい。
実行できなくても、この段階では実行できない、何かが間違っているなど、
得る情報があると思います。
そういった勘を養うためにも、ご自分で実行のタイミングを取って下さい。
ちょっとしたアドバイスです。
おそらく、大体の方が分かっていると思いますが、
なるべく小さく実行していった方が良いですね。
作成 → 実行 → 修正 → 実行…といったように、
少しずつ小さく実行を繰り返していくことが、大量のエラーに悩まされない方法です。

Index

Modeling users
|> Preparation
|> User data model
|> User
|> Verify
|> Add password colum
|> Encrypted password
|> Additional verification
|> Before the end

Preparation

ブランチを切ります。

Example:

>cd path/to/sample_app
>git checkout -b modeling_users
ライブラリの準備をします。
利用するライブラリは以下のものになります。
Github: safetybox

File: mix.exs

defmodule SampleApp.Mixfile do
  ...
  # Specifies your project dependencies
  #
  # Type `mix help deps` for examples and options
  defp deps do
    [...
     {:safetybox, "~> 0.1"}]
  end
end
依存関係の解決を行います。

Example:

>mix deps.get

User data model

ユーザのデータモデルの提示と実装を行います。
ユーザの最初のデータモデルは以下のようになります。
  • ユーザのデータモデル
    • モデル名: User
    • テーブル名: users
    • 生成カラム(カラム名:型): name:string, email:string
    • 自動生成カラム(カラム名:型): id:integer, inserted_at:timestamp, updated_at:timestamp
    • インデックス(対象カラム名): name, email
データモデルを把握したところで、モデルの作成に移りたいと思います。
今回、使うコマンドは、”mix phoenix.gen.html”ではありません。
“mix phoenix.gen.model”と言うコマンドを使います。
生成したファイルの一覧を見ればどういった、コマンドなのか分かると思います。

Example:

>mix phoenix.gen.model User users name:string email:string
Generated sample_app app
* creating priv/repo/migrations/[timestamp]_create_user.exs
* creating web/models/user.ex
* creating test/models/user_test.exs
以下の三つのファイルが生成されていますね。
  • [timestamp]_create_user.exs
  • user.ex
  • user_test.exs
“mix phoenix.gen.model”コマンドは、
(上から)マイグレーションファイル、モデルファイル、モデルのテストファイルを生成します。
このコマンドは、コントローラやビューなどが不要の場合に利用すると良いですね。
マイグレーションファイルの確認と編集を行います。
マイグレーションファイルを開き、インデックスの作成を追加します。

File: priv/repo/[timestamp]_create_user.exs

defmodule SampleApp.Repo.Migrations.CreateUser do
  use Ecto.Migration
  @disable_ddl_transaction true

  def change do
    create table(:users) do
      add :name, :string
      add :email, :string

      timestamps
    end

    create index(:users, [:name], unique: true, concurrently: true)
    create index(:users, [:email], unique: true, concurrently: true)
  end
end
マイグレーションファイルの内容はEctoの機能が使われています。
Ectoは、DBとの仲介をしてくれる便利なライブラリです。
Webサイトを作成する上でDBは切っても切り離せません。
しかし、DBとのやり取りが難しいと開発の効率が落ちてしまいますね。
WebサイトとDBの間を仲介してくれる便利な機能があれば、
難しい部分は考えなくてもよくなります。
そのために、Ectoがあります。
テーブルとカラムを作成する記述方法を見てみましょう。

Example:

create table(テーブル名 do
  add カラム名, 型
  add カラム名, 型
  ...

  timestamps
end
大体は、見たままですね。
“create table”でテーブルを作成、
テーブル内の”add”でカラムを追加しています。
timestampsは、inserted_atとupdated_atになります。
自動で付与されるidは、特に記述はありません。
次は、インデックスについて見てみましょう。

Example:

create index(:users, [:name], unique: true, concurrently: true)
create index(:users, [:email], unique: true, concurrently: true)
インデックスの引数は、左から順番に…テーブル名、カラム名(複数可)、オプションと言った順番になっています。
(カラムの複数指定は別のテーブルを作成する時に行っていきます。)
uniqueオプションは、値に対して一意性を強制するものです。
concurrentlyオプションは、
対象テーブルに対する同時挿入、更新、削除を防止するようなロックを獲得せずにインデックスを作成するオプションです。

Example:

@disable_ddl_transaction true
@disable_ddl_transaction属性は、
トランザクションの外部で実行するように強制できる属性です。
この属性がないと以下のようなエラーが発生します。
PostgreSQLのログに出力されているメッセージです。

Example:

2015-07-29 14:06:19 JST ERROR:  CREATE INDEX CONCURRENTLYはトランザクションブロックの内側では実行できません
2015-07-29 14:06:19 JST ステートメント:  CREATE INDEX CONCURRENTLY "microposts_user_id_inserted_at_index" ON "microposts" ("user_id", "inserted_at")
DB関連でエラーが出たら、
利用しているDBのログも確認した方が良いです。
ようかくですが、初めてのマイグレーションを実行してみましょう。

Example:

>mix ecto.migrate

22:38:04.343 [info]  == Running SampleApp.Repo.Migrations.CreateUser.change/0 forward

22:38:04.343 [info]  create table users

22:38:04.379 [info]  create index users_name_index

22:38:04.388 [info]  create index users_email_index

22:38:04.398 [info]  == Migrated in 0.4s
マイグレーションが無事終わったら、DBの方でも確認してみて下さい。

User

Userのコントローラ、ビューを作成します。
また、サインアップのためのルーティングの追加とUserのnewテンプレートを作成します。
サインアップのルーティングを追加します。

File: web/router.ex

scope "/", SampleApp do
  pipe_through :browser # Use the default browser stack

  ...
  get "/signup", UserController, :new
end
Userコントローラを作成します。
また、newアクションの関数も作成します。

File: web/controllers/user_controller.ex

defmodule SampleApp.UserController do
  use SampleApp.Web, :controller

  def new(conn, _params) do
    render conn, "new.html"
  end
end
続いて、Userビューを作成します。

File: web/views/user_view.ex

defmodule SampleApp.UserView do
  use SampleApp.Web, :view
end
Userのテンプレートを格納するディレクトリを作成します。
userと言うディレクトリを作成して下さい。

Directory: web/templates/user

Userのnewテンプレートを作成します。

File: web/templates/user/new.html.eex

<div class="jumbotron">
  <h1>Sign up</h1>
  <p>Find me in web/templates/user/new.html.eex</p>
</div>
“Filling in Layout”で追加しておいた、リンクを修正します。

File: web/templates/static_pages/home.html.eex

<div class="jumbotron">
  <h1>Welcom to the Sample App</h1>

  ...

  <%= link "Sign up now!", to: user_path(@conn, :new), class: "btn btn-large btn-primary" %>
</div>
これでUserを扱うために、最低限必要なものが実装できました。
今回、”mix phoenix.gen.html”を利用しなかったのは、理由があります。
確かにコマンドを使えば楽なのですが、一から手を動かしていかなければ覚えられないためです。
少し手間でしょうが、一から学んで行きましょう!!

Note:

補足として知っておいて欲しいのですが、  
マイグレーションファイルの記述方法は、通常のElixirの記述方法ではありません。  

この記述が可能なのは、Elixirの機能であるマクロを利用しているからです。  
マクロを使えば、独自の記述ができるDSL(Domain-Specific Language)を実装できます。  

勿論ですが、限界や制限はあります。  
それでも有用な機能ですので、いずれ使う時が来た時のために、  
こういったことも可能なのだと知識の一つとして持っておいて損はありません。

Verify

モデルのデータをDBに格納する前には、検証を行うのが一般的ですね。
Userモデルのデータも例外ではないです。Userモデルへ検証を追加していきます。
今あるカラムは、nameとemailですね。
さて、必要な検証は何でしょうか?
簡単にまとめてみました。
  • nameの検証内容
    • 存在性
    • 一意性
    • 文字数
  • emailの検証内容
    • 存在性
    • 一意性
    • フォーマット
とりあえずは、こんなところでしょう。
さて、検証項目が分かったところで実装していきましょう。
検証を行うための機能ですが…実は既にEctoに実装されています。
Ectoにある機能では足りない場合、自分で検証用の機能を実装します。
UserモデルへEcto.Changesetの検証関数を追加します。

File: web/models/user.ex

defmodule SampleApp.User do
  ...

  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)
    |> unique_constraint(:name)
    |> unique_constraint(:email)
    |> validate_length(:name, min: 1)
    |> validate_length(:name, max: 50)
  end
end
各関数の機能は、以下のようになっています。
  • validate_format/4: フォーマットの検証
  • unique_constraint/3: 一意性の検証
  • validate_length/3: 文字数の検証
この他にも多くの検証用関数が用意されていますので、
一度、Ecto.Changesetのドキュメントに目を通した方が良いと思います。
さてここで、困ったことが起きました。
存在性を検証してくれる関数が存在しません。
そのため、自分で実装しなければいけません。
存在性が存在しないとはこれいかに…
lib配下に、helpersと言うディレクトリを作成して下さい。

Directory: lib/helpers

検証を補助するモジュールを作成します。

File: lib/helpers/validate_helper.ex

defmodule SampleApp.Helpers.ValidateHelper do
  def validate_presence(changeset, field_name) do
    field_data = Ecto.Changeset.get_field(changeset, field_name)

    cond do
      field_data == nil ->
        Ecto.Changeset.add_error changeset, field_name, "#{field_name} is nil"
      field_data == "" ->
        Ecto.Changeset.add_error changeset, field_name, "No #{field_name}"
      true ->
        changeset
    end
  end
end
changesetと検証したいフィールド名を引数に取り、
そのフィールドの値が、nilか空文字でないことを確認しています。
作成した存在性の検証を追加します。
モデル全体で使いたいので、SampleApp.Web.model/0で
SampleApp.Helpers.ValidateHelperのimportを追加します。

File: web/web.ex

def model do
  quote do
    use Ecto.Model

    import Ecto.Changeset
    import Ecto.Query, only: [from: 1, from: 2]

    import SampleApp.Helpers.ValidateHelper
  end
end
このSampleApp.Webでは、
各モデル、ビュー、コントローラ、ルータで必ず使うモジュールのuseやimportなどを行っています。
全てのモデルで使うモジュールがあるといった場合は、ここで追加してあげましょう。
勿論、モデル以外でも問題ありません。
Userモデルへ検証関数を追加します。

File: web/models/user.ex

defmodule SampleApp.User do
  ...

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> validate_presence(:name)
    |> validate_presence(:email)
    ...
  end
end

Add password colum

パスワードのカラムを、ユーザのデータモデルに追加します。
本来であれば、一度のマイグレーションで行えば良いのですが、
再マイグレーションする練習として丁度良いので、パスワードは別で追加しています。
  • 追加のデータモデル
    • 対象モデル名: User
    • 対象テーブル名: users
    • 追加カラム(カラム名:型(オプション)): password:string(virtual)、password_digest:string
virtual属性とは・・・フィールドが正しいとき、データベースまで持続させない属性です。
ユーザ入力とデータ検証は生の文字列のまま行いたいですが、
DBへ格納するデータとしては暗号化されていて欲しいです。
そうなると、passwordカラムは生の文字列のままなので、DBまで値の持続をさせたくありません。
なので、passwordカラムで入力と検証を行った後、
正しければ暗号化してpassword_digestカラムへ格納するといった動作をさせたいです。
そして、passwordカラムの値はDBまで持続させないようにできるのが、virtual属性です。
そうするとDBのデータ上、passwordカラムは空、password_digestカラムは暗号化した文字列といった状態が作れます。
  • password: 生の文字列
  • password_digest: 暗号化した文字列
それでは、パスワードカラムの追加を行います。
Ectoのコマンドを利用して、マイグレーションファイルだけ生成します。

Example:

>mix ecto.gen.migration add_password_to_users
“mix ecto.gen.migration”コマンドを使えば、
マイグレーションファイルだけ生成できます。
マイグレーションファイルを以下のように編集します。

File: priv/repo/[timestamp]_add_password_to_users.exs

defmodule SampleApp.Repo.Migrations.AddPasswordToUsers do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :password, :string
      add :password_digest, :string
    end
  end
end
今回は新しくテーブルを追加するわけではないです。
既存のテーブルへカラムを追加するので、
“alter table(テーブル名(アトム))”を利用しています。
マイグレーションを実行します。

Example:

>mix ecto.migrate
Userモデルのスキーマへパスワードのフィールドを追加します。
また、required_fieldsにも追加します。
(virtual属性は、ここで付加します)

File: web/models/user.ex

defmodule SampleApp.User do
  ...

  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()

  ...
end

Encrypted password

パスワードの暗号化を行えるようにしましょう。
暗号化と復号化は、Safetyboxを利用して行います。
せっかくですから、暗号化を扱うためのモジュールを作成してみましょう。

File: lib/encryption.ex

defmodule SampleApp.Encryption do
  def decrypt(password) do
    Safetybox.decrypt(password)
  end

  def encrypt(password) do
    Safetybox.encrypt(password, :default)
  end
end
入力、検証後にパスワードの暗号化を行い、password_digestへ格納したいです。
しかし、入力、検証を行った後どうやってpassword_digestへ値を格納すればよいでしょうか?
何かしらの処理の後にフィールドへ操作を行いたい時は、
Ectoのコールバック機能を利用します。
UserモデルへCallbacksのuseとbefore_insertのコールバックを追加して下さい。
また、before_insertで指定したアトム名と同じ関数を定義して下さい。

File: web/models/user.ex

defmodule SampleApp.User do
  use SampleApp.Web, :model
  use Ecto.Model.Callbacks

  before_insert :set_password_digest

  ...

  def set_password_digest(changeset) do
    password = Ecto.Changeset.get_field(changeset, :password)
    change(changeset, %{password_digest: SampleApp.Encryption.encrypt(password)})
  end
end
各コールバックには関数名を指定します。
その関数名で実装すれば各動作の前後に動いてくれます。
今回利用している、before_insertは、DBへの挿入処理の前に実行するものです。
他にも色々なコールバックがありますので、用途に応じて使い分けができます。

Additional verification

パスワードにも検証が必要ですね。
検証の追加を行います。
  • passwordの検証内容
    • 存在性
    • 文字数
Userモデルへパスワードの検証を追加して下さい。

File: web/models/user.ex

defmodule SampleApp.User do
  ...

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    ...
    |> validate_presence(:password)
    |> validate_length(:password, min: 8)
    |> validate_length(:password, max: 100)
  end

  ...
end

Before the end

ソースコードをマージします。
>git add .
>git commit -am "Finish modeling_users."
>git checkout master
>git merge modeling_users

Speaking to oneself

Userモデルが実装できました。
次の章では、このUserモデルを使ってサインアップ機能を実装していきます。

Bibliography

​

2015年7月1日

[Phoenix]Ectoで1対多の関係性を検証する

目的

Ectoで1対多の関係性を検証する。

実行環境

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. 1対多の関係?
  2. has_manyを検証!

1. 1対多の関係?

1対多の関係と言うと何が思いつくでしょうか?
男一人に対して女複数人のハーレム。(またはその逆)
若しくは、複数人に対しての浮気。
碌なのが思いつかないですね。(冗談)
閑話休題。
簡単なWebサイトを例にしましょう。
登録しているユーザが記事を投稿できるWebサイトがあります。
その場合、ユーザ一人に対して複数の記事が紐づけられますね。
そういった場合に今回実施する1対多の関係性を設定しておくと楽になるわけです。
(一々、SQL文で探して結合してなんてやりたくないです・・・)
Ectoには以下のような機能があります。
  • Schema.has_many/3
  • Schema.belongs_to/3
  • Schema.has_one/3
大体どれを使うか想像ができたのではないでしょうか?

2. has_manyを検証!

では、実際に検証していきましょう。
まず、Userモデル以外に新しいモデルを作成します。
記事モデルでいいか・・・
データモデルは以下になります。
microposts: id:integer, content:string, user_id:integer, timestamp
※idとtimestampは自動で作成されるので、それ以外の項目を指定する
>mix phoenix.gen.html Micropost microposts content:string user_id:integer
DBでSQLを使ってデータを入れるのが面倒くさいので、
画面上からmicropostsテーブルへデータを入力するために”gen.html”を使用している。
>mix ecto.migrate
[info] == Running EctoModelsSample.Repo.Migrations.CreateMicropost.change/0 forward
[info] create table microposts
[info] == Migrated in 0.2s
それでは、二つのモデルを編集します。
web/models/user.exを開き、
スキーマを以下のように編集して下さい。
schema "users" do
  field :name, :string
  field :email, :string

  has_many :microposts, EctoModelsSample.Micropost
  timestamps
end
web/models/micropost.exを開き、
スキーマを以下のように編集して下さい。
schema "microposts" do
  field :content, :string
  belongs_to :user, EctoModelsSample.User, foreign_key: :user_id

  timestamps
end

@required_fields ~w(content user_id)
@optional_fields ~w()
Tips:
foreign_keyを指定しない場合、
“:user+_id = user_id”と言った形で補完するようです。
次にUserコントローラを修正します。
web/controllers/user_contoroller.exを開き、
以下のように関数を編集して下さい。
def index(conn, _params) do
  users = EctoModelsSample.User |> Repo.all() |> Repo.preload [:microposts]
  render(conn, "index.html", users: users)
end
def show(conn, %{"id" => id}) do
  user = EctoModelsSample.User |> Repo.get(id) |> Repo.preload [:microposts]
  render(conn, "show.html", user: user)
end
Micropostを画面上に表示させたいですね!
テンプレートに以下のようなコードを記述すれば表示できます。
<%= for user <- @users do %>
  <%= user.name %>
  <%= user.email %>

  <%= for post <- user.microposts do %>
    <%= post.content %>
  <% end %>
<% end %>

管理人の独り言~

Phoenixのガイドだとuser_idを生成していないのだが・・・
どうやってあれで動いているのだろうか・・・
検証として、user_idを生成しないパターンも行ったのですが、
そうすると実行時に「user_idなんてキーねぇよ」って怒られるのです。
まだ不明な所は残っているが、
1対多の関係性を検証できたからとりあえず良しとしよう。
また後でhexdocs - Ecto.Schemaを参考にしてやってみよう。

参考文献

​

[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で表示しようと思ったのですけど・・・
デザインが崩れる・・・何でだよう!もう!!

参考文献

​

人気の投稿