静的型付け言語原理主義者によるRailsチュートリアル(第6章)
第6章 ユーザーのモデルを作成する
- 第6章から第12章を通して Rails のログインと認証システムを一通り開発する
- 第6章ではユーザー用のデータモデルの作成と、データを保存する手段の確保について
6.1 User モデル
- ユーザー登録のために必要なのは、まずは情報を保存するためのデータ構造を作成すること
- Rails ではデータモデルとして扱うデフォルトのデータ構造のことをモデルと呼ぶ
- データ永続化のデフォルトの選択肢はデータベース
- データベースとやりとりするデフォルトのライブラリは Active Record
- SQLを意識しなくてよい
- 意識しなくてよいことのメリットよりも、ブラックボックス化するデメリットの方が大きいと思えてならない
- SQLを意識しなくてよい
- Rails にはマイグレーション機能もある
- データ定義を Ruby で記述可能
- Rails はデータベースの細部をほぼ完全に隠蔽し、切り離してくれる
6.1.1 データベースの移行
- 永続化可能なユーザーのモデルを作成する
- モデルを作成するときには
generate model
コマンドを使用する- Rails の慣習として、コントローラ名には複数形を、モデル名には単数形を用いる
- マイグレーションファイルやテストクラスも作成される便利コマンド
- マイグレーションファイルによりインクリメンタルな変更が可能になる
- マイグレーション自体はデータベースに与える変更を定義した
change
メソッドの集合体 t.timestamp
によりcreated_at
とupdated_at
のマジックカラムが生成される- このカラム名、業務で見たことのあるやつだ…!!
- 作成・更新時に自動更新される
- マイグレーションは
rails db:migrate
で実行可能db/schema.rb
が更新される
rails db:rollback
でもとに戻すことも可能- ロールバックすると
db/schema.rb
の内容も元に戻る - 中では
drop_table
を呼び出している- こわやこわや
- ロールバックすると
6.1.2 model ファイル
- 以後モデル用ファイルを理解することに専念する
6.1.3 ユーザーオブジェクトを作成する
- Rails コンソールをサンドボックスモードで起動
- すべての変更は終了時にロールバックされる
User.new
を引数無しで呼んだ場合はすべての属性がnil
user.valid?
でオブジェクトの有効性が確認できる- DBに保存するためには
user.save
する必要がある- これだけでSQLが実行される
- 当然クエリは指定できない
- Active Record では
User.create
でモデルの生成と保存を同時に行うことが可能- 相変わらず強力。。。
destroy
で delete 可能- SQLはもちろん自動的に実行される
destroy
しても削除されたオブジェクトはまだメモリ上に残っている点に注意- 本当に削除されたのかはどうやって分かるのか??
6.1.4 ユーザーオブジェクトを検索する
- Active Record ではオブジェクトを検索する方法が複数ある
find
メソッドで結果が得られなかったときは例外が発生するActiveRecord::RecordNotFound
- 属性でユーザーを検索することも可能
find
だけでなくfirst
やall
もある
6.1.5 ユーザーオブジェクトを更新する
- 基本的な更新方法は2つ
- ひとつは属性を個別に代入する
- save を忘れずに
- もうひとつは
update_attributes
を使う方法
- ひとつは属性を個別に代入する
>> user.update_attributes(name: "The Dude", email: "dude@abides.org")
=> true
>> user.name
=> "The Dude"
>> user.email
=> "dude@abides.org"
update_attributes
メソッドは属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行う- どんな SQL が吐かれているのか?単純に update しているだけ?
- 検証に一つでも失敗すると、この処理は成功しない
6.2 ユーザーを検証する
- User モデルの属性に制限を設ける
- Active Record の Validationを使う
6.2.1 有効性を検証する
- バリデーション機能実装には TDD がぴったり
- User 用のテストを書いていく
setup
メソッド- 各テストの直前に実行される
- よくあるテスティングフレームワークと同じ
- 各テストの直前に実行される
rails test:models
コマンドでモデルのテストだけが実行可能
6.2.2 存在性を検証する
- もっとも基本的なバリデーションは存在性
- User モデルに存在性に関するテストを追加する
validates
メソッドにpresence:true
の引数を与えると、属性の存在を検査できる
class User < ApplicationRecord
validates :name, presence: true
end
- 変数が有効かどうかを
valid?
メソッドでチェック可能になる- すなわち、name 属性が空の場合は検証に失敗するということ
erros.full_messages
を使えばさらに詳細なエラーが確認できるerrors.messages
だとハッシュでエラー詳細が確認可能
6.2.3 長さを検証する
- User モデルの属性に名前の長さの制限を加える
class User < ApplicationRecord
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 }
end
valid?
メソッドで長さも検証可能になる- 検証に失敗した場合は同様に
errors.messages
で詳細が確認可能
6.2.4 フォーマットを検証する
- メールアドレスの有効性を検証する
- パターンが多いので大変
- メールアドレスのフォーマット検証用に
format
というオプションを使用する
validates :email, format: { with: /<regular expression>/ }
- 引数に正規表現を取る
- Rubular というサイトで学習可能
- 正規表現を今まで避けてきたけどやってみると結構わかりやすい気がしてきた
- Rubular というサイトで学習可能
- User モデルに検証用定数を追加
- 大文字で始まる名前は Ruby では定数を意味する
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
- ただし、上記パターンでは
foo@bar..com
を検出できないので、以下のように変更する
class User < ApplicationRecord
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
end
()
と*
がポイント()
でグループ化して*
は直前の文字がないか、直前の文字が1つ以上連続するかをチェック
6.2.5 一意性を検証する
- メールアドレスの一意性を検証する
validates
メソッドの:unique
オプションを使用
test "email addresses should be unique" do
duplicate_user = @user.dup
@user.save
assert_not duplicate_user.valid?
end
@user.dup
で@user
と同じメールアドレスのユーザーは作成できないことをテストする- 上記テストをパスさせるために、 User モデルの
email
のバリデーションにuniquness:true
を追加する - 通常メールアドレスでは大文字小文字が区別されないため、検証でもこの点を考慮する必要がある
- バリデーションを
uniqueness: { case_sensitive: false }
に変更すると、uniquness:true
のまま大文字小文字を区別しなくなる - しかしこの時点では Active Record がデータベースレベルでは一意性を保証していないという問題が残る
- データベースレベルでも一意性を強制することで解決する
- 具体的には email カラムにインデックスを追加し、そのインデックスが一位であるようにする
- データベースレベルでも一意性を強制することで解決する
- email インデックスの追加にはデータモデリングの変更が必要
- Rails ではマイグレーションでインデックスを追加する
migration
ジェネレーターを使用- まさかマイグレーションファイルを用意し、 Rails のメソッドでインデックスを追加できるとは。。。
- Rails ではマイグレーションでインデックスを追加する
- この時点では DB 用のサンプルデータが含まれている fixture 内で一意性の制限が保たれていないためテストが通らない
- fixutre とは
- 第8章で再登場するので今回はスルー
- fixutre とは
- データベースのアダプタによっては常に大文字小文字を区別するインデックスを使っているとは限らない
- DBに保存される直前にすべての文字列を小文字に変換する
- Active Record のコールバックメソッドを使用
- 特定の時点で呼び出されるメソッド
- 今回はオブジェクトが保存される時点で処理を実行したいので
before_save
を使用する
class User < ApplicationRecord
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
end
- 左辺の
self
は省略できないが、右辺のself
は省略可能 self.email = email.downcase
はemail.downcase!
と書いても結果は変わらないdowncase!
は属性を直接変更できる
6.3 セキュアなパスワードを追加する
- セキュアパスワード
- ユーザーにパスワードとパスワードの確認を入力させる
- パスワードをハッシュ化したものをDBに保存する
- ユーザーの認証手順
- パスワードの送信
- ハッシュ化
- DB内のハッシュ化された値との比較
- 比較結果が一致すれば送信されたパスワードが正しいと認識される
6.3.1 ハッシュ化されたパスワード
- セキュアなパスワード実装は
has_secure_password
という Rails メソッドを呼び出すだけでほとんど終わってしまう- 相変わらずなんて強力な。。。
- User モデルに
has_secure_password
メソッドを追加- セキュアにハッシュ化したパスワードをDB内の
password_digest
という属性に保存できるようになる password
とpassword_confirmation
という仮想的なペア属性が使えるようになる- 存在性と値が一致するかどうかのバリデーションも追加される
authenticate
メソッドが使えるようになる
- セキュアにハッシュ化したパスワードをDB内の
- 「この魔術的な
has_secure_password
機能を使えるようにするには一つだけ条件があります。」- 自分で魔術って言っちゃったよ
- モデル内に
password_digest
という属性が含まれていることが条件
password_digest
カラム用のマイグレーションを実施する- 末尾に
to_users
を付けたマイグレーション名にしておくと、users
テーブルにカラムを追加するマーグレーションが Rails によって自動的に追加される- どこまでやってくれるんだ Rails は。。。
- 末尾に
has_secure_password
を使ってパスワードをハッシュ化するためには、ハッシュ関数の bcrypt が必要- bcrypt gem を Gemfile に追加しておく
6.3.2 ユーザーがセキュアなパスワードを持っている
- テストが通るようにしていく
has_secure_password
には仮想的なpassword
属性とpassword_confirmation
属性に対してバリデーションする機能が強制的に追加されているため、その対応が必要
6.3.3 パスワードの最小文字数
- Rails でパスワードの長さを設定する方法はたくさんある
- たくさんある。。。
- 簡単でないことと最小文字数(6文字)を設定する
maximum
と同様にminimum
というオプションで最小文字数のバリデーションを実装できる
validates :password, length: { minimum: 6 }
has_secure_password
メソッドは存在性のバリデーションもしてくれるが、新しくレコードが追加されたときだけに適用される- 更新はバリデーションが効かない
6.3.4 ユーザーの作成と認証
- User モデルの基本部分が完成
- Web からのユーザー登録はまだできないので、Rails コンソールから手動で追加する
- 追加後に
password_digest
属性が参照可能 - さらに
authenticate
メソッドに正しいパスワードを渡すと、モデルがユーザーオブジェクトを返す
6.4 最後に
- ゼロから User モデルを作成し、name, email, password の各属性を追加、さらにそれらのバリデーションも追加した
- パスワードをセキュアに認証できる機能も実装
- これらが12行で実現できた
- Rails 恐ろしや
所感
Rails チュートリアルもようやく折返し地点まできた。今まで避けてきた正規表現にトライし、見た目ほど難しくないということが実感できたのが大きな収穫だった。Rails は相変わらず強力な機能を発揮している。わずか12行のコードで多機能なモデルを構築できたことには驚きしかない。当初は強力すぎるがゆえにマイナスの印象が強かった Rails だが、徐々に「適切に使えばこれほど便利なものはない」という認識に変わりつつある。それがどれほど難しいかはプロダクトコードで使用してみないと分からないのだろうが。