rails-tutorial第6章

###そもそもなんでmodelが必要なの？
・永続的な情報を保存したいけど普通の変数だと実現できない。
・永続的な情報の保存にはDBを使わないといけない。
・ActiveRecordを使うと変数のようにDBに保存をすることができる。
・モデルはActiveRecordを継承したApplicationRecordを継承している。
・つまり、RubyとDBの橋渡しをしてくれるから。

###Userモデルの作成

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

モデルの作成時はUserのように単数形で書く。
name:string はnameカラムでデータ形式はstringだよーって意味。

ちなみに id:integer created_at:datetime updated_at:datetimeはデフォルトで入っている。

このコマンドにより、テストファイルやマイグレーションファイルが作成される。
以下は作成されたマイグレーションファイル

```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
```
これで $ rails db:migrateをすると、Userリソースを保存するためのテーブルが作成される。
rails g modelコマンドだけではテーブルは作成されないから注意が必要。

###$ rails db:rollback
このコマンドによってrails db:migrateの更新を戻すことができる。


###modelファイルを見てみよう

```app/models/user.rb
class User < ApplicationRecord
end
```
UserクラスがApplicationRecordを継承していることがわかる。
この継承によって、Userクラスのインスタンスにsaveメソッドが使えるようになり、DBに保存することができる。（findメソッドとかallメソッドとか色々使える。）またマジックカラム(id ,created_at, updated_at)はDBに保存されて初めて値が埋まる。

###User.create
User.create(name: "A Nother", email: "another@example.org")
user.new, user.saveなどが面倒なときは、User.createでいきなりDBベースに保存することができる。
また、

```
u = User.create(name: "A Nother", email: "another@example.org")
```
createメソッドはUserインスタンスを返すので、上記でDBに保存し、かつローカル変数uに代入することができる。

###findメソッドとfind_byメソッドの違い

findメソッドは見つからなかったときに例外を出すのに対して、find_byは見つからなかったときにnilを返す。

###update_attributes
update_attributesはcreateと似ていて、更新のショートカットを可能にする。

```
>> user.update_attributes(name: "The Dude", email: "dude@abides.org")
=> true
```
このように1行で書ける。

###update_attribute
update_attributeは二つの引数を使って更新する。

```
>> user.update_attribute(:name, "El Duderino")
=> true
```
update_attributeはvalidationを介さずにDBに登録をすることができるという特徴がある。


##ユーザーを検証する

モデルの場合、テストコードを先に書いて、あとでvalidationを書いていったほうが早い

###モデルのテストを見ていこう

```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
```
上記のsetupメソッドはその下の　test do endが実行される直前に実行されるという特徴がある。

また、modelだけテストをしたいときは、

$ rails test:models

とすると良い。
このテストの場合、バリデーションが設定されてないのでテストは通る。

では次のテストはどうだろうか？

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

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

この場合、user.nameが空文字の際に、@userはnot validじゃなきゃいけないよね？っていうテスト。
この場合、バリデーションが設定されていないので、テストは失敗する。

###validationを設定しよう

```app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
end
```
validationはmodelのファイルに書く。

###次は長さを検証してみよう

```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 "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
```
@user.nameはsetupメソッドでテスト直前に定義されるから問題ない。
"a" * 51とすることで、aを50回打つなどの面倒を避ける。

この場合、バリデーションを定義していないのでテストは失敗してしまう。
なので、

```app/models/user.rb
class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }
end
```
とすることで、文字数のバリデーションを定義できる。


###適切なメールアドレスかチェックする

```test/models/user_test.rb

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  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
  
  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
```
現在、メールアドレスは存在性と長さしかバリデーションを設定してないので、2つ目のテストで失敗してしまう。

なので、正規表現でバリデーションを設定しよう。

```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
```
formatオプションで設定すると、その型通りじゃないとvalidにならないようになる。
ちなみにVALID_EMAIL_REGEXは定数である。


###一意性を知ろう。
同じメールアドレスがDB内に複数あると困る。

テストを見てみよう

```test/models/user_test.rb

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
    @user.save
    assert_not duplicate_user.valid?
  end
end
```
dupは複製するメソッド。
@userがDBに保存された状態で、duplicate_userという複製されたインスタンスはvalidですか？というテストである。このテストは落ちる。

じゃあ、一意性を担保するには？

```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
```
このようにuniqueness: trueとすると一意性が担保される確率が上がる。

まだ、このままだとメールアドレスの大文字小文字を区別できない。
そのため、

```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
```
最後のテスト、@userがDBに登録され、またメールアドレスが大文字になったインスタンスはnot validになるか？というテストなのだが、落ちてしまう。これはuniqueness: trueがデフォルトで大文字小文字を区別するようになっているためだ。

そこで

```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
```
uniqueness: trueからuniqueness: { case_sensitive: false }に変えてあげる。
case_sensitive: falseは大文字小文字を区別しなくてもいいよーって意味。
これで大文字小文字関係なく、スペルが同じアドレスは登録できなくなり、一意性が担保されるようになる?

いや、まだだ。

全く同じメールアドレスが全く同じ時間に登録されたらどうなるか？
なんと、どちらも登録されてしまう。。。

なので、DBにも一意性を担保してもらうようにお願いをしなければいけない。
具体的には片方のアドレスが登録されるまで次の登録を待ってもらう。

その際はマイグレーションファイルを使う

```
$ rails generate migration add_index_to_users_email
```

できたマイグレーションファイルに

```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
```
このように書く。
unique: trueは一意性をDB側でも担保してくださいねーってお願い。

で、　rails db:migrateをする。

これでOKか？
いや、このままだと、テストが全て通らなくなってしまう。


これは以下が原因となっている。

```test/fixtures/users.yml
one:
  name: MyString
  email: MyString

two:
  name: MyString
  email: MyString
```

テスト用データベースの中に、MyStringというメールアドレスが2つあり、一意性に引っかかってしまったのだ。

解決策としては、このファイルの内容を全て消してあげれば良い。


##セキュアなパスワードを設定する。

まずは散らばった文字列のパスワードのハッシュ値を入れる場所を作る。

$ rails generate migration add_password_digest_to_users password_digest:string

ちなみにマイグレーションファイル名を　add_カラム名_to_テーブル名とするとRailsが勝手に判断して以下のようにコーディングしてくれる。

```db/migrate/[timestamp]_add_password_digest_to_users.rb
class AddPasswordDigestToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :password_digest, :string
  end
end
```
あとは、上のファイルが自分のやりたいことと一致するか確認して、
$ rails db:migrateを実行する。

これでパスワードのハッシュ値を保存する場所ができた。

###bcrypt
次に、パスワードをハッシュ化するためのgem bcryptをインストールする。
gem 'bcrypt',         '3.1.12'
bundle

そして、モデルファイルに has_secure_passwordと書けば完了。以下

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

ただ、この状態だとテストが落ちてしまう。
理由はtestファイルのsetupメソッドにpassword属性とpassword_confirmation属性の値を指定していないためらしい。

ちなみにpasswordとpassword_confirmationは仮想的な属性で、実際にDBに保存されるのはpassword_digestだけ。

そこで、

```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
```
というように、仮想的な属性を指定してあげる。これで一応テストは通る。

あとは、パスワードの文字数を6文字以上とかにするバリデーションを設定してあげる。

```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
```
パスワードの存在性と長さ6文字以上を指定。

これで一応セキュアなパスワードは実装完了。

###パスワードの認証について

has_secure_passwordをUserモデルに追加したことで、そのオブジェクト内でauthenticate()メソッドが使えるようになっています。このメソッドは、引数に渡された文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較します。


マイグレーションファイルを色々設定したあとは、
$ heroku run rails db:migrate
を忘れずに！！！