Rails-tutorial自分用まとめ(第7章ユーザー登録 主に演習とその回答)

その6から続きます

7.1.1 デバッグとRails環境

サイトのレイアウトにデバッグ情報を追加するapp/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
 デバッグ表示を整形するための追加と、Sassのミックスイン.
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

@mixin box_sizing {-moz-box-sizing:    border-box;-webkit-box-sizing: border-box;box-sizing:         border-box;}.
.
.
/* miscellaneous */
.debug_dump {clear: both;float: left;width: 100%;margin-top: 45px; -moz-box-sizing:    border-box;
  -webkit-box-sizing: border-box;
  box-sizing:         border-box;
}

演習

1:ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。

このページを表示するとき、どのコントローラとアクションが使われ
ていたでしょうか? paramsの内容から確認してみましょう。

controller: static_pages
action: about

2:Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。

その後、puts user.attributes.to_yamlを実行すると何が表示されますか?
ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。

user = User.first

表示は同じ

HTTP   リクエストURL  アクション 名前付きルート        用途
GET    /users       index   users_path     すべてのユーザーを一覧するページ
GET    /users/1     show    user_path(user) 特定のユーザーを表示するページ
GET    /users/new   new     new_user_path  ユーザーを新規作成するページ (ユーザー登録)
POST   /users       create  users_path     ユーザーを作成するアクション
GET    /users/1/    edit    edit edit_user_path(user) id=1のユーザーを編集するページ
PATCH  /users/1     update  user_path(user) ユーザーを更新するアクション
DELETE /users/1     destroy user_path(user) ユーザーを削除するアクション
Usersリソースが提供するRESTfulなルート

演習

埋め込みRubyを使って、マジックカラム (created_atとupdated_at)
の値をshowページに表示してみましょう埋め込みRubyを使って、
Time.nowの結果をshowページに表示してみましょう。
ページを更新すると、その結果はどう変わっていますか? 確認してみてください。

<%= @user.name %>, <%= @user.email %>,
<%= @user.updated_at %>, <%= @user.created_at %>,
<%= Time.now %>

演習

1:showアクションの中にdebuggerを差し込みブラウザから/users/1
にアクセスしてみましょう。

その後コンソールに移り、putsメソッドを
使ってparamsハッシュの中身をYAML形式で表示してみましょう。

a = params
<ActionController::Parameters {"controller"=>"users", "action"=>"show", "id"=>"1"} permitted: false>
(byebug) puts a.to_yaml
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
controller: users
action: show
id: '1'
permitted: false
nil

2:newアクションの中にdebuggerを差し込み、/users/new にアクセスし
てみましょう。@userの内容はどのようになっているでしょうか?

@user
nil

Digestライブラリのhexdigestメソッドを使うと、
MD5のハッシュ化が実現できます。

>> email = "MHARTL@example.COM"
>> Digest::MD5::hexdigest(email.downcase)
=> "1fda4469bcbec3badf5418269ffc5968"
gravatar_forヘルパーメソッドを定義する
app/helpers/users_helper.rb
module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  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

ユーザーのサイドバーの最初のバージョンを作りましょう。ここではasideタグを使って実装します。このタグはサイドバーなどの補完コンテンツの表示に使われますが、単独で表示することもできます。rowクラスとcol-md-4クラスも追加しておきます。これらのクラスはBootstrapの一部です。ユーザー表示ページを変更した結果をリスト 7.10に示します。

showビューにサイドバーを追加するapp/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
</div>
SCSSを使ってサイドバーなどのユーザー表示ページにスタイルを与えるapp/assets/stylesheets/custom.scss
.
.
.
/* sidebar */

aside {
  section.user_info {
    margin-top: 20px;
  }
  section {
    padding: 10px 0;
    margin-top: 20px;
    &:first-child {
      border: 0;
      padding-top: 0;
    }
    span {
      display: block;
      margin-bottom: 3px;
      line-height: 1;
    }
    h1 {
      font-size: 1.4em;
      text-align: left;
      letter-spacing: -1px;
      margin-bottom: 3px;
      margin-top: 0px;
    }
  }
}

.gravatar {
  float: left;
  margin-right: 10px;
}

.gravatar_edit {
  margin-top: 15px;
}

演習

定義したgravatar_forヘルパーを変更して、
sizeをオプション引数として受け取れるようにしてみましょう。
うまく変更できると、gravatar_for user, size: 50といった呼び出し方ができるようになります。
重要:この改善したヘルパーは10.3.1で実際に使います。忘れずに実装しておきましょう

gravatar_forヘルパーにオプション引数を追加するapp/helpers/users_helper.rb
module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
def gravatar_for(user, options = { size: 80 }) 
   gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
size = options[:size]
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)」でも実現することができます。先ほど変更したリストを置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。

gravatar_forヘルパーにキーワード引数を追加する
app/helpers/users_helper.rb
module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  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_forを使用する

newアクションに@user変数を追加する
app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  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_for(@user) 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>
ユーザー登録フォームのCSS
app/assets/stylesheets/custom.scss
.
.
.
/* forms */

input, textarea, select, .uneditable-input {
  border: 1px solid #bbb;
  width: 100%;
  margin-bottom: 15px;
  @include box_sizing;
}

input {
  height: auto !important;
}

演習

試しに、リスト 7.15にある:name:nomeに置き換えてみましょう。どんなエラーメッセージが表示されるようになりますか?

undefined method `nome'

試しに、ブロックの変数fをすべてfoobarに置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobarとするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。

変数fは “form” のfを使用している。”f.label”等がHTMLフォーム要素に対応することがわかるようにする。”foobar”はメタ構文変数(意味を持たない名前)として認識されるため適さない。ほかはhogeとかhugaとか

7.2.2 フォームHTML

これまで見てきた次のようなコード (マスアサインメントと呼びます) は、

@user = User.new(params[:user])

実際にはこのようなコードとほぼ同じである、ということです。

@user = User.new(name: "Foo Bar", email: "foo@invalid",
password: "foo", password_confirmation: "bar")

演習

/signup?admin=1 にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認してみましょう。

Sassの@extend関数を使ってBootstrapのhas-errorというCSSクラスを適用してみます。

#error_explanation {
  color: red;

  ul {
    color: red;
    margin: 0 0 30px 0;
  }
 }

.field_with_errors {
   @extend .has-error;

 .form-control {
    color: $state-danger-text;
  }
}

演習

1:リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。

users_signup_test.rb

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path

(中略)

    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.alert'   
  end
end

2:ユーザー登録フォームのURLは /signup ですが、無効なユーザー登録データを送付するとURLが /users に変わってしまいます。これはリスト 5.43で追加した名前付きルート (/signup) と、RESTfulなルーティング (リスト 7.3) のデフォルト設定との差異によって生じた結果です。リスト 7.26とリスト 7.27の内容を参考に、この問題を解決してみてください。うまくいけばどちらのURLも /signup になるはずです。あれ、でもテストは greenのままになっていますね…、なぜでしょうか? (考えてみてください)

ユーザー登録のルーティングにPOSTリクエストを追加するconfig/routes.rb

  post '/signup',  to: 'users#create'
 /signup に対して送信する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_for(@user, url: signup_path) 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>

いずれのURLも /signup となる。

3:リスト 7.25のpost部分を変更して、先ほどの演習課題で作った新しいURL (/signup) に合わせてみましょう。また、テストが greenのままになっている点も確認してください。

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 signup_path, params: { user: { name:  "",
(後略)

4:リスト 7.27のフォームを以前の状態に戻してみて、テストがやはり greenになっていることを確認してください。これは問題です! なぜなら、現在postが送信されているURLは正しくないのですから。assert_selectを使ったテストをリスト 7.25に追加し、このバグを検知できるようにしてみましょう (テストを追加して redになれば成功です)。その後、変更後のフォーム (リスト 7.27) に戻してみて、テストが green になることを確認してみましょう。ヒント: フォームから送信してテストするのではなく、’form[action=”/signup”]’という部分が存在するかどうかに着目してテストしてみましょう。

リスト 7.27のフォームを以前の状態 (リスト 7.20) に戻してもテストはGREEN。

7.4.2 flash

flashという特殊な変数を使います。この変数はハッシュのように扱います。Railsの一般的な慣習に倣って、:successというキーには成功時のメッセージを代入するようにします。

演習

1:コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう
例えば”#{:success}”といったコードを実行すると、どんな値が返ってきますか?

"#{:success}"
=> "success"

2:先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。

flash = { success: "It worked!", danger: "It failed." }
"#{flash[:success]}"
=> "It worked!"
>> "#{flash[:danger]}"
=> "It failed."

演習

1:Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。

動作確認するだけ

2:自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。

動作確認するだけ

演習

1:実装したflashに対するテストを書いてみてください。どのくらい細かくテストする
かはお任せ。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。
テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。

users_signup_test.rb

require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
(中略)
  test "valid signup information" do
(中略)
    assert_not flash.empty?    
  end  
end

2:本文中でも指摘しましたが、flash用のHTMLは読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
      .
      .
      .
      <% flash.each do |message_type, message| %>
        <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
      <% end %>
      .
      .
      .
</html>

3:リダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。

保存とリダイレクトを行う、userのcreateアクションapp/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      #redirect_to @user
    else
      render 'new'
    end
  end

4:リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。

保存とリダイレクトを行う、userのcreateアクションapp/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.false
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end

7章のまとめ

1:debugメソッドを使うことで、役立つデバッグ情報を表示できる

2:Sassのmixin機能を使うと、CSSのルールをまとめたり他の場所で再利用できるようになる

3:Railsには標準で3つ環境が備わっており、それぞれ開発環境 (development)、テスト環境 (test)、本番環境 (production)と呼ぶ

4:標準的なRESTfulなURLを通して、ユーザー情報をリソースとして扱えるようになった

5:Gravatarを使うと、ユーザーのプロフィール画像を簡単に表示できるようになる

6:ユーザー登録に失敗した場合はnewビューを再描画するようにした。その際、Active Recordが自動的に検知したエラーメッセージを表示できるようにした

7:flash変数を使うと、一時的なメッセージを表示できるようになる

8:ユーザー登録に成功すると、データベース上にユーザーが追加、プロフィールページにリダイレクト、ウェルカムメッセージの表示といった順で処理が進む

9:統合テストを使うことで送信フォームの振る舞いを検証したり、バグの発生を検知したりできる

10:セキュアな通信と高いパフォーマンスを確保するために、本番環境ではSSLとPumaを導入した

その8に続く

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です