45日目
今日の学習
Ruby on Rails
Railsチュートリアル 第6章
昨日から引き続き、6章を学習。
メールフォーマットのバリデーション
Rails 6 でプロダクト開発を学ぼう - Railsチュートリアル
正規表現を用いることで、メールアドレスが無効でないかどうかを調べることができる。
Railsチュートリアルで紹介されていた、メールアドレス用の実用的な正規表現は以下のもの。
/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
一つずつ見ていくと、一番最初と最後の/
が、それぞれ開始と終了を示す。最後尾にあるi
は、大文字小文字を無視するオプション。
先頭の次にある\A
は、文字列の先頭を意味する。
[\w+\-.]
の部分で、@以前の文字列を該当させる。意味合いは、英数字、_、+、-、.のいずれかを少なくとも1文字以上繰り返す。
その後に@が入り、後の[a-z\d\-.]+
で、英小文字、数字、ハイフン、ドットのいずれかを少なくとも1文字以上繰り返す。
\.
でドットを該当させ、ドット以下の[a-z]+
では英小文字を少なくとも一文字以上繰り返す。(jpやcomのような部分)
\z
は文字列の末尾を表し、この全てに合致して初めて正規表現とマッチしたことになる。
上記の正規表現を実際にバリデーションとして加えるなら、以下のような形にする。
class User < ApplicationRecord # 定数として正規表現を代入する VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i # formatヘルパーを利用して、引数に正規表現を入れる validates :email, format: { with: VALID_EMAIL_REGEX }
Active Record バリデーション - Railsガイド
format
ヘルパーの効果を引用。
formatヘルパーは、withオプションで与えられた正規表現と属性の値がマッチするかどうかのテストによる検証を行います
また、上記の正規表現では、foo@bar..com
のようにドットが連続しているメールアドレスも該当させてしまうため、以下のような正規表現に書き換えることも行なった。
/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
一意性のバリデーション(callbackメソッドを利用)
同じメールアドレスが登録されないように、メールアドレスに対して一意性を強制する。
validates
メソッドの、:uniqueness
オプションを利用する。
さらに、ここでemail
カラムに対してインデックスを追加し、このインデックスも一意になるようにする。
Userテーブルのemailカラムにインデックスを追加する場合
rails generate migration add_index_to_users_email
上のコマンドを実行して作成されたマイグレーションファイルに対して、一意性の定義をする必要がある。定義後はrails db:migrate
を忘れずに。
# 作成されたマイグレーションファイル class AddIndexToUsersEmail < ActiveRecord::Migration[6.0] def change # ここに定義を追加する add_index :users, :email, unique: true end end
また、以下のように大文字と小文字の違いだけのメールアドレスは、同一のものだと判断する必要がある。
Foo@ExAMPle.Com foo@example.com
これらを同一の文字列だと認識させるために、データベースに保存される直前に全ての文字列を小文字に変換する。
Foo@ExAMPle.Com
の場合は、保存する直前に全て小文字の状態(foo@example.com
)に変換してしまおうという手段。
これを実装するために、Active Record
のcallback
メソッドを利用する。
Active Record コールバック - Railsガイド
コールバックを利用することで、Active Recordオブジェクトが作成/保存/更新/削除/検証/データベースからの読み込み、などのイベント発生時に常に実行されるコードを書くことができます。
callback
メソッドでは、オブジェクトの状態が切り替わる「前」か「後」に指定したアクションを発生させる。
Active Record コールバック - Railsガイド
どうやら種類がやたらとある。今回の場合は、「データを保存する直前に」全て小文字にしたいので、before_save
というコールバックを利用する。
class User < ApplicationRecord # emailをdowncaseメソッドで書き換えて代入 before_save { self.email = email.downcase } validates :email, uniqueness: true end
左式のself.
を忘れないように注意。右式は省略可能。
もしくは、破壊的メソッドを利用しても同じ効果が得られる。
before_save { email.downcase! }
安全なパスワード
パスワードは、入力されたものをハッシュ化
してデータベースに保存する。
ハッシュ化
を行うことで、パスワードを入力された生の状態で保存してしまうという、セキュリティ上非常に危険なことを避けることができる。
これをRailsチュートリアルではセキュアなパスワード
と表現しているため、ここでもそのように書くことにする。
has_secure_password
セキュアなパスワード
の実装は、has_secure_password
というRailsのメソッドを呼び出す。
今回は、Userテーブルにパスワードを保存したいので、Userモデルに上のメソッドを書き込む。
書き込むことで、以下の三つの機能が使用可能になる。
①ハッシュ化
されたセキュアなパスワード
が、データベース内のpassword_digest
という属性に保存できるようになる。
password_digest
は、ハッシュ化されたパスワード。
has_secure_password
を使うためには、まずテーブルに新しくpassword_digest
用のカラムを用意しなければならない。usersテーブルに追加する場合は以下のようにする。
rails g migration add_password_digest_to_userspassword_digest:string
また、パスワードをハッシュ化するために必要なgem、BCrypt
もGemfileに追加すること。
②テーブルにpassword
属性で保存ができるようになる。
わざわざpassword_digest
を用いずに、password
を用いれば保存が可能。
password_confirmation
属性も同時に使えるようになり、こちらはデータベースに保存されない仮想の属性で、パスワードの入力確認に使用される。
password
、password_confirmation
の双方が合致しているかどうか確かめるバリデーションも追加される。
password
として受け取った文字列は、BCrypt
で変換される。
>>user = User.create(password: "foobar") >> user.password_digest # ハッシュ化されたfoobar => "$2a$12$ZidSEZgTY9xTylGlaLrpaOjuM.4EoMyJfy5utl0EmKU1d4TEx4i8O"
参考 Railsユーザーモデルのバリデーション設定(has_secure_password解説) - 独学プログラマ
③authenticate
メソッドが使えるようになる。
このメソッドは、引数に渡された文字列をハッシュ化した値と、データベースないにあるpassword_digest
カラムの値を比較してくれる。
user.authenticate("foobar")
正しいパスワードを渡すと、userオブジェクトの情報が返ってくる。
オブジェクトの先頭に!!
をつけて、論理値オブジェクト
に変換することによって、合致している場合はtrue
を返してくれるようにする。
!!user.authenticate("foobar") => true
Active Recordのメソッド
これまで、find_by
やall
に対して、「Railsでよく見かけるやつ」くらいの認識しかなかったので、もうちょっとしっかり覚えておきたい。
やんばるエキスパート教材 | Active Recordの様々なメソッド
やんばるエキスパート教材 | Active Record の様々な削除メソッド
【初心者向け】RailsのActive Recordの解説&メソッドまとめ - Qiita
CRUD(create, read, update, delete)
●Create(生成)
- create
new
とsave
両方の効果があり、生成と同時にDBへ保存
●Read(参照)
- all
User.all
などで、特定のテーブルの全てのレコードを取得する
order
作成日時順にレコードを取得できる
User.order(created_at: :asc)
User.order(created_at: :desc)
find
User.find(1)
のように、数字を渡すと該当するidのレコードを呼び出すfind_by
User.find_by(age: 15)
の場合、ageカラムが15のレコードから最初の一件を取得where
User.where(age: 15)
の場合、ageカラムが15のレコードを全て取得
,
で区切って条件を足していくことも可能where.not
whereで指定したものではないレコードを全て取得limit
取得するデータの数を制限することができる
User.order(created_at: :asc).limit(5)
であれば、作成日時から昇順で5件取得size
User.where(age: 15).size
の場合、ageが15のユーザーの人数を取得select
特定のカラムの値だけ取得pluck
特定のカラムの値だけ取得し、配列として返す
User.pluck(:id, :name)
の場合、usersテーブルのidカラムとnameカラムの値だけ取得する
●Update(更新)
- update
レコードを更新する
●Delete(削除)
- destroy
データを一件削除し、アソシエーションで関連付けられていればデータも自動的に削除
destroy_all
複数のデータを削除するdelete
データを一件削除するが、関連するデータは削除されないdelete_all
複数のデータを削除するが、関連するデータは削除されない
DBごとに書き方が微妙に異なるが、どのDBでもRubyで統一してDBを操作できて便利だ。
学習メモ
update_attribute
データベースの特定の属性のみを更新したい場合は、update_attribute
を利用する。
user.update_attribute(name: "Hasegawa")