学習記録 16日目
16日目の学習記録をまとめていきます。
- 学習計画
- 学習内容
- Ruby on Railsチュートリアル 6章
- Ruby on Railsチュートリアル 7章
- 7.1 ユーザーを表示する
- 7.1.1 デバックとRails環境
- 演習
- 7.1.2 Userリソース
- 演習
- 7.1.3 debuggerメソッド
- 演習
- 7.1.4 Gravatar画像とサイドバー
- 演習
- 7.2.1 form_withを使用する
- 演習
- 7.2.2 フォームHTML
- 7.3.2 Strong Parameters
- 7.3.3 エラーメッセージ
- 演習
- 7.3.4 失敗時のテスト
- 演習
- 7.4.1 登録フォームの完成
- 演習
- 7.4.2 flash
- 演習
- 7.4.3 実際のユーザー登録
- 演習
- 7.4.4 成功時のテスト
- 演習
- 7.5 プロのデプロイ
- 7.5.1 本番環境でのSSL
- 7.5.2 本番環境用のWebサーバー
- 7.5.3 本番データベースを設定する
- 7.5.4 本番環境へのデプロイ
- 演習
- 本日の総括
学習内容
Ruby on Railsチュートリアル 6章
6.1 Userモデル
- トピックブランチを作成します。
$ git checkout -b modeling-users
- rails generate modelコマンドを使用してUserモデルを作成します。Userモデルにはname, emailの2つのカラムがあります。どちらのカラムもデータ型はstringにします。
$ rails generate model User name:string email:string Running via Spring preloader in process 73153 invoke active_record create db/migrate/20201021023859_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml
- 作成されたマイグレーションは以下のようになっています。
# db/migrate/20201021023859_create_users.rb class CreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.string :name t.string :email t.timestamps end end end
- create_tableメソッドはブロックを受け取りブロック内で各カラムを作成します。
- t.timestampsは特別なコマンドでcreated_atとupdated_atという2つのマジックカラムを作成します。
- マイグレーションの適用は以下のコマンドを使用します。
$ rails db:migrate
- マイグレーションを適用するとdb/development.sqlite3というファイルが作成されます。DB Browser for SQLiteというソフトを使ってデータベースの構造を確認してみます。
演習
1. Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造(スキーマ(schema)と呼びます)を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル(リスト 6.2)の内容を比べてみてください。
# db/migrate/20201021023859_create_users.rb class CreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.string :name t.string :email t.timestamps end end end
# db/schema.rb ActiveRecord::Schema.define(version: 2020_10_21_023859) do create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end end
- t.timestampがcreated_atとupdated_atに変わっている以外同じ内容になっている。
2. ほぼすべてのマイグレーションは、元に戻すことが可能です(少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック(rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。上のコマンドを実行後、db/schema.rbの内容を調べてみて、ロールバックが成功したかどうか確認してみてください。(以下略)
- rollback実行してみます。
$ rails db:rollback
- schema.rbの内容を見てみます。
# db/schema.rb ActiveRecord::Schema.define(version: 2020_10_21_023859) do end
- db:migrateする前の状態に戻っています。
3. もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。
$ rails db:migrate
- schema.rbの内容が元に戻りました。
# db/schema.rb ActiveRecord::Schema.define(version: 2020_10_21_023859) do create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end end
6.1.2 modelファイル
演習
1. Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください(ヒント: 4.4.4で紹介したテクニックを使ってみてください)。
2. 同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。
- railsコンソールを使って調べていきます。
$ rails c > user = User.new > user # => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> > user.class.superclass # => ApplicationRecord(abstract) > user.class.superclass.superclass # => ActiveRecord::Base
6.1.3 ユーザーオブジェクト
- Railsコンソール上でデータベースを更新させないために--sandboxオプションを使用してコンソールを起動する。
- モデルのインスタンスが登録できる状態かどうかはvalid?メソッドで確認出来る。
- モデルのインスタンスをデータベースに登録するにはsaveメソッドを使用します。登録に成功すればtrueを返します。
$ rails c --sandbox > user = User.new(name: "Michael Hartl", email: "michael@example.com") > user.valid? # => true > user.save (0.1ms) SAVEPOINT active_record_1 User Create (0.5ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Michael Hartl"], ["email", "michael@example.com"], ["created_at", "2020-10-21 03:06:47.159996"], ["updated_at", "2020-10-21 03:06:47.159996"]] (0.1ms) RELEASE SAVEPOINT active_record_1 => true
- モデルのインスタンスはドット記法を用いて属性にアクセスできる。
> user.name # => "Michael Hartl" > user.email # => "michael@example.com" > user.updated_at # => Wed, 21 Oct 2020 03:06:47 UTC +00:00
- モデルの生成と保存を一気に行うにはcreateメソッドを使用します。
> User.create(name: "A Nother", email: "another@example.org") (0.1ms) SAVEPOINT active_record_1 User Create (0.1ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "A Nother"], ["email", "another@example.org"], ["created_at", "2020-10-21 03:11:03.433361"], ["updated_at", "2020-10-21 03:11:03.433361"]] (0.0ms) RELEASE SAVEPOINT active_record_1 # => #<User id: 2, name: "A Nother", email: "another@example.org", created_at: "2020-10-21 03:11:03", updated_at: "2020-10-21 03:11:03"> > foo = User.create(name: "Foo", email: "foo@bar.com") (0.1ms) SAVEPOINT active_record_1 User Create (0.1ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Foo"], ["email", "foo@bar.com"], ["created_at", "2020-10-21 03:11:12.465011"], ["updated_at", "2020-10-21 03:11:12.465011"]] (0.0ms) RELEASE SAVEPOINT active_record_1 > foo # => #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2020-10-21 03:11:12", updated_at: "2020-10-21 03:11:12">
- destroyメソッドを使用するとデータベース上からデータを削除出来ます。データベース上から削除なのでインスタンスにはデータは入ったままになります。
> foo.destroy (0.1ms) SAVEPOINT active_record_1 User Destroy (0.1ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 3]] (0.1ms) RELEASE SAVEPOINT active_record_1 # => #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2020-10-21 03:11:12", updated_at: "2020-10-21 03:11:12"> > foo # => #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2020-10-21 03:11:12", updated_at: "2020-10-21 03:11:12">
演習
1. user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。
> user.name.class # => String > user.email.class # => String
2. created_atとupdated_atは、どのクラスのインスタンスでしょうか?
> user.created_at.class # => ActiveSupport::TimeWithZone > user.updated_at.class # => ActiveSupport::TimeWithZone
6.1.4 ユーザーオブジェクトを検索する
- モデルのデータベース上の登録情報を確認するにはfindメソッドを使用する。id番号で検索できる。id=3は先ほどdestroyしたので検索に引っかからない。
> User.find(1) # => #<User id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: "2020-10-21 03:06:47", updated_at: "2020-10-21 03:06:47"> > User.find(3) ActiveRecord::RecordNotFound (Couldn't find User with 'id'=3)
- 他の検索方法としてfind_byメソッドがある。カラム名とデータ内容で検索できる。
> User.find_by(email: "michael@example.com") # => #<User id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: "2020-10-21 03:06:47", updated_at: "2020-10-21 03:06:47">
- 先頭の行や末尾の行を検索するにはfirst, lastメソッドを使用する。
> User.first # => #<User id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: "2020-10-21 03:06:47", updated_at: "2020-10-21 03:06:47"> > User.last # => #<User id: 2, name: "A Nother", email: "another@example.org", created_at: "2020-10-21 03:11:03", updated_at: "2020-10-21 03:11:03">
- 全てのデータを出力するにはallメソッドを使用します。
> User.all # => #<ActiveRecord::Relation [#<User id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: "2020-10-21 03:06:47", updated_at: "2020-10-21 03:06:47">, #<User id: 2, name: "A Nother", email: "another@example.org", created_at: "2020-10-21 03:11:03", updated_at: "2020-10-21 03:11:03">]>
演習
1. nameを使ってユーザーオブジェクトを検索してみてください。また、find_by_nameメソッドが使えることも確認してみてください(古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。
> User.find_by_name("Michael Hartl") # => #<User id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: "2020-10-21 03:06:47", updated_at: "2020-10-21 03:06:47">
2. 実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。
3. User.allに対してlengthメソッドを呼び出すと、その長さを求められることを確認してみてください(4.2.2)。
> User.all.class # => User::ActiveRecord_Relation > User.all.length # => 2
6.1.5 ユーザーオブジェクトを更新する
- ユーザーオブジェクトはsaveメソッドを使用して簡単に更新出来る。また、save前にreloadを使用するとデータベース上から元のデータを持ってきてくれる。
> user # => #<User id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: "2020-10-21 03:06:47", updated_at: "2020-10-21 03:06:47"> > user.email # => "michael@example.com" > user.email = "mhartl@example.net" > user.save # => true > user.email = "foo@bar.com" > user.reload.email > user.email # => "mhartl@example.net"
- また、updateメソッドを使用することでsaveまで一気に行えます。正しupdateメソッドはカラムの保存が1つでも失敗すると全て失敗するようになっています。
> user.update(name: "The Dude", email: "dude@abides.org") # => true > user.name # => "The Dude" > user.email # => "dude@abides.org"
- 特定の属性のみ更新したい場合はupdate_attributeメソッドを使用する。
> user.update_attribute(:name, "El Duderio") # => true > user.name # => "El Duderino"
演習
1. userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。
> user = User.create(name: "Foo Bar", email: "foo@bar.com") # => true > user # => #<User id: 1, name: "Foo Bar", email: "foo@bar.com", created_at: "2020-10-21 03:43:27", updated_at: "2020-10-21 03:43:27"> > user.name = "ChangedName" > user.save > User.first # => #<User id: 1, name: "ChangedName", email: "foo@bar.com", created_at: "2020-10-21 03:43:27", updated_at: "2020-10-21 03:44:38">
2. 今度はupdateを使って、email属性を更新および保存してみてください。
> user.update_attribute(:email, "updated@example.com") # => true > User.first # => #<User id: 1, name: "ChangedName", email: "updated@example.com", created_at: "2020-10-21 03:43:27", updated_at: "2020-10-21 03:46:23">
3. 同様にして、マジックカラムであるcreated_atも直接更新できることを確認してみてください。ヒント: 更新するときは「1.year.ago」を使うと便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の時間を算出してくれます。
> user.update_attribute(:created_at, 1.year.ago) # => true > User.first # => #<User id: 1, name: "ChangedName", email: "updated@example.com", created_at: "2019-10-21 03:47:49", updated_at: "2020-10-21 03:47:49">
6.2.1 有効性を検証する
- モデルのテストを行います。Userモデルのインスタンスを1つ作り有効なオブジェクトかをテストします。
# 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
- テストが成功することを確認します。モデルのテストのみ行うには以下のコマンドを使用する。
$ rails test:models 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
演習
1. コンソールから、新しく生成したuserオブジェクトが有効(valid)であることを確認してみましょう。
$ rails c --sandbox > user = User.new(name: "Example User", email: "user@example.com") > user.valid? # => true
2. 6.1.3で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。
> user = User.new(name: "Michael Hartl", email: "michael@example.com") > user.valid? # => true
6.2.2 存在性を検証する
- テストにnameが空白だった際、valid?がfalseになるかの検証を記載する。
# 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 be present" do @user.name = " " assert_not @user.valid? end end
- テストが失敗します。つまりnameが空白でもvalid?がtrueになってしまっています。
$ rails test:models 2 tests, 2 assertions, 1 failures, 0 errors, 0 skips
- nameに必ず文字列が入っていけないと検査する方法はモデルにvalidatesを追記します。presenceをtrueにすることで空白文字の場合valid?がfalseになります。
# app/models/user.rb class User < ApplicationRecord validates :name, presence: true end
- railsコンソールで正しく検査されているか確認します。
$ rails c > user = User.new(name: "", email: "michael@example.com") > user.valid? # => false > user.errors.full_messages # => ["Name can't be blank"] > user.save # => false
- 以上によりテストが成功します。
$ rails test:models 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
- emailも同様にpresence設定します。
# 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 should be present" do @user.email = " " assert_not @user.valid? end end
$ rails test:models 3 tests, 3 assertions, 1 failures, 0 errors, 0 skips
# app/models/user.rb class User < ApplicationRecord validates :name, presence: true validates :email, presence: true end
$ rails t 10 tests, 22 assertions, 0 failures, 0 errors, 0 skips
演習
1. 新しいユーザーuを作成し、作成した時点では有効ではない(invalid)ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。
$ rails c --sandbox > u = User.new > u.valid? # => false > u.errors.full_messages # => ["Name can't be blank", "Email can't be blank"]
2. u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?
> u.errors.messages # => {:name=>["can't be blank"], :email=>["can't be blank"]} > u.errors.messages[:email] # => ["can't be blank"]
6.2.3 長さを検証する
- nameの長さの上限を50文字, emailは255文字に制限する。まずはテストを書く。
# 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 bee too long" do @user.name = "a" * 51 assert_not @user.valid? end test "email should not bee too long" do @user.email = "a" * 244 + "@examole.com" assert_not @user.valid? end end
- テストが失敗することを確認します。
$ rails test:models 5 tests, 5 assertions, 2 failures, 0 errors, 0 skips
- モデルファイルにvalidatesを追記していく。文字数の上限はlength: { maximum: }を使用する。
# app/models/user.rb class User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 } end
- テストが成功することを確認します。
$ rails test:models 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips
演習
1. 長すぎるnameとemail属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。
$ rails c --sandbox > user = User.new(name: "a" * 51, email: "a" * 244 + "@example.com") > user.valid? # => false
2. 長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。
> user.errors.full_messages # => ["Name is too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]
6.2.4 フォーマットを検証する
- メールがちゃんとしたメールのフォーマットをしているか検証していきます。まずはテストを書きます。
# 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 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
- まだemailのフォーマットを指定していないのでテストは成功します。
$ rails test:models 6 tests, 10 assertions, 0 failures, 0 errors, 0 skips
- 配列内のメールアドレスを正しくないフォーマットに変更しassert_notでテストします。
# 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 validation should accept valid addresses" do valid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com] valid_addresses.each do |valid_address| @user.email = valid_address assert_not @user.valid?, "#{valid_address.inspect} should be valid" end end end
$ rails test:models 6 tests, 6 assertions, 1 failures, 0 errors, 0 skips
- 正規表現を用いてemailのフォーマットを指定します。
# app/models/user.rb class User < ApplicationRecord VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } end
- フォーマットの指定をしたのでテストが成功します。
$ rails test:models 6 tests, 10 assertions, 0 failures, 0 errors, 0 skips
演習
1. リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
2. 先ほど触れたように、リスト 6.21のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスをリスト 6.19の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。次に、リスト 6.23で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。
# 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 validation should accept valid addresses" do valid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com foo@bar..com] valid_addresses.each do |valid_address| @user.email = valid_address assert_not @user.valid?, "#{valid_address.inspect} should be valid" end end end
$ rails test:models 6 tests, 11 assertions, 1 failures, 0 errors, 0 skips
3. foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
# app/models/user.rb class User < ApplicationRecord VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } end
$ rails test:models 6 tests, 11 assertions, 0 failures, 0 errors, 0 skips
6.2.5 一意性を検証する
- メールアドレスは一意でなければなりません。varidatesメソッドの:uniquenessオプションを使用することで一意性を強制することが出来ます。
# 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 address should be unique" do duplicate_user = @user.dup @user.save assert_not duplicate_user.valid? end end
$ rails test:models 7 tests, 12 assertions, 1 failures, 0 errors, 0 skips
# app/models/user.rb class User < ApplicationRecord VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true end
$ rails test:models 7 tests, 12 assertions, 0 failures, 0 errors, 0 skips
- 現状はFOO@BAR.COMとfoo@bar.comは別々のアドレスとみなされています。一般的にメールアドレスは大文字と小文字は区別しません。テストを修正します。
# 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 address should be unique" do duplicate_user = @user.dup duplicate_user.email = @user.email.upcase @user.save assert_not duplicate_user.valid? end end
$ rails test:models 7 tests, 12 assertions, 1 failures, 0 errors, 0 skips
- :uniquenessオプションには:case_sensitiveオプションがありこのオプションをfalseにすることで大文字と小文字を区別しなくなります。
# app/models/user.rb class User < ApplicationRecord VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } end
- これでテストは成功するようになります。
$ rails test:models 7 tests, 12 assertions, 0 failures, 0 errors, 0 skips
- またデータベースにindexを追加して一意性をデータベースの登録時にも行うようにします。
$ rails g migration add_index_to_users_email
- マイグレーションに以下の記述を追記します。add_indexメソッドで指定のカラムにインデックスを追加しuniqueオプションをtrueにすることで一意性を強制できます。
# db/migrate/20201021050120_add_index_to_users_email.rb class AddIndexToUsersEmail < ActiveRecord::Migration[6.0] def change add_index :users, :email, unique: true end end
- データベースをマイグレートします。
$ rails db:migrate
- テスト用のfixtureを空にしておく。(一意でないため)
- データベース上ではまだ大文字と小文字を区別していない。この問題を解決する方法としてデータベースに保存する前にメールアドレスの文字列を小文字にしてしまうことである。
# app/models/user.rb class User < ApplicationRecord before_save { self.email = email.downcase } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true end
- テストが成功することを確認します。
$ rails test:models 7 tests, 12 assertions, 0 failures, 0 errors, 0 skips
演習
1. リスト 6.34のように、メールアドレスを小文字にするテストをリスト 6.26に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.34のテストがうまく動いているか確認するためにも、before_saveの行をコメントアウトして red になることを、また、コメントアウトを解除すると green になることを確認してみましょう。
# 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 address 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 end
$ rails test:models 8 tests, 13 assertions, 0 failures, 0 errors, 0 skips
2. テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう。ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります(リスト 6.35)。
# app/models/user.rb class User < ApplicationRecord before_save { email.downcase! } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true end
$ rails test:models 8 tests, 13 assertions, 0 failures, 0 errors, 0 skips
6.3.1 ハッシュ化されたパスワード
- セキュアにハッシュ化されたパスワードをデータベースのpassword_digestに保存できるようにする。XXX_digestという属性を作成するとXXX, XXX_confirmationという2つの仮想的な属性を使用出来るようになる。またauthenticateメソッドが使用出来るようになる。これは引数がパスワードと一致していればオブジェクトを返すメソッド。
- password_digestを追加するマイグレーションを作成する。
$ rails g migration add_password_digest_to_users password_digest:string
# db/migrate/20201021051857_add_password_digest_to_users.rb class AddPasswordDigestToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :password_digest, :string end end
- データベースをマイグレートします。
$ rails db:migrate
- ハッシュ関数を使用するためにbcrypt gemを追加します。
6.3.2 ユーザーがセキュアなパスワードを持っている
- Userモデルでhas_secure_passwordを使用します。これでpassword属性とpassword_confirmation属性が使用出来るようになりました。
# app/models/user.rb class User < ApplicationRecord before_save { email.downcase! } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true has_secure_password end
- テストを修正します。
# 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
- テストが成功することを確認します。
$ rails test:models 8 tests, 13 assertions, 0 failures, 0 errors, 0 skips $ rails t 15 tests, 32 assertions, 0 failures, 0 errors, 0 skips
演習
1. この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。
$ rails c --sandbox > user = User.new(name: "Foo Bar", email: "foo@bar.com") > user.valid? # => false
2. なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。
> user.errors.full_messages # => ["Password can't be blank"]
6.3.3 パスワードの最小文字数
- パスワードの最小文字数を6文字に設定します。まずテストから書いていきます。
# 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 #... 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 - テストが失敗することを確認します。 >|command| $ rails t 17 tests, 34 assertions, 2 failures, 0 errors, 0 skips
- validatesメソッドでpasswordにpresence: trueとlength: { minimum: }を設定します。
# app/models/user.rb class User < ApplicationRecord before_save { email.downcase! } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true has_secure_password validates :password, presence: true, length: { minimum: 6 } end
- テストが成功することを確認します。
$ rails t 17 tests, 34 assertions, 0 failures, 0 errors, 0 skips
演習
1. 有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。
$ rails c --sandbox > user = User.new(name: "Foo Bar", email: "foo@bar.com", password: "foo", password_confirmation: "foo") > user.valid? # => false
2. 上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。
> user.errors.full_messages # => ["Password is too short (minimum is 6 characters)"]
6.3.4 ユーザーの作成と認証
- 次の章に向けてUserモデルに1つオブジェクトを追加しておきます。
$ rails c > User.create(name: "Michael Hartl", email: "michael@example.com", password: "foobar", password_confirmation: "foobar")
- うまく作成されたかdb/development.sqlite3の中身を確認します。
演習
1. コンソールを一度再起動して(userオブジェクトを消去して)、このセクションで作ったuserオブジェクトを検索してみてください。
$ rails c > user = User.first > user # => #<User id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: "2020-10-21 05:40:52", updated_at: "2020-10-21 05:40:52", password_digest: [FILTERED]>
2. オブジェクトが検索できたら、名前を新しい文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね...、なぜうまくいかなかったのでしょうか?
- passwrod, password_confirmationがnilのため
$ rails c > user.name = "foobar" > user.save # => false > user.errors.full_messages # => ["Password can't be blank", "Password is too short (minimum is 6 characters)"] > user.password # => nil > user.password_confirmation # => nil
3. 今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。
> user.update_attribute(:name, "Foo Bar") # => true > user.name # => "Foo Bar"
6.4 最後に
- リモートリポジトリへプッシュしてherokuへデプロイします。
$ rails t $ git add -A $ git commit -m "Make a basic User model (including secure passwords)" $ git push origin modering-users $ git checkout master $ git merge modering-users $ git push origin master $ git push heroku $ heroku run rails db:migrate
- 6章は以上で終了です。
Ruby on Railsチュートリアル 7章
7.1 ユーザーを表示する
- トピックブランチを作成します。
$ git checkout -b sign-up
7.1.1 デバックとRails環境
- アプリケーションにデバッグ情報を出力するようにします。
# app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title><%= full_title(yield(:title)) %></title> <%= render 'layouts/rails_default'%> <%= render 'layouts/shim' %> </head> <body> <%= render 'layouts/header'%> <div class="container"> <%= yield %> <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> </div> </body> </html>
演習
1. ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。このページを表示するとき、どのコントローラとアクションが使われていたでしょうか?paramsの内容から確認してみましょう。
- static_pagesコントローラのaboutアクションが使用されています。
2. Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。その後、puts user.attributes.to_yamlを実行すると何が表示されますか? ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。
- 同じ内容が表示される。
$ rails c --sandbox > user = User.first > puts user.attributes.to_yaml --- id: 1 name: Michael Hartl email: michael@example.com created_at: !ruby/object:ActiveSupport::TimeWithZone utc: &1 2020-10-21 05:40:52.899711000 Z zone: &2 !ruby/object:ActiveSupport::TimeZone name: Etc/UTC time: *1 updated_at: !ruby/object:ActiveSupport::TimeWithZone utc: &3 2020-10-21 05:48:52.973765000 Z zone: *2 time: *3 password_digest: "$2a$12$UUe6iuNT8ToJvzjx/GfItuQIu3aXoyXFDPF0GBP/bFrZU.438WEvO" > y user.attributes --- id: 1 name: Michael Hartl email: michael@example.com created_at: !ruby/object:ActiveSupport::TimeWithZone utc: &1 2020-10-21 05:40:52.899711000 Z zone: &2 !ruby/object:ActiveSupport::TimeZone name: Etc/UTC time: *1 updated_at: !ruby/object:ActiveSupport::TimeWithZone utc: &3 2020-10-21 05:48:52.973765000 Z zone: *2 time: *3 password_digest: "$2a$12$UUe6iuNT8ToJvzjx/GfItuQIu3aXoyXFDPF0GBP/bFrZU.438WEvO"
7.1.2 Userリソース
- UserをRESTfulなアクションを有効にするためにルーティングを追加します。
# config/routes.rb Rails.application.routes.draw do root "static_pages#home" get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' get '/signup', to: 'users#new' resources :users end
- まずは/users/:idのGETリクエストのusers#showを作成していきます。まずはビューから作成。
$ touch app/views/users/show.html.erb
# app/views/users/show.html.erb <%= @user.name %>, <%= @user.email %>
- アクションも作成します。ビュー内で使用するローカル変数@userを定義します。
# app/controllers/users_controller.rb class UsersController < ApplicationController def new end def show @user = User.find(params[:id]) end end
- Railsサーバで動作確認を行います。
演習
1. 埋め込みRubyを使って、マジックカラム(created_atとupdated_at)の値をshowページに表示してみましょう(リスト 7.4)。
- ビューを編集します。
# app/views/users/show.html.erb <%= @user.name %>, <%= @user.email %>
2. 埋め込みRubyを使って、Time.nowの結果をshowページに表示してみましょう。ページを更新すると、その結果はどう変わっていますか? 確認してみてください。
# app/views/users/show.html.erb <%= @user.name %>, <%= @user.email %>, <%= @user.created_at %>, <%= @user.updated_at %><br> <%= Time.now %>
7.1.3 debuggerメソッド
- byebug gemによりdebuggerメソッドが使用できる。このメソッドを実行するとアプリケーションの動作を一時停止しターミナル上でデバッグを行うことが出来る。(変数の値を確認したり)
演習
1. showアクションの中にdebuggerを差し込み(リスト 7.6)、ブラウザから /users/1 にアクセスしてみましょう。その後コンソールに移り、putsメソッドを使ってparamsハッシュの中身をYAML形式で表示してみましょう。ヒント: 7.1.1.1の演習を参考にしてください。その演習ではdebugメソッドで表示したデバッグ情報を、どのようにしてYAML形式で表示していたでしょうか?
# app/controllers/users_controller.rb class UsersController < ApplicationController def new end def show @user = User.find(params[:id]) debugger # <- この位置で停止しデバッグが出来る end end
(byebug) puts params.to_yaml --- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess controller: users action: show id: '1' permitted: false
2. newアクションの中にdebuggerを差し込み、/users/new にアクセスしてみましょう。@userの内容はどのようになっているでしょうか? 確認してみてください。
- newアクション内では@userは定義されていないためnilとなる。
(byebug) @user nil
7.1.4 Gravatar画像とサイドバー
- gravatar_forヘルパーメソッドを使用してGravatarの画像を利用出来るようにします。
# app/views/users/show.html.erb <% provide(:title, @user.name) %> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1>
- gravatar_forヘルパーメソッドを定義します。GravatarのURLはメールアドレスをMD5でハッシュ化したものが使用される。MD5のハッシュ化はDigest::MD5::hexdigestメソッドを使用する。
# app/helpers/users_helper.rb module UsersHelper def gravatar_for(user) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end
- 画像が表示されました。
- railsコンソールを使用してデータベースの内容を変更します。
$ rails c > user = User.first > user.update(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar") # => true
- 情報、画像が変更されたことを確認します。
演習
1. (任意)Gravatar上にアカウントを作成し、あなたのメールアドレスと適当な画像を紐付けてみてください。メールアドレスをMD5ハッシュ化して、紐付けた画像がちゃんと表示されるかどうか試してみましょう。
- 省略
2. 7.1.4で定義したgravatar_forヘルパーをリスト 7.12のように変更して、sizeをオプション引数として受け取れるようにしてみましょう。うまく変更できると、gravatar_for user, size: 50といった呼び出し方ができるようになります。重要: この改善したヘルパーは10.3.1で実際に使います。忘れずに実装しておきましょう。
# app/helpers/users_helper.rb module UsersHelper def gravatar_for(user, options = { size: 80 }) size = options[:size] gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end
3. オプション引数は今でもRubyコミュニティで一般的に使われていますが、Ruby 2.0から導入された新機能「キーワード引数(Keyword Arguments)」でも実現することができます。先ほど変更したリスト 7.12を、リスト 7.13のように置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。
- 演習2はハッシュのキーにsizeという名前を付けて値を渡している。演習3はメソッドのキーワード引数に値を渡している。
# app/helpers/users_helper.rb module UsersHelper def gravatar_for(user, size: 80 ) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end
7.2.1 form_withを使用する
- form_withヘルパーメソッドを使用することでActiveRecordのオブジェクトを取り込みそのオブジェクトの属性を使ってフォームを構築出来ます。
- まずはローカル変数@userを定義します。
# app/controllers/users_controller.rb class UsersController < ApplicationController def new @user = User.new end def show @user = User.find(params[:id]) end end
- ビューにフォームを追記します。
# app/views/users/new.html.erb <% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, local: true) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>
- app/assets/stylesheets/custom.scssを編集してフォームの見た目を整えます。
- 動作確認をします。
演習
1. 試しに、ブロックの変数fをすべてfoobarに置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobarとするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。
- foobarへ変更しても動作することを確認。変数名fはform_withのfでありfoobarという変数名は適当ではない。
7.2.2 フォームHTML
- Userモデルが新規作成される時はusersコントローラのcreateアクションが実行されます。createアクションにUserモデルを新規作成するコードを記載していきます。
# app/controllers/users_controller.rb class UsersController < ApplicationController #... def create @user = User.new(params[:user]) if @user.save else render 'new' end end end
- 実際にcreateアクションを実行するとエラーになります。
- このエラーはRails4.0以降ではStrong Parametersという手法を使用することが標準になったためです。
7.3.2 Strong Parameters
- Strong Parametersを使用すると以下のようなコードになります。
# app/controllers/users_controller.rb class UsersController < ApplicationController #... def create @user = User.new(user_params) if @user.save else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end
7.3.3 エラーメッセージ
- ユーザー登録失敗時にエラーメッセージが表示されるようビューを変更します。
# app/views/users/new.html.erb <% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>
$ mkdir app/views/shared $ touch app/views/shared/_error_messages.html.erb
# app/views/shared/_error_messages.html.erb <% if @user.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@user.errors.count, "error") %> </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
- CSSを整えて動作確認をします。
演習
1. 最小文字数を5に変更すると、エラーメッセージも自動的に更新されることを確かめてみましょう。
- バリデーションを変更します。
# app/models/user.rb class User < ApplicationRecord before_save { email.downcase! } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true has_secure_password validates :password, presence: true, length: { minimum: 5 } end
- エラーメッセージも変更されました。
2. 未送信のユーザー登録フォーム(図 7.13)のURLと、送信済みのユーザー登録フォーム(図 7.19)のURLを比べてみましょう。なぜURLは違っているのでしょうか? 考えてみてください。
# 未送信のユーザー登録フォームURL # ルーティングでいうget '/signup', to: 'users#new'が実行されている。 http://127.0.0.1:3000/signup # 送信済みのユーザー登録フォームURL # ルーティングでいうresources :usersが実行されている。 http://127.0.0.1:3000/users
7.3.4 失敗時のテスト
- ユーザ新規登録用の統合テストを作成します。
$ rails g integration_test users_signup Running via Spring preloader in process 2573 invoke test_unit create test/integration/users_signup_test.rb
- テストを書いていきます。無効なユーザ登録のPOSTを送った前後でUser.countに差異がないかをチェックしています。
# test/integration/users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" }} end assert_template 'users/new' end end
- テストが成功することを確認します。
$ rails test:integration 2 tests, 10 assertions, 0 failures, 0 errors, 0 skips
演習
1. リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。
# test/integration/users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do #... assert_template 'users/new' assert_select 'div#error_explanation' assert_select 'div.alert-danger' end end
$ rails t 18 tests, 38 assertions, 0 failures, 0 errors, 0 skips
7.4.1 登録フォームの完成
- 登録が成功した際、ユーザーページにリダイレクトするようにする。
# app/controllers/user_controller.rb class UsersController < ApplicationController #... def create @user = User.new(user_params) if @user.save redirect_to @user else render 'new' end end #... end
演習
1. 有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。
$ rails c > User.find(2) # => #<User id: 2, name: "foobar", email: "foo@bar.com", created_at: "2020-10-21 12:22:38", updated_at: "2020-10-21 12:22:38", password_digest: [FILTERED]>
7.4.2 flash
- flash変数に代入したメッセージはリダイレクトした直後のページで表示されるようになる。
# app/controllers/user_controller.rb class UsersController < ApplicationController #... def create @user = User.new(user_params) if @user.save flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end #... end
- flash変数の内容をWebサイトのレイアウトに追加します。
# app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <title><%= full_title(yield(:title)) %></title> <%= render 'layouts/rails_default'%> <%= render 'layouts/shim' %> </head> <body> <%= render 'layouts/header'%> <div class="container"> <% flash.each do |message_type, message| %> <div class="alert alert-<%= message_type%>%"><%= message %></div> <% end %> <%= yield %> <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> </div> </body> </html>
演習
1. コンソールに移り、文字列内の式展開(4.2.1)でシンボルを呼び出してみましょう。例えば"#{:success}"といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。
$ rails c > puts "#{:success}" success
2. 先ほどの演習で試した結果を参考に、リスト 7.28のflashはどのような結果になるか考えてみてください。
> puts "#{flash[:success]}" It worked! > puts "#{flash[:danger]}" It failed.
7.4.3 実際のユーザー登録
- データベースのリセットをして動作確認をします。
$ rails db:migrate:reset
演習
1. Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.30のようになるはずです。
$ rails c > User.find_by(email: "example@railstutorial.org") # => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2020-10-21 14:10:26", updated_at: "2020-10-21 14:10:26", password_digest: [FILTERED]>
2. 自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。
- 省略
7.4.4 成功時のテスト
- 有効な送信に対するテストを書きます。follow_redirect!メソッドを実行することでリダイレクト先に移動出来ます。
# test/integration/users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest #... test "valid signup information" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar" }} end follow_redirect! assert_template 'users/show' end end
- テストが成功することを確認します。
$ rails t 19 tests, 40 assertions, 0 failures, 0 errors, 0 skips
演習
1. 7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.32に最小限のテンプレートを用意しておいたので、参考にしてください(FILL_INの部分を適切なコードに置き換えると完成します)。ちなみに、テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。
# test/integration/users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest #... test "valid signup information" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar" }} end follow_redirect! assert_template 'users/show' assert_not flash.empty? end end
$ rails t 19 tests, 41 assertions, 0 failures, 0 errors, 0 skips
2. 本文中でも指摘しましたが、flash用のHTML(リスト 7.29)は読みにくいです。より読みやすくしたリスト 7.33のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。
- 指定の記述へ変更を行いテストが成功することを確認します。
3. リスト 7.26のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。
- 省略
4. リスト 7.26で、@user.saveの部分をfalseに置き換えたとしましょう(バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。
- 有効な送信をしているのにUser.countの変化量が0(変化していない)ためエラーになる。
7.5 プロのデプロイ
- 現在までの変更をローカルリポジトリにプッシュします。
$ rails t $ git add -A $ git commit -m "Finish user signup" $ git push origin sign-up
- トピックブランチをmasterブランチへマージします。
$ git checkout master $ git merge sign-up
7.5.2 本番環境用のWebサーバー
- 本番環境のWebサーバ設定を行います。config/puma.rbに本番環境の設定を記載します。設定内容はherokuの公式ドキュメントを参考にします。
Deploying Rails Applications with the Puma Web Server | Heroku Dev Center
- HerokuでPumaを走らせるための設定ファイルを作成します。
7.5.3 本番データベースを設定する
- herokuの公式ドキュメントを参考にconfig/database.ymlを編集します。
Getting Started on Heroku with Rails 6.x | Heroku Dev Center
7.5.4 本番環境へのデプロイ
- リモートリポジトリへのプッシュおよびherokuへデプロイを行います。
$ rails t $ git add -A $ git commit -m "Use SSL and the Puma webserver in production" $ git push origin master $ git push heroku
本日の総括
- モデルの作成, 検査, フォームの作成について学習した。曖昧になっていたhas_secure_passwordやform_withの知識を固められた。