チュートリアル6章

Userモデルを生成する
※コントローラ名には複数形を使い、モデル名には単数形を用いる

```ruby:ubutu
rails generate model User name:string email:string
```
このコマンドで作成されたマイグレーションファイル

```ruby:db/migrate/[timestamp]_create_users.rb
 class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end
```
マイグレーとする

```ruby:ubuntu
$ rails db:migrate
```
ユーザ作成
このオプションは“ロールバック” (取り消し) してくれるものです。（保存されない）

```ruby:ubuntu
$ rails console --sandbox

>>user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
>> user.save

trueが返ってくれば成功、falseが失敗

>> user.name
>> user.email
>> user.updated_at
>> User.create(name: "A Nother", email: "another@example.org")
>> foo = User.create(name: "Foo", email: "foo@bar.com")
>> foo.destroy
>> User.find(1)
>> User.find_by(email: "mhartl@example.com")
>> User.first
>> User.all

変更をデータベースに保存するために最後にsaveを実行する必要があることを忘れないでください。
例えば▼
>> user.email
=> "mhartl@example.net"
>> user.email = "foo@bar.com"
=> "foo@bar.com"
>> user.reload.email
=> "mhartl@example.net"

リロードすると変更が取り消される

更新する方法は他にも▼
>> user.update_attributes(name: "The Dude", email: "dude@abides.org")
>> user.update_attribute(:name, "El Duderino")

```

有効性を検証する
有効なユーザかテストする

```ruby:test/models/user_test.rb
 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
```
Userモデルにはまだバリデーションがないので、このテストは成功する

基本的なバリデーションは「存在性 (Presence)」です。これは単に、渡された属性が存在することを検証

```ruby:test/models/user_test.rb
 require 'test_helper'
.
.
.

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

```
name属性の存在を検査する方法は、validates メソッドにpresence: trueという引数を与えて使うこと

```ruby:app/models/user.rb
 class User < ApplicationRecord
  validates :name, presence: true
end
```
ここでちゃんとバリデーションがかかっているか確認

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

falseが表示されれば、失敗しているということでバリデーションが効いている証拠。（名前無しだから）

>> user.errors.full_messages
=> ["Name can't be blank"]
このコマンドでエラーメッセージを確認

>> user.save
=> false
ここで保存しようとしてもできない
```

これでテストは通る

メールも追加する

```ruby:test/models/user_test.rb
.
.
.

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

```ruby:app/models/user.rb
 class User < ApplicationRecord
  validates :name,  presence: true
  validates :email, presence: true
end
```
これでテストも通ります

長さを検証する
名前もメールも長さの縛りを

```ruby:test/models/user_test.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
end
```
```ruby:app/models/user.rb
 class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }
end
```
これでテストが通る

フォーマットを検証する
name属性の検証には、空文字でない、名前が51文字未満であるという最小限の制約しか与えていませんでした。email属性の場合は、有効なメールアドレスかどうかを判定するために、もっと厳重な要求を満たさなければなりません。

文字列の配列を簡単に作れる%w[]という便利なテクニックを知っておくと良いでしょう。

```ruby:ubuntu
>> %w[foo bar baz]
=> ["foo", "bar", "baz"]
>> addresses = %w[USER@foo.COM THE_US-ER@foo.bar.org first.last@foo.jp]
=> ["USER@foo.COM", "THE_US-ER@foo.bar.org", "first.last@foo.jp"]
>> addresses.each do |address|
?>   puts address
>> end
USER@foo.COM
THE_US-ER@foo.bar.org
first.last@foo.jp
```
無効なメールアドレスを保存できないように実装していく、まずはテストから

```ruby:test/models/user_test.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
end
```
上記のコードでどのメールアドレスでテストが失敗したのかを特定できるようになります。▼コード

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

エラーメッセージのカスタマイズテスト

```ruby:test/models/user_test.rb
  .
  .
  .
  test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                           foo@bar_baz.com foo@bar+baz.com]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  end
end
```
有効なメールアドレスだけにマッチして、無効なメールアドレスにはマッチしない正規表現を組み立てる必要があります。
堅牢であることが実戦で保証されている正規表現を採用します。これが、その正規表現です。

```ruby:
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
```
Rubularという対話的に正規表現を試せるWebサイトがあります ので、そちらを参考に。

定数を使って実装する

```ruby:app/models/user.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
```
これでテストが通る。

メールアドレスの一意性を強制するために (ユーザー名として使うために)、validatesメソッドの:uniqueオプションを使います。
まずはテストから

```ruby:test/models/user_test.rb
  .
  .
  .
  test "email addresses should be unique" do
    duplicate_user = @user.dup
    @user.save
    assert_not duplicate_user.valid?
  end
end
```
emailのバリデーションにuniqueness: trueというオプションを追加します

```ruby:app/models/user.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 },
                    uniqueness: true　←ここ
end
```

小文字・大文字を区別しないでテストする

```ruby:test/models/user_test.rb
 require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email addresses should be unique" do
    duplicate_user = @user.dup
    duplicate_user.email = @user.email.upcase　←ここ追加
    @user.save
    assert_not duplicate_user.valid?
  end
end
```
一意性検証でcase_sensitiveオプションにfalseを指定することで、大文字小文字の差を無視するすることが可能です。

```ruby:app/models/user.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 },
                    uniqueness: { case_sensitive: false }　←ここ
end
```
1つの問題が残っています。素早く2回クリックすると２つの同じアドレスが登録される。
改善していく

```ruby:ubuntu
$ rails generate migration add_index_to_users_email

```
```ruby:db/migrate/[timestamp]_add_index_to_users_email.rb
 class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
  def change
    add_index :users, :email, unique: true
  end
end
```
```ruby:ubuntu
$ rails db:migrate
```
DBのテストデータが入っているのでそれを消す（fixtures内で一意性の制限が保たれていないため「同じアドレスのテストデータだった」）

```ruby:test/fixtures/users.yml
 # 空にする (既存のコードは削除する)
```
これでテストは通る

これで1つの問題が解決されましたが、一意性を保証するためには、もう1つやらなければならないことがあります。
「データベースに保存される直前にすべての文字列を小文字に変換する」という対策（before_saveというコールバックを使います。）

```ruby:app/models/user.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
```

セキュアなパスワードを追加する
ハッシュ化したものをデータベースに保存します。

```ruby:ubuntu
$ rails generate migration add_password_digest_to_users password_digest:string

$ rails db:migrate
```
has_secure_passwordを使ってパスワードをハッシュ化するためには、bcryptが必要

```ruby:Gemfile
source 'https://rubygems.org'

gem 'rails',          '5.1.6'
gem 'bcrypt',         '3.1.12'
.
.
.
```
```ruby:ubuntu
$ bundle install
```
```ruby:app/models/user.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 }
  has_secure_password　←ここ追加
end
```
テストユーザにパスワードを追加する

```ruby:test/models/user_test.rb
 require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")　←ここ↑のカンマに注意
  end
  .
  .
  .
end
```

パスワードを簡単に当てられないようにするために、パスワードの最小文字数を設定
多重代入 (Multiple Assignment) を使っていることに注目

```ruby:test/models/user_test.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
end
```
```ruby:app/models/user.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 }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }　←ここ
end
```

ユーザ作成と認証

```ruby:ubuntu
$ rails console
>> User.create(name: "Michael Hartl", email: "mhartl@example.com",
?>             password: "foobar", password_confirmation: "foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 20:36:46", updated_at: "2016-05-23 20:36:46",
password_digest: "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3...">
```
確認

```ruby:
>> user = User.find_by(email: "mhartl@example.com")
>> user.password_digest
=> "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3yOcVFzb6oK"
＃ハッシュかされているか

>> user.authenticate("not_the_right_password")
false
>> user.authenticate("foobaz")
false
＃間違ったパスワードを入力するとfalseになるか
```
ここで一旦heroku等のプッシュを

```ruby:ubuntu
$ git add -A
$ git commit -m "Make a basic User model (including secure passwords)"
$ git push

$ rails test
$ git push heroku
$ heroku run rails db:migrate

herokuで上手くできたかは、本番環境のコンソールに接続することで確認できます。

$ heroku run rails console --sandbox
>> User.create(name: "Michael Hartl", email: "michael@example.com",
?>             password: "foobar", password_confirmation: "foobar")
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.com",
created_at: "2016-05-23 20:54:41", updated_at: "2016-05-23 20:54:41",
password_digest: "$2a$10$74xFguZRoTZBXTUqs1FjpOf3OoLhrvgxC2wlohtTEcH...">
```

6章終わり