プログラミング備忘録

プログラミングの学習状況をメモしています

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に設定するだけ。

f:id:hasegawa_note:20210621141347p:plain

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.ymlproductionを書き換えるだけでよい。こちらもHerokuのドキュメントに従ってコピペなので割愛。

Rails 6 でプロダクト開発を学ぼう - Railsチュートリアル

f:id:hasegawa_note:20210621143605p:plain 実際に設定を経てデプロイすると、httpsになっていて、🔒マークもあり証明書が有効になっている。上手く設定できているようだ。

*1:success, info, warning, danger