プログラミング備忘録

プログラミングの学習状況をメモしています

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カラムに対してインデックスを追加し、このインデックスも一意になるようにする。

Railsではマイグレーションでインデックスを追加する。

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 Recordcallbackメソッドを利用する。

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属性も同時に使えるようになり、こちらはデータベースに保存されない仮想の属性で、パスワードの入力確認に使用される。

passwordpassword_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_byallに対して、「Railsでよく見かけるやつ」くらいの認識しかなかったので、もうちょっとしっかり覚えておきたい。

やんばるエキスパート教材 | Active Recordの様々なメソッド

やんばるエキスパート教材 | Active Record の様々な削除メソッド

【初心者向け】RailsのActive Recordの解説&メソッドまとめ - Qiita

CRUD(create, read, update, delete)

●Create(生成) - create
newsave両方の効果があり、生成と同時に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")