47日目
今日の学習
Ruby on Rails
Railsチュートリアル 第7章
二日空けてしまったが、ストロングパラメータを設定したところから再開する。
エラーメッセージの実装
何かしらの問題が起こり、ユーザー登録に失敗した場合に、ユーザー側に分かりやすくなるようにエラーメッセージを表示するようにする。
Railsは、エラーメッセージをUserモデル検証時に自動的に生成してくれる。
以下の様なユーザーを作成する >> user = User.new(name: "Foo Bar", email: "foo@invalid", password: "dude", password_confirmation: "dude") saveしようとするが失敗する >> user.save User Exists? (0.6ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "foo@invalid"], ["LIMIT", 1]] => false errorsメソッドとfull_messagesメソッドでエラーを調べる >> user.errors.full_messages => ["Email is invalid", "Password is too short (minimum is 6 characters)"]
上の例では、二種類のエラーが出ている。「無効なメールアドレス」であること、「パスワードが短すぎる(最低6文字なのに4文字)」ことを指摘している。
このメッセージを表示するために、ユーザーのnew
ページでエラーメッセージのパーシャル(部分テンプレート)を出力する。
Railsチュートリアルでは、このときBootstrapが用意しているclass form-control
を一緒に追加して活用している。
<%= f.label :name %> classに'form-control'を追加 <%= f.text_field :name, class: 'form-control' %>
この時に準備するパーシャルは、new.html.erb
と同じディレクトリに用意する...のではなく、shared
というディレクトリを作成してその中に入れる。
Rails全般の慣習として、複数のビューで使われるパーシャルは専用のディレクトリshared
を作成し、その中に置くようだ。
作成したパーシャル 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 %>
any?
メソッドで、エラーが一つでもあった場合はそれ以下を実行する様にする。
pluralize
というのは、英語専用のテキストヘルパー。最初の引数に整数が与えられると、それに基づいて二番目の引数の英単語を複数形にしてくれる。
>> helper.pluralize(1, "error") => "1 error" 2以上だと、pluralizeメソッドが複数形にしてくれる >> helper.pluralize(2, "error") => "2 errors"
これを利用して、以下の様にすることでエラーが2つ以上の場合はerrors
となるようにできる。
pluralize(@user.errors.count, "error")
日本語だと、エラーは何個あってもエラーだと表現するため手間がなくていいなと思った。
(ものすごく余談だが、0
の場合はerrors
が返ってくる。ゼロとあれば複数形が正しいらしい。知らなかった)
新規ユーザー登録失敗時のテストを作成
新規ユーザー登録用の統合テストを生成する。リソース名は複数形にする。
rails g integration_test users_signup
生成したファイルtest/integration/users_signup_test.rb
に以下を書き込む。
require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do # getメソッドでユーザー登録ページにアクセス get signup_path # テスト前と後で実際のユーザー数が変わらないかどうかを確認 assert_no_difference 'User.count' do # User.newに入れるデータをparams[:user]というハッシュにまとめる post users_path, params: { user: {name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end # ユーザー登録が失敗した場合に本来描画されるアクションを指定 assert_template 'users/new' end end
また、ブラウザに描画される結果が適切かどうかを調べるために、assert_select
を利用してHTMLの構造が適切かどうかを調べる。今回のテストでは、assert_template
の後に配置している。
CSSのセレクタで指定する感覚で書く。指定したdivがあるかどうかをテストする。
assert_select 'div#error_explanation' assert_select 'div.field_with_errors' assert_select 'div.alert'
登録フォームを完成させる。
Railsチュートリアル通り進めていると、今はまだcreateアクションが完璧ではないため、ユーザー登録フォームに条件を満たした状態で送信してもエラーが起こる(createアクションに対応するビューがないため)。
createアクション後は、createのビューに移行するのではなく作成成功したユーザーのアカウント(もしくはルートURL)にリダイレクトするのが一般的なので、そのような措置を採る。
def create @user = User.new(user_params) if @user.save # リダイレクトさせる redirect_to @user else render 'new' end end
redirect_to @user
これは、以下のような意味を持つ。Railsが勝手に推察して、上の形でも読み取ってくれる。
redirect_to user_url(@user)
flashの実装
Railsでは、フラッシュを実装する際にflash
という特殊な変数を利用する。これはハッシュの様に扱う。
成功した時 flash[:succses] = "成功です" flash[:danger] = "失敗です" flash[:notice] = "お知らせです"
また、Bootstrap CSSは、flashのクラス用に、4つのスタイルを持っている*1。
Railsチュートリアルでは、flashを以下のように追加した。
<% flash.each do |message_type, message| %> <%= content_tag(:div, message, class: "alert alert-#{message_type}") %> <% end %>
content_tag
というヘルパーを用いている。これは、HTMLとERBが混ざっているときに使用すると、文章がすっきりする。
content_tag
# content_tagで書き換える前の状態 <div class="alert alert-<%= message_type %>"><%= message %></div> # content_tagで書き換え <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
Rails tips: ビューの`content_tag`のあまり知られていないオプション(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社
<div>
で挟んでいるので、第1引数には:div
<div>
で挟むものを、第2引数に設定message
- 属性があれば渡す
class:
新規ユーザー登録成功時のテストを作成
今回は、ユーザーが作成されたかどうかを確認するため、assert_difference
というメソッドを利用して、先ほどとは逆にUserの数に変化があるか(増えたかどうか)を検証する。
test "valid signup iformation" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end follow_redirect! assert_template 'users/show' end
assert_difference
の第1引数には、文字列'User.count'
を渡して、このブロック内の処理を実行する直前と、実行した直後のUser.count
の値を比較する。
第2引数はオプションで、今回は差が1になるはずなので、1
を渡している。
follow_redirect!
メソッドは、対応するコントローラ内にあるリダイレクトの挙動に従い、ページを遷移する。
flashのテストコード
flashが機能しているのかどうかテストコードを書く。assert_template
の後ろに追加する。
flashの中身があるかどうかを判断すればよいので、empty?
メソッドを利用する。
assert_not flash.empty?
データを操作できるようにするデプロイ
SSL/TLS
ローカルサーバーではテスト用にアカウントを作ったりするが、その情報がデプロイ時に流れてしまうことを防ぐために、情報を暗号化する必要がある。
その技術が、TLS(Transport Layer Security)
。これを使って、セキュリティの欠陥を防ぐ。
これの別名がSSL
のようだ。いきなりSSLを導入します、という文章が出てきてちょっと混乱した。
元々はSSL(Secure Socket Layer)
という名称だったが、TLS
に名称が変わったようだ。いまだにSSL
の方で呼ばれることがあるらしい。
https://wa3.i-3-i.info/word16310.html
インターネットの大事な情報を暗号化して、安心して利用できるようにする仕組みを導入していく。
やり方は複雑ではなく簡単なようで、config
に「本番環境ではSSLを使う様にする」と設定をするだけでいいようだ。
config/environments/production.rb
の1行、config.force_ssl
の部分をtrue
に設定するだけ。
47行目に発見。このコメントアウトを解除するだけでいい。なんとも楽。
SSL証明書
SSLを設定した後は、ドメイン毎にSSL証明書
を購入する必要がある。
今使っているデプロイ先はHerokuなのだが、HerokuのSSL証明書
に便乗する形でこれをクリアできるそうだ。
もちろん、他の独自ドメインを使う場合は、SSL証明書
を購入する必要があるが、今回は購入作業などをしなくても良いらしい。
https://wa3.i-3-i.info/word1836.html
本番環境用のWebサーバーにPumaを使用
HerokuのデフォルトWebサーバーは、WEBrick
というものだが、本番環境として適切なサーバーではないらしい。
その代わりに、Puma
という多数のリクエストを捌けるRuby.Rackアプリケーション用のサーバーを利用する。
Rails 5移行では、Puma
のgemはデフォルトで使えるようになっているため、わざわざ設定しなくていい。
設定するために色々とファイルを編集するようだが、これは内容をコピーペーストしただけなので割愛。
Rails 6 でプロダクト開発を学ぼう - Railsチュートリアル
本番データベースを設定
開発環境ではsqliteを利用しているが、HerokuではPostgreSQLが推奨されているため、本番ではそちらを使うようにする。
config/database.yml
のproduction
を書き換えるだけでよい。こちらもHerokuのドキュメントに従ってコピペなので割愛。
Rails 6 でプロダクト開発を学ぼう - Railsチュートリアル
実際に設定を経てデプロイすると、https
になっていて、🔒マークもあり証明書が有効になっている。上手く設定できているようだ。
*1:success, info, warning, danger