Rails
初心者

Rails Tutorial 第七章 ユーザー登録 簡易まとめ

7.1 ユーザーを表示する

image.png
↑理想とするモックアップ

この章からここを目指していく
ということでブランチをきる

$ git checkout -b sign-up

7.1.1 デバックと環境

3つの環境

Railsには
1テスト環境 (test)
2開発環境 (development)
3本番環境 (production)
の3つの環境がデフォルトで装備されています。
Rails consoleのデフォルトの環境はdevelopmentです。

  $ rails console
  Loading development environment
  >> Rails.env
  => "development"
  >> Rails.env.development?
  => true

テスト環境のデバッグなど、他の環境でconsoleを実行する必要が生じた場合は、環境をパラメータとしてconsoleスクリプトに渡すことができます。

  $ rails console test
  Loading test environment
  >> Rails.env
  => "test"
  >> Rails.env.test?
  => true

Railsサーバーもデフォルトではdevelopmentが使われますが、次のように他の環境を明示的に実行することもできます。

$ rails server --environment production

※注: console、server、migrateの3つのコマンドでは、デフォルト以外の環境を指定する方法がそれぞれ異なっており、混乱を招く可能性があります。ggr

デバック機能の実装

上の情報から、ビルトインのdebugメソッドとparams変数を使って、各プロフィールページにデバッグ用の情報が表示されるように、しかし開発環境のみでデバック情報が表示されるように追加する

※debugメソッドはアプリケーションの振る舞いを理解するため

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>
</html>

デバック出力用のCSS

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;
  @include box_sizing;
}

image.png

ミックスイン機能

上でSassのミックスイン機能 (ここではbox_sizing) を使っています。ミックスイン機能を使うことで、CSSルールのグループをパッケージ化して複数の要素に適用することができます。例えば次のようなコードは、

.debug_dump {
  .
  .
  .
  @include box_sizing;
}

このように変換されます。

.debug_dump {
  .
  .
  .
  -moz-box-sizing:    border-box;
  -webkit-box-sizing: border-box;
  box-sizing:         border-box;
}

7.1.2 Users リソース

目標:ユーザー情報をWebアプリケーション上に表示すること
ユーザープロフィールページを作成するには、その前にデータベースにユーザーが登録されている必要があります。これはいわゆる「卵が先か鶏が先か」問題

$rails c で新規ユーザを作成して置く事

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

サンプルアプリケーションにこの1行を追加すると、ユーザーのURLを生成するための多数の名前付きルートと共に、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになる。下参考

image.png

上記によって、ルーティングが有効になります。ただし、ルーティング先のページはまだありません

1,viewにファイルを作成し仮のビューとして下を追加

app/views/users/show.html.erb
 <%= @user.name %>, <%= @user.email %>

2,showアクションの追加

app/controllers/users_controller.rb
 class UsersController < ApplicationController

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

  def new
  end
end

こではUserモデルのfindメソッドを使ってデータベースからユーザーを取り出します
ユーザーのid読み出しにはparamsを使いました。Usersコントローラにリクエストが正常に送信されると、params[:id]の部分はユーザーidの1に置き換わります

3,/user/1にアクセスしてみて確認

7.1.3 byebug gemによるdebuggerメソッド

debugメソッドより直接的なデバック方法

app/controllers/users_controller.rb
 class UsersController < ApplicationController

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

  def new
  end
end

userコントローラにdebuggerメソッドを差し込んだあとブラウザで/user/1にアクセスし、ターミナルをみてみると

byebugプロンプトが表示されているはず
このプロンプトではRailsコンソールのようにコマンドを呼び出すことができて、アプリケーションのdebuggerが呼び出された瞬間の状態を確認することができます。(抜けるのにはCtrl-Dを押す)

(byebug) @user.name
"Example User"
(byebug) @user.email
"example@railstutorial.org"
(byebug) params[:id]
"1"

確認がおわったらshowアクション内のdebuggerの行を削除してしまいましょう

今後Railsアプリケーションの中でよく分からない挙動があったら、上のようにdebuggerを差し込んで調べてみましょう。トラブルが起こっていそうなコードの近くに差し込むのがコツです。byebug gemを使ってシステムの状態を調査することは、アプリケーション内のエラーを追跡したりデバッグするときに非常に強力なツールになります。

7.1.4 Gravatar画像とサイドバー

http://gravatar.com/
プロフィール画像の導入をめざす

app/views/users/show.html.erb
 <% provide(:title, @user.name) %>
<h1>
  <%= gravatar_for @user %>
  <%= @user.name %>
</h1>

デフォルトでは、ヘルパーファイルで定義されているメソッドは自動的にすべてのビューで利用できます。ここでは、利便性を考えてgravatar_forをUsersコントローラに関連付けられているヘルパーファイルに置くことに。

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

Gravatarのホームページにも書かれているように、GravatarのURLはユーザーのメールアドレスをMD5という仕組みでハッシュ化しています。Rubyでは、Digestライブラリのhexdigestメソッドを使うと、MD5のハッシュ化が実現できます。

メールアドレスは大文字と小文字を区別しませんが、MD5ハッシュでは大文字と小文字が区別されるので、Rubyのdowncaseメソッドを使ってhexdigestの引数を小文字に変換

続いてユーザー情報をページ左に集約するためのサイドバーづくり

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>

CSS

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;
}

演習(7.1.4.2 , 3)

良く分からず。
編集後のファイルだけ下に記す

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 ユーザー登録フォーム

目標:signupページの実装

7.2.1 form_forを使用する

ユーザー登録ページで重要な点は、ユーザー登録に欠かせない情報を入力するためのform_forです。このメソッドはActive Recordのオブジェクトを取り込み、そのオブジェクトの属性を使ってフォームを構築

先にビューを整えて次の節でかみ砕いていく

1,/signup のルーティングは、Usersコントローラーのnewアクションに既に紐付けられているためnewアクションを編集

app/controllers/users_controller.rb
 class UsersController < ApplicationController

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

  def new
    @user = User.new
  end
end

2,newビューの編集(html,css)

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>
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.2.2 フォームHTML

form_forとはなんぞや

rb:<%= form_for(@user) do |f| %>
  .
  .
  .
<% end %>

doキーワードは、 form_forが1つの変数を持つブロックを取ることを表します。この変数fは “form” のfです。
このfオブジェクトは、HTMLフォーム要素 (テキストフィールド、ラジオボタン、パスワードフィールドなど) に対応するメソッドが呼び出されると、@userの属性を設定するために特別に設計されたHTMLを返します

ブラウザ上で生成されたHTMLソースがこちら

<form accept-charset="UTF-8" action="/users" class="new_user"
      id="new_user" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden"
         value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" />
  <label for="user_name">Name</label>
  <input id="user_name" name="user[name]" type="text" />

  <label for="user_email">Email</label>
  <input id="user_email" name="user[email]" type="email" />

  <label for="user_password">Password</label>
  <input id="user_password" name="user[password]"
         type="password" />

  <label for="user_password_confirmation">Confirmation</label>
  <input id="user_password_confirmation"
         name="user[password_confirmation]" type="password" />

  <input class="btn btn-primary" name="commit" type="submit"
         value="Create my account" />
</form>

それぞれラベル、ID,Class等がつくられていることにきづくこと。

そのなかでもユーザーの作成で重要なのはinputごとにある特殊なname属性です。

<input id="user_name" name="user[name]" - - - />
.
.
.
<input id="user_password" name="user[password]" - - - />

Railsはnameの値を使って、初期化したハッシュを (params変数経由で) 構成します。このハッシュは、入力された値に基づいてユーザーを作成するときに使われます

次に重要な要素は、formタグ自身です。
Railsはformタグを作成するときに@userオブジェクトを使います。すべてのRubyオブジェクトは自分のクラスを知っている ので、Railsは@userのクラスがUserであることを認識します。また、@userは新しいユーザーなので、 Railsはpostメソッドを使ってフォームを構築すべきだと判断します。
なお、新しいオブジェクトを作成するために必要なHTTPリクエストはPOSTなので、このメソッドはRESTfulアーキテクチャとして正しいリクエスト。

<form action="/users" class="new_user" id="new_user" method="post">#/users に対してHTTPのPOSTリクエスト送信する

7.3 ユーザー登録失敗

解り易く失敗からすすめていく

7.3.1 正しいフォーム

Usersリソース表から、/usersへのPOSTリクエストはcreateアクションに送られます

だったらcreateアクションを整備しよう

app/controllers/users_controller.rb
 class UsersController < ApplicationController

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

  def new
    @user = User.new
  end

  def create
    @user = User.new(params[:user])    # 実装は終わっていないことに注意!
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new'
    end
  end
end

この時点で無効なユーザーデータ登録をしてみる(submit)
するとデバック情報としてパラメーターハッシュのuser部分が

image.png

Usersコントローラにparamsとして渡されているものである。

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

上は下ようなコードとほぼ同じである、ということです。

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

このキーが上のinputタグのname属性にそれぞれ割り当てられている

Strong Parameters

ここで問題になるのが悪意のあるユーザーによってアプリケーションのデータベースが書き換えられてしまう問題があったということ
例えばURLの末尾に?admin=1などを書き込むことが挙げられます。adminとはWebサイトの管理者であるかどうか(管理者であればadmin="1")をチェックする属性ですが、/users/new?admin=1と打ち込むだけで、paramsにadmin属性が含まれてしまうのです。
image.png

その対策としてrequireメソッドやpermitメソッドを使った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

user_paramsという外部メソッドを使って適切に初期化したハッシュを返し、params[:user]の代わりとして使われます。

Privateキーワード

※詳しくは9.1で

このuser_paramsメソッドはUsersコントローラの内部でのみ実行され、Web経由で外部ユーザーにさらされる必要はないためprivateキーワードで外部アクセス不可にしている

7.3.3 エラーメッセージ

目標:登録失敗時にブラウザに原因説明を原因によって表示させる

$ rails console
>> user = User.new(name: "Foo Bar", email: "foo@invalid",
?>                 password: "dude", password_confirmation: "dude")
>> user.save
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]

errors.full_messagesオブジェクトはエラーメッセージの配列をもっていることをうまく利用すれば出来る

1,パーシャルをつくる

Rails全般の慣習として、複数のビューで使われるパーシャルは専用のディレクトリ「shared」によく置かれます(view内にディレクトリ作成 省略)

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 %>

2,new.htmlを編集

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| %>
      <%= 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>

※form-controlというCSSクラスも一緒に追加することで、Bootstrapがうまく取り扱ってくれるようになります

3,CSS

app/assets/stylesheets/custom.scss
 .
.
.
/* forms */
.
.
.
#error_explanation {
  color: red;
  ul {
    color: red;
    margin: 0 0 30px 0;
  }
}

.field_with_errors {
  @extend .has-error;
  .form-control {
    color: $state-danger-text;
  }
}

any?メソッド

これはempty?メソッドと互いに補完します。

>> user.errors.empty?
=> false
>> user.errors.any?
=> true

文字列に対してempty?メソッドを使うと、Railsのエラーオブジェクトに対しても使えます。オブジェクトが空の場合はtrue、 それ以外の場合は falseを返します。
any?メソッドはちょうどempty?と逆の動作で、要素が1つでもある場合はtrue、ない場合はfalseを返します。

pluralizeメソッド

これはhelperオブジェクトを通して、Railsコンソールからも試してみることができます。

>> helper.pluralize(1, "error")
=> "1 error"
>> helper.pluralize(5, "error")
=> "5 errors"

pluralizeの最初の引数に整数が与えられると、それに基づいて2番目の引数の英単語を複数形に変更したものを返します。このメソッドの背後には強力なインフレクター (活用形生成) があり、不規則活用を含むさまざまな単語を複数形にすることができます。

>> helper.pluralize(2, "woman")
=> "2 women"
>> helper.pluralize(3, "erratum")
=> "3 errata"

pluralizeを使うことで、コードは次のようになります。

<%= pluralize(@user.errors.count, "error") %>

このコードは、例えば "0 errors"、"1 error"、"2 errors" などのように、エラーの数に応じて活用された単語を返します。これにより、"1 errors" のような英語の文法に合わない文字列を避けることができます

7.3.4 失敗時のテスト

簡単に。。。

統合テストのファイル名はusers_signupとします。

$ rails generate integration_test users_signup
      invoke  test_unit
      create    test/integration/users_signup_test.rb
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#サインアップ後のUser数が変わっていないか
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'#送信失敗時newアクションが再描画されているか
  end
end

演習問題後のコード(#解説)

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'
    assert_select 'div#error_explanation'#/shared/_error_messages.html にエラー説明があるか
    assert_select 'div.alert'#上と同義
    assert_select 'form[action="/signup"]'#正しいURLにpostがおくられているかどうか
end
  .
  .
  .
end

ユーザー登録成功

目標:新規ユーザーを実際にデータベースに保存できるようにし (もちろんフォームが有効な場合に)、ユーザー登録フォームを完成させましょう。まずは、ユーザーを保存できるようにします。保存に成功すると、ユーザー情報は自動的にデータベースに登録されます。次にブラウザの表示をリダイレクトして、登録されたユーザーのプロフィールを表示します。ついでにウェルカムメッセージも表示しましょう

7.4.1 登録フォームの完成

Createアクションに対するビューを設定してないのでまずそこから

とここで、成功した場合は新しいページにアクセスするのではなく、すでにつくったプロフィールページへリダイアルさせる

app/controllers/users_controller.rb
 class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user#これはredirect_to user_url(@user)
    else
      render 'new'
    end
  end

  private

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

※redirect_to @userというコードから (Railsエンジニアが) user_url(@user)といったコードを実行したいということを、Railsが推察してくれた結果になります

4.4.2 flash

登録完了後に表示されるページにメッセージを表示し (この場合は新規ユーザーへのウェルカムメッセージ)、2度目以降にはそのページにメッセージを表示しないようにするというもの

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

app/controllers/users_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

  private

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

アクション内でリダイレクトした直後のページで表示できるようにする

app/views/layouts/application.html.erb
 <!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <% flash.each do |message_type, message| %>#each構文でキーと値をそれぞれ変数代入
        <div class="alert alert-<%= message_type %>"><%= message %></div>#CSSクラスに利用して値によって適用されるクラスを変更できるようにする。
      <% end %>
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
    .
    .
    .
  </body>
</html>

レイアウトに差し込む。

7.4.4 成功時のテスト
データベースの中身がただしいのかどうか。すなわち有効な情報を送信してユーザーが作成されるのかのテスト

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:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
    assert_not flash.empty?#シンプルに空ではないよな??テスト
  end
end

assert_differenceメソッド

失敗時にかいたassert_not_differenceとは逆に、実行後のUserインスタンスの数が違うことを確かめるメソッド

第一引数に文字列 (’User.count’) を取り、assert_differenceブロック内の処理を実行する直前と、実行した直後のUser.countの値を比較します。第二引数はオプションですが、ここには比較した結果の差異 (今回の場合は1) を渡します

follow_redirect! メソッド

POSTリクエストを送信した結果を見て、
テスト対象を!指定されたリダイレクト先に移動するメソッドです。したがって、この行の直後では’users/show’テンプレートが表示されているはずです

assert_template の真意

ユーザー登録に成功させた後に、どのテンプレートが表示されているのか検証していることにも注目してください。
このテストを成功させるためには、UserのルーティングとUserのshowアクション そしてshow.html.erbビューがそれぞれ正しく動いている必要があります。

ということはユーザープロフィールに関するほぼ全て (例えばページにアクセスしたらなんらかの理由でエラーが発生しないかどうかなど) をテストできていることに注目

content_tag ヘルパー

現時点ではよくわからない。

要点:
DRY

下記参照
http://blog.livedoor.jp/sasata299/archives/51309025.html

7.5 プロのデプロイ(SSL)

別投稿で少しまとめてある為下
SSL/TLS項参照

https://qiita.com/krppppp/items/f80b66913e0c8b2566ca

むずかしくなってきた~~