Ruby on Rails チュートリアル第6章をやってみて

#ユーザーのモデルを作成する
■第6章
第5章では、新しいユーザーを作成するためのスタブページを作ったところで終わった。本章では、一番重要なステップであるユーザー用のデータモデルの作成と、データを保存する手段の確保について学んでいく。

Railsでは、本番アプリケーションに適した認証システムを自分で構築するアプローチも選択できる。筆者曰く、自分自身で認証システムを構築した経験があれば仕組みを理解し、必要に応じて変更することがずっと容易になるらしい。

##6.1 Userモデル
ユーザー登録でまず初めにやることは、それらの情報を保存するためのデータ構造の作成。
Railsは、データベースの細部をほぼ完全に隠蔽し、切り離してくれる。
本書ではSQLiteを開発環境で使い、また、PostgreSQLを(Herokuでの)本番環境で使う。

###6.1.1 データベースの移行
この節での目的は、簡単に消えることのないユーザーのモデルに構築。
Railsはデータを保存する際にデフォルトでリレーショナルデータベースを使う。リレーショナルデータベースは、データ行で構成されるテーブルからなり、各行はデータ属性のカラム(列)を持ちます。

```
$ rails generate model User name:string email:string
```

今回は`name`や`email`といった属性を付けたUserモデルを使いたいので、このコマンドを打つ。`name:string`や`email:string`オプションのパラメータを渡すことによって、データベースで使いたい2つの属性をRailsに伝える。

*コントローラ名には複数形を使い、モデル名には単数形を用いるという慣習を頭に入れておく


今回のコマンドの結果で、マイグレーションと呼ばれる新しいファイルが生成される。

`db/migrate/[timestamp]_create_users.rb`

```
class CreateUsers < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end
```


マイグレーションファイル名の先頭には、それが生成された時間のタイムスタンプが追加された。複数の開発者によるチームでは、複数のプログラマが同じ整数を持つマイグレーションを生成してしまい、衝突を起こしていた。現在のタイムスタンプによる方法であれば、基本衝突は避けられる。

マイグレーションは`change`メソッドの集まり。このメソッドは`create_table`というRailsのメソッドを呼び、ユーザーを保存するためのテーブルをデータベースに作成する。

`t.timestamps`は特別なコマンドで、`created_at`と`updated_at`という２つの「マジックカラム(Magic Columns)」を作成します。これらは、あるユーザーが作成または更新されたときに、その時刻を自動的に記録するタイムスタンプ。

　　

```
rails db:migrate
```

このコマンドでマイグレーションの実行。`development.sqlite3`というファイルが生成される。これはSQLite5データベースの実体である。

[DB Browser for SQLite](https://sqlitebrowser.org)をダウンロードして`users`テーブルを確認するとこんな感じに。

![スクリーンショット 2022-02-12 17.48.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2210462/d41f7ebd-5291-2e24-d2d2-866cbad907f2.png)



###6.1.2 modelファイル

ApplicationRecordはActiveRecord::Baseを継承している。

###6.1.3 ユーザーオブジェクトを作成する
データベースを変更したくない時は、コンソールをサンドボックスモードで起動。

```rb
$ rails console --sandbox
```

ここからはコンソールで作業。

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

引数なしで呼んだ場合は、すべての属性が`nil`のオブジェクトを返す。

userを作ってみる。

```rb
>> 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>
```

nameとemail属性がちゃんと設定された。

`valid?`メソッドで`user`オブジェクトが有効か確認。

```rb
>> user.valid?
true
```

現時点ではデータベースに格納されていないので、`save`メソッドを使って保存。

```rb
>> user.save
   (0.1ms)  SAVEPOINT active_record_1
  SQL (7.1ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Michael Hartl"], ["email", "mhartl@example.com"], ["created_at", "2022-02-12 10:41:35.029896"], ["updated_at", "2022-02-12 10:41:35.029896"]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
```


idが更新され、マジックカラムには現在の日時が代入されている。

```rb
>> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2022-02-12 10:41:35", updated_at: "2022-02-12 10:41:35">
```

###6.1.4 ユーザーオブジェクトを検索する
データベースに保存されているかどうかを確認するために、`User.find`を使う。

```rb
User.find(1)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2022-02-12 10:41:35", updated_at: "2022-02-12 10:41:35">
```

`User.find`にユーザーのidを渡している。その結果、Active Recordはそのidのユーザーを返す。他にもいろんなメソッドがある。

###6.1.5 ユーザーオブジェクトを更新する
更新の方法は基本的に2つ。1つは属性を個別に代入する方法。

```rb
>> user           # userオブジェクトが持つ情報のおさらい
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 19:05:58", updated_at: "2016-05-23 19:05:58">
>> user.email = "mhartl@example.net"
=> "mhartl@example.net"
>> user.save
=> true
```

変更をデータベースに保存するために最後にsaveを実行する必要がある。

もう1つの方法は`update_attributes`を使うケース。


```rb
>> user.update_attributes(name: "The Dude", email: "dude@abides.org")
=> true
>> user.name
=> "The Dude"
>> user.email
=> "dude@abides.org"
```

このメソッドは、属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行う。

##6.2 ユーザーを検証する
`name`と`email`にあらゆる文字列を許すのは避けるべきなので、これらの属性値に、何らかの制約を与える。検証 (Validation) という機能を通して、こういった制約を課すことができるようになっている。

###6.2.1 有効性を検証する
まず有効なモデルのオブジェクトを作成し、その属性のうちの1つを有効でない属性に意図的に変更します。そして、バリデーションで失敗するかどうかをテストする、といった方針で進めていく。

###6.2.2 存在性を検証する
最も基本的なバリデーションは「存在性 (Presence)」。

`user_test.rb`に`name`属性の存在性に関するテストを追記。

`user.rb`に、空白が入らないようにバリデーションを追加する。

```rb
class User < ApplicationRecord
 validates :name, presence: true
end
```

そうしたら、空白を弾くように。

```rb
$ rails console --sandbox
>> user = User.new(name: "", email: "mhartl@example.com")
>> user.valid?
=> false
```

`user.errors.full_messages`で、なんでエラーになったのか見れる。

`email`でも同様のことをやる。

###6.2.3 長さを検証する
これでUserモデル上に名前を持つことを強制されるようになった。名前の長さにも制限を与える。

名前は50文字まで、emailは255文字まで。

`user_test.rb`に追記。

```rb
test "name should not be too long" do
  @user.name = "a" * 51
  assert_not @user.valid?
end

test "email should not be too long" do
  @user.email = "a" * 244 + "@example.com"
  assert_not @user.valid?
end
```


`user.rb`長さのバリデーション追加。

```rb
class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }
end
```

###6.2.4 フォーマットを検証する
有効なメールアドレスかどうかの判定をする。

メールアドレスのバリデーションは扱いが難しく、エラーが発生しやすい。

`user_test.rb`で有効なメールフォーマットをテストする。


```rb
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
```

以下のコードでどのメールアドレスでテスト失敗したのか特定できるように。

```rb
assert @user.valid?, "#{valid_address.inspect} should be valid"
```

次は無効性についてテスト。フォーマット検証には、`format`を使う。

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

メールフォーマットを正規表現で検証する。

```rb
class User < ApplicationRecord
  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 }
end
```

正規表現`VALID_EMAIL_REGEX`は定数。大文字で始まる名前はRubyでは定数を意味する。残る制約は、メールアドレスが一意であることを強制することだけとなった。

###6.2.5 一意性を検証する
メールアドレスの一意性を強制するために (ユーザー名として使うために)、`validates`メソッドの`:unique`オプションを使う。

一意性のテストのためには、メモリ上だけではなく、実際にレコードをデータベースに登録する必要がある。

`user_test.rb`に重複するメールアドレス拒否のテスト。


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


`@user`と同じメールアドレスのユーザーは作成できないことを、`@user.dup`を使ってテストしている。

大文字小文字を区別しない一意性のテストも必要。

```rb
duplicate_user.email = @user.email.upcase
```

このコードを上のコードに追加。

ここまでやったことは、あくまでもアプリケーションレベルで一意性を強制しただけであるため、素早く2回連続でクリックしたなどのデータベースレベルでは一意性を強制したことにならない。

emailインデックスを追加すると、データモデリングの変更が必要になる。
今回は、既に存在するモデルに構造を追加するので、次のようにmigrationジェネレーターを使ってマイグレーションを直接作成する必要がある。

```
$ rails generate migration add_index_to_users_email
```

メールアドレスの一意性のマイグレーションは未定義なので、`[timestamp]_add_index_to_users_email.rb`に次のように定義。

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

`users`テーブルの`email`カラムにインデックスを追加するために`add_index`というRailsのメソッドを使っている。

最後に、データベースをマイグレート。

```rb
$ rails db:migrate
```

データベースによって、メールアドレスの大文字と小文字を区別せずにバリデーションが効かないという問題がある。

これを、`user.rb`で`before_save`というコールバックで解決する。

```rb
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
```

##6.3 セキュアなパスワードを追加する
最後の砦である「セキュアなパスワード」に取り掛かる。セキュアパスワードという手法では、各ユーザーにパスワードとパスワードの確認を入力させ、それをハッシュ化したものをデータベースに保存する。

今回の「ハッシュ化」は、ハッシュ関数を使って、入力されたデータを不可逆なデータにする処理を指す。

ハッシュ化することで、仮にデータベースの内容が盗まれたり覗き見されるようなことがあっても、パスワードの安全性が保たれる。

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

セキュアなパスワードの実装は、`has_secure_password`というRailsのメソッドを呼び出すだけでほぼ終わるっぽい。

```rb
class User < ApplicationRecord
  .
  .
  .
  has_secure_password
end
```

このメソッドを追加すると、次のような機能が使えるようになる。

・セキュアにハッシュ化したパスワードを、データベース内の`password_digest`という属性に保存できるようになる。
・2つのペアの仮想的な属性 (`password`と`password_confirmation`) が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。
・`authenticate`メソッドが使えるようになる (引数の文字列がパスワードと一致すると`User`オブジェクトを、間違っていると`false`を返すメソッド) 。


`has_secure_password`機能を使えるようにするには、モデル内に`password_digest`という属性が含まれていることが条件となる。


今回はUserモデルにそれを追加する。そのために以下のコマンドを実行。

```rb
$ rails generate migration add_password_digest_to_users password_digest:string
```

上のコマンドでは`password_digest:string`という引数を与えて、今回必要になる属性名と型情報を渡している。

また、`has_secure_password`を使ってパスワードをハッシュ化するためには、最先端のハッシュ関数である`bcrypt`が必要になる。`bcrypt` gemを`Gemfile`に追加。


###6.3.2 ユーザーがセキュアなパスワードを持っている

```rb
$ rails test
```

を実行してもREDに。

セットアップメソッドでいろいろと情報が足りてないらしい。
`user_test.rb`にパスワードとパスワード確認を追加。

```rb
 def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end
```

これでテストはGREENに。

###6.3.3 パスワードの最小文字数
今回は簡潔にパスワードが空でないことと最小文字数 (6文字) の2つを設定する。


パスワードの長さが6文字以上であることを検証するテスト。

```rb
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
```

また、空のパスワードを入力させないために、存在性のバリデーションも追加。

###6.3.4 ユーザーの作成と認証
以上でUserモデルの基本部分が完了。次章でユーザー情報表示ページを作成するときに備えて、データベースに新規ユーザーを1人作成しておく。

今度はrails consoleでサンドボックスでないパターン。

```rb
>> User.create(name: "Michael Hartl", email: "mhartl@example.com",
?>             password: "foobar", password_confirmation: "foobar")
```

うまくデータベースに保存されていました。

![スクリーンショット 2022-02-13 16.16.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2210462/d9b29377-aa33-59af-7c39-cb3d12753bc1.png)



## 感想
この章から難易度がまた一段と上がった気がします。たまにエラーも出ましたが無事解決できました。
半年前くらいにインターンを始めようとしたときに「バックエンドとフロントエンド、どっちに興味があるの？」と聞かれた時、何もわかんなかったのが、この章で、バックエンドの人ってこんな感じのことやってんだろうなーとなんとなくですがわかった気がします。

## 疑問・単語集
*`rails test`を実行しようとしたら

```rb
Migrations are pending. To resolve this issue, run:
        bin/rails db:migrate RAILS_ENV=test
```

とエラーになってしまいました。以下のコードを実行したらうまくいきました。

```rb
$RAILS_ENV=test rails db:drop
$RAILS_ENV=test rails db:create
$RAILS_ENV=test rails db:migrate
```