Rails Tutorialをやってみよう07

第6章 ユーザーのモデルを作成する

6.1 Userモデル

第6章 ユーザーのモデルを作成する | Rails チュートリアル


Gitでバージョン管理を行なっているのであれば、このタイミングでユーザーをモデリングするためのトピックブランチを作成しておいてください。

$ git checkout master
$ git checkout -b modeling-users

gitがどのように記録されているかわからなくなったら

$ git log

でgitの記録を見てみましょう。

ちなみにgit logは、"q"で終了して、元のコマンドラインに戻ることができます。

では、引き続き、ユーザーモデルを作成していきます。
userモデルを作成していきます。

userのデーターベースをイメージします。
http://railstutorial.jp/books/4.2/images/figures/users_table.png

userのデータモデルのスケッチです。
http://railstutorial.jp/books/4.2/images/figures/user_model_sketch.png

まず、userのコントローラーを作成します。

$ rails generate controller Users new

コントローラーの作成には、
rails generate controller <大文字かつ複数形> new
というメソッドを使います。

次に、userのモデルを作成します。
このときに、データモデルのスケッチの属性を付属させて作成します。
id属性は自動で作成されます。

$ rails generate model User name:string email:string

モデルの作成には、
rails generate model <大文字かつ単数形> <属性名>:<データ形式> <属性名>:<データ形式>
というメソッドを使います。

generateコマンドの結果のひとつとして、マイグレーションと呼ばれる新しいファイルが生成されます。マイグレーションは、データベースの構造をインクリメンタルに変更する手段を提供します。

db/migrate/[timestamp]_create_users.rbが作成されています。
t.timestamps null: falseも同時に作成されています。
このt.timestamps null: falseがあることで、
created_atとupdated_atが自動作成されます。
この2つはユーザーが作成または更新された時の時刻を記録するタイムスタンプを記録します。

マイグレーションは、以下のようにrakeコマンドを使って実行することができます。

$ bundle exec rake db:migrate

SQliteのデータベース構造を可視化する方法

SQLiteBrowserをインストールします。
DB Browser for SQLite

macでhomebrewを使ってインストールする場合は、以下のbrew caskコマンドでインストールできます。

$ brew cask install sqlitebrowser

RubyMineを使っている場合は、このブログを参考にしてください。mi813.hatenablog.com

6.1.2 modelファイル
6.1.3 ユーザーオブジェクトを作成する
$ rails console --sandbox

sandboxモードでrailsを起動します。
sandboxモードでrails consoleを起動すると、
データ・モデルを変更せずに調べることができます。
rails consoleは、"control + d"にて終了できます。

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

nameとemail属性が期待通り設定されていることがわかります。

>> user.valid?
true
>> user.save

   (0.1ms)  SAVEPOINT active_record_1
  SQL (3.2ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Michael Hartl"], ["email", "mhartl@example.com"], ["created_at", "2015-11-23 03:36:30.051170"], ["updated_at", "2015-11-23 03:36:30.051170"]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true>

saveメソッドは、成功すればtrueを、失敗すればfalseを返します (現状では、保存はすべて成功するはずです。

>> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">

f:id:mi813:20151123123937p:plain

以下の3つのタイトルでは、データ・モデルの変更をrails consoleにて編集するやり方が記載されています。
6.1.3 ユーザーオブジェクトを作成する

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

6.1.5 ユーザーオブジェクトを更新する


取りあえず、テストは飛ばして次に進みます。

Ruby正規表現を試すことが出来るWebサービスwww.rubular.com


メールフォーマットを正規表現で検証する (GREEN app/models/user.rb)

class User < ActiveRecord::Base
  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

既に存在するモデルに構造を追加する

$rails neerate migration add_index_to_user_email

f:id:mi813:20151123130043p:plain


メールアドレスの一意性を強制するためのマイグレーション db/migrate/[timestamp]_add_index_to_users_email.rb

class AddIndexToUsersEmail < ActiveRecord::Migration
  def change
    add_index :users, :email, unique: true
  end
end
$ bundle exec rake db:migrate

Userのデフォルトfixture RED
test/fixtures/users.yml

# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/
# FixtureSet.html

one:
  name: MyString
  email: MyString

two:
  name: MyString
  email: MyString

上記のファイルを空にしておきます。
空のfixtureファイル GREEN test/fixtures/users.yml

# empty


email属性を小文字に変換してメールアドレスの一意性を保証する GREEN app/models/user.rb

class User < ActiveRecord::Base
  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.1 ハッシュ化されたパスワード
セキュアなパスワードの実装は、has_secure_passwordというRailsメソッドを呼び出すだけでほとんど終わってしまいます。このメソッドは、Userモデルで次のように呼び出せます。
app/models/user.rb

class User < ActiveRecord::Base
  .
  .
  .
  has_secure_password
end

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

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

この魔術的なhas_secure_password機能を使えるようにするには、1つだけ条件があります。それは、モデル内にpassword_digestという属性が含まれていることです。

http://railstutorial.jp/books/4.2/images/figures/user_model_password_digest_3rd_edition.png


http://railstutorial.jp/books/4.2/images/figures/user_model_password_digest_3rd_edition.png

データモデルにするために、まずはpassword_digestカラム用の適切なマイグレーションを生成します。マイグレーション名は自由に指定できますが、上のように末尾をto_usersにしておくことをお勧めします。こうしておくと、usersテーブルにカラムを追加するマイグレーションRailsによって自動的に作成されるからです。add_password_digest_to_usersというマイグレーションファイルを生成するためには、以下のコマンドを実行します。

$ rails generate migration \
add_password_digest_to_users password_digest:string

f:id:mi813:20151123131926p:plain

usersテーブルにpassword_digestカラムを追加するマイグレーション db/migrate/[timestamp]_add_password_digest_to_users.rb

class AddPasswordDigestToUsers < ActiveRecord::Migration
  def change
    add_column :users, :password_digest, :string
  end
end
$ bundle exec rake db:migrate

has_secure_passwordを使ってパスワードをハッシュ化するためには、最先端のハッシュ関数であるbcryptが必要になります。パスワードを適切にハッシュ化することで、たとえ攻撃者によってデータベースからパスワードが漏れてしまった場合でも、Webサイトにログインされないようにできます。サンプルアプリケーションでbcryptを使用するために、bcrypt-ruby gemをGemfileに追加します

bcryptをtGemfileに追加する

source 'https://rubygems.org'

gem 'rails',                '4.2.2'
gem 'bcrypt',               '3.1.7'
.
.
.
6.3.2 ユーザーがセキュアなパスワードを持っている

Userモデルにpassword_digest属性を追加し、Gemfileにbcryptを追加したことで、ようやくUserモデル内でhas_secure_passwordが使えるようになりました。

Userモデルにhas_secure_passwordを追加する RED app/models/user.rb

class User < ActiveRecord::Base
  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
bundle exec rake test


空のパスワードを入力させないために、存在性のバリデーション (6.2.2) も一緒に追加します。結果として、Userモデルのコードはリスト6.39のようになります。(has_secure_passwordメソッドは存在性のバリデーションもしてくれるのですが、これは新しくレコードが追加されたときだけに適用されます。したがって、たとえばユーザーが " " (6文字分の空白スペース) といった文字列をパスワード欄に入力して更新しようとすると、バリデーションが適用されずに更新されてしまう問題が発生してしまいます。)

セキュアパスワードの完全な実装 GREEN app/models/user.rb

class User < ActiveRecord::Base
  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.3.4 ユーザーの作成と認証

ユーザーモデルの基本部分が完成しました。
データベースに新規ユーザーを1人作成しましょう。

$ 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: "2014-09-11 14:26:42", updated_at: "2014-09-11 14:26:42",
password_digest: "$2a$10$sLcMI2f8VglgirzjSJOln.Fv9NdLMbqmR4rdTWIXY1G...">

f:id:mi813:20151123133113p:plain

RubyMineのデータベースで確認してみると、rails consoleで作成したデータが記録されているのが確認できます。
f:id:mi813:20151123133329p:plain


コンソールに戻ってpassword_digest属性を参照してみると、has_secure_passwordの効果を確認できます。

>> user = User.find_by(email: "mhartl@example.com")
>> user.password_digest
=> "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQWITUYlG3XVy"