LoginSignup
4
1

More than 3 years have passed since last update.

わたしがRailsチュートリアルで学んだこと【6章】

Last updated at Posted at 2019-07-11
  • 注意:プログラミング歴31日の初心者が書いています

  • 注意:間違っていたら優しく教えてください(喜びます)

「Ruby on Rails チュートリアル実例を使ってRailsを学ぼう」
https://railstutorial.jp/

素晴らしいチュートリアルに感謝します。

6.1.1 データベースの移行

リレーショナルデータベースについて

テーブル型になっているデータベース。
よくあるデータベースの形です。
1つの行は1つのIDを示しており、それぞれのIDがnameやemailといったデータを持ちます。

マイグレーションについて

マイグレーションはデータベースへの追加や変更を行います。

rails generate model User name:string email:stringでUserモデル(データベース)が自動生成されますが、user.rbはまだ空っぽです。
そのかわり、db/migrateディレクトリにマイグレーションファイルが生成されています。

rails db:migrate

上記を実行することで、テーブルの作成を行います。
また、このマイグレーションファイルを使ってテーブルへの変更を元に戻す(ロールバックする)ことも可能です。

rails db:rollback

上記を実行すると、テーブルの作成を元に戻すことが可能です。

マイグレーションやロールバックを行うたびにdbディレクトリ内のdevelopment.sqlite3というファイルが更新されます。「DB browser」を利用してデータベースの中身を確認することができます。
https://sqlitebrowser.org/dl/

6.1.3 ユーザーオブジェクトを作成する

User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

作成したUserクラスに対して、.newメソッドを使用すると、Userオブジェクト(Userインスタンス)を作成できます。
引数なしで呼び出した場合、idnameemailcreated_atupdated_atのプロパティはいずれもnillです。

user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com",
created_at: nil, updated_at: nil>

引数にnameemailを指定する場合は、上記のように記述します。
ただし、idcreated_atupdated_atのプロパティはいずれもnillです。

.newで作成したオブジェクトに対して.saveメソッドを使用することで、データベースに保存されます。
idcreated_atupdated_atのプロパティは、この段階で決定されます。

User.create(name: "A Nother", email: "another@example.org")
#<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2016-05-23 19:18:46", updated_at: "2016-05-23 19:18:46">

.createメソッドを使用すれば、.new.saveを同時に行うことができます。

.destroyメソッドは、反対にオブジェクトを削除します。

6.1.4 ユーザーオブジェクトを検索する

User.find(1) #id==1 のオブジェクトを返す

.find()を使用すると、引数に合致するオブジェクトを返します。

User.find_by(email: "mhartl@example.com")

特定の属性で検索する場合は、.find_byが有効です。

 user.email
=> "mhartl@example.net"
 user.email = "foo@bar.com"
=> "foo@bar.com"
 user.reload.email
=> "mhartl@example.net"

.プロパティ名でそのオブジェクトのプロパティにアクセスし、上書きすることも可能です。
また、.reloadを実行すると、データベースの情報を元にオブジェクトを再読み込みするので、.save前であれば変更が取り消されたように見えます。
(実際は、保存前のデータをDBから再読み込みしているだけ)

6.2 ユーザーを検証する

6.2.1 有効性を検証する

require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  test "should be valid" do
    assert @user.valid?
  end
end

UserTestクラス内のsetupメソッドは、テストが実行される直前に実行されます。
ここでは、Userオブジェクトがちゃんと作成できるかを確認するために、.newして
@userインスタンス変数に格納しています。

"should be valid"と名付けられたテストでは、.valid?メソッドで@userが有効かどうかを確認します。
.valid?メソッドは、後述する「バリデーション」を確認し、オブジェクトにエラーがない場合はtrueが返され、そうでなければfalseが返されます。

ここでは何もバリデーションを設定していないので、@user.valid?はtrueになります。

6.2.2 存在性を検証する

 test "name should be present" do
    @user.name = "     "
    assert_not @user.valid?
 end

まず、@user.nameにからの文字列を代入しています。名無しです。

assert_notは、引数がfailseもしくはnillのとき成功となります。

ここで注意したいのは、あなたはまだバリデーションをなにも設定していないので、名前が空欄だろうがなんだろうが、.@user.valid?trueだということです。

空欄を認めないバリデーションを設定しない限り、.@user.valid?trueなので、assert_notを含むテストは失敗します。

6.2.4 フォーマットを検証する

%w[]について

Rubyの%記法と呼ばれているものです。
以下にまとまっています。
『Rubyで%記法(パーセント記法)を使う』 @mogulla3
https://qiita.com/mogulla3/items/46bb876391be07921743

%w[]は配列を作ります。スペースで区切りを指定します。
カンマやクオーテーションを省略して配列を作ることができます。

test "email validation should accept valid addresses" do
    valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
                         first.last@foo.jp alice+bob@baz.cn]
    valid_addresses.each do |valid_address|
      @user.email = valid_address
      assert @user.valid?, "#{valid_address.inspect} should be valid"
    end
  end

このテストでは、

  • まず、valid_addressに有効と思われるメールアドレスの例を配列として定義します。

  • 次に、.eachメソッドで配列の中身をひとつずつ取り出してassertにかけます。

  • assertメソッドの第2引数は、エラーメッセージになっています。assartが失敗した際に呼び出されます。

ちなみに、.inspectメソッドはオブジェクトや配列などに対して使用すると、対象の型に沿った文字列を返します。

正規表現について

validates :email, format: { with: /<regular expression>/ }

formatというオプションは引数に「正規表現(regex)」をとります。
正規表現は、文字列のマッチングに使うと便利な言語です。

/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

上記はメールアドレスを正規表現で表したものです。
「英数字の組み合わせ」@「英数字の組み合わせ」.「英数字の組み合わせ」

という内容です。

6.2.5 一意性を検証する

  test "email addresses should be unique" do
    duplicate_user = @user.dup
    @user.save
    assert_not duplicate_user.valid?
  end

@user.dup.dupメソッドはオブジェクトのコピーを作成して返すメソッドです。
@userのコピーを作成し、変数duplicate_userに格納しています。

@userと同じ情報を持つオブジェクトは、作成できてはなりません。

duplicate_user.valid?trueつまり有効なメールアドレスとして判定されると、テストは失敗します。

@userと同じ情報を持つオブジェクトの作成を許可しないよう、Userクラスにバリデーションを追加してテストが通るように修正します。

データベースのインデックスを追加する

class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
  def change
    add_index :users, :email, unique: true
  end
end

add_indexはRailsの関数です。テーブルにインデックスを追加します。

add_index :users, :email, unique: true
  • 第一引数はインデックスを追加するテーブル名(users

  • 第二引数はインデックスを追加するカラム名(email

  • 第三引数はオプション追加(unique: true

以下のようなオプションを設定できます。

:name #インデックスの名前
:unique #trueを指定するとユニークなインデックス
:length #インデックスに含まれるカラムの長さ

例えば、あるメールアドレスを使ってログインする場合、

  • インデックスを使わない場合だと、データベースの隅から隅まで検索をおこなわなければいけません。

* インデックスを貼っておけば、アルファベット順に検索したり、長さ順に検索したりできます。

このチュートリアルでは、ユニークであることを保証するためにインデックスを使っています。

6.32: email属性を小文字に変換してメールアドレスの一意性を保証する

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

before_saveはデータベースへの保存前に、引数の関数(コールバック関数)を実行します。

before_savenewされるたびに起動するので、selfnewしたインスタンス自身です。自分自身のメールアドレスに対して.downcaseで小文字に変換します。

なお、

self.email = self.email.downcase

の右辺selfを省略した書き方になっています。

6.33: リスト 6.32のメールアドレスの小文字化に対するテスト

  test "email addresses should be saved as lower-case" do
    mixed_case_email = "Foo@ExAMPle.CoM"
    @user.email = mixed_case_email
    @user.save
    assert_equal mixed_case_email.downcase, @user.reload.email
  end
  • まず、テスト用に"Foo@ExAMPle.CoM"大文字小文字を含むアドレスを用意

  • @user.emailに上書きし、.saveでデータベースに保存する...けど、

  • でも、データベースへの保存の前に、Userクラスのbefore_save { self.email = email.downcase }が起動。小文字にしてから@userは保存される。

  • "Foo@ExAMPle.CoM".downcaseを使用した文字列」と、「データベースに保存された文字列」が同じかどうかassert_equalで検証する。

【補足】メールアドレスの検証まとめ

  1. メールアドレスの長さは255文字を上限とするバリデーション追加
  2. 正規表現でメールアドレスを表現し、それに合わない文字列はNGとするバリデーション追加
  3. 大文字・小文字を区別せず、ユニークであるようバリデーション追加
  4. データベース内でもユニークであることを保証するため、インデックスをモデルに追加
  5. データベースへの保存の際に、データベースに保存される直前にすべての文字列を小文字に変換し、大文字・小文字の区別をさらに安全にする

6.3.1 ハッシュ化されたパスワード

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 }
  has_secure_password #これ
end

Railsにおいてパスワードの実装は、以下をまず実装する。

  • データベースにpassword_digestカラムを追加する

  • モデルにhas_secure_passwordを追加する

  • bcryptをGemfileに追加する(よりセキュアに)

  • テストコード内の仮想インスタンスのプロパティにpassword: "文字列", password_confirmation: "文字列"を追加する(has_secure_passwordによって仮想属性が追加されているため)

6.3.3 パスワードの最小文字数

多重代入について

  test "password should be present (nonblank)" do
    @user.password = @user.password_confirmation = " " * 6
    assert_not @user.valid?
  end

  test "password should have a minimum length" do
    @user.password = @user.password_confirmation = "a" * 5
    assert_not @user.valid?
  end

passwordpassword_confirmationの2つの属性に同時に代入を行っています。

  • 上のテストは、スペースが6文字続く場合

  • 下のテストは、パスワードが5文字しかない場合

がfalseになるようテストしています。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1