1
1

More than 3 years have passed since last update.

【第7章】Railsチュートリアル 5.1(第4版) ユーザー登録

Last updated at Posted at 2020-01-04

大まかな流れの自己整理が目的のため、不足・誤り等あれば追記&訂正していきますのでご指摘頂けますと幸いです:bow_tone1:
なお、筆者はYassLabさんの動画版で学んでいるため、本記事は「チュートリアル sample_app」+「他補足」個人的に「電子ページ以上に分かりやすい!」と感じた解説部分+参考記事を整理してみようと試みた劣化の内容寄りになってます。

7.1 ユーザーを表示する

params

Railsで送られてきた値を受け取るためのメソッド(その後Railsが保存を行う)。中身はハッシュ。
主に、以下の2つ
・投稿フォームなどPOSTで送信されたデータ
・検索フォームなどGETで送信されURLにクエリとして入るデータ

ex.params[:id] → ユーザなどのid

<参考>
【Rails】paramsについて徹底解説!
【Rails入門】params使い方まとめ

debug(メソッド)

paramsの中身(表示画面)を出力してくれる。putsのような扱い。
  

本番環境に展開したアプリケーションではデバッグ情報を表示したくないので、後置if文でデベロッパー確認を追加する。
(後置if文は1行で済むときによく使用される)

app/views/layouts/application.html.erb 内に下記追加


<%= debug(params) if Rails.env.development? %>

補足

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

app/assets/stylesheets/custom.scssに
デバッグ表示を整形するための追加と、Sassのミックスインを追加する(ここでは省略)
  
/users/1 のURLを有効にするため、routes(るーつ、らうつ)ファイルsignup下に追加

resources :users

 
確認(今回注目するのはnewアクションとshowアクション)

$ rails routes
           Prefix Verb   URI Pattern                  Controller#Action
             root GET    /                            static_pages#home
static_pages_home GET    /static_pages/home(.:format) static_pages#home
             help GET    /help(.:format)              static_pages#help
            about GET    /about(.:format)             static_pages#about
          contact GET    /contact(.:format)           static_pages#contact
           sighup GET    /sighup(.:format)            users#new
            users GET    /users(.:format)             users#index
                  POST   /users(.:format)             users#create
         new_user GET    /users/new(.:format)         users#new
        edit_user GET    /users/:id/edit(.:format)    users#edit
             user GET    /users/:id(.:format)         users#show
                  PATCH  /users/:id(.:format)         users#update
                  PUT    /users/:id(.:format)         users#update
                  DELETE /users/:id(.:format)         users#destroy

putリクエスト  → 全部(旧残り)
patchリクエスト → 欲しい情報の一部(現在主流)

 
Usersコントローラにshowアクションを追加する(showアクションはGET /users/:id と連動)。挿入場所(順番)は可読性を考慮し出来ればRESTfulなルート順(表)が望ましい。
@userはインスタンス変数とされ、showアクション外(例えばviewであるshowテンプレート: => app/views/users/show.html.erb)からも呼び出すことが出来るため、「paramsで持ってきたユーザid(リクエスト値)を@userに格納する」ような流れ。

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

 

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) ユーザーを削除するアクション

 
サンプルアプリケーションを既にHeroku上にデプロイしている場合は、heroku run rails consoleというコマンドを打つことで、本番環境を確認することができます。

  $ heroku run rails console
  >> Rails.env
  => "production"
  >> Rails.env.production?
  => true

デバッガー(debugger)

追記して稼働させるとゆっくり読み込み中になる

def show
    @user = User.find(params[:id])
    # => app/views/users/show.html.erb
    debugger
  end

rails サーバを立ち上げると、止まってる場所がわかる


def show
    4:     @user = User.find(params[:id])
    5:     # => app/views/users/show.html.erb
    6:     debugger
=>  7:   end
    8:   
    9:   def new
   10:   end
   11: end
(byebug)

コマンドはlist,nextなど。exit、ctrl+cで終了。
使い終わったらコメントアウト。
  
 
show.html.erbにgravatar_forヘルパーメソッドを使い、WordPress系アバター画像のGravatarの画像を利用できるようにする。

<% provide(:title, @user.name) %>
<h1>
  <%= gravatar_for @user %>
  <%= @user.name %>
</h1>

ヘルパーメソッドとしてusers_helper.rbに追加する。
最後の行のimage_tag(url、alt&class属性付与)がgravatar_forに返される。
なお、gravatar_forメソッドでのemailは(has_secure_passwordのように)Digest::MD5によってハッシュ化されている。

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

image.png

この時点でデフォルト画像のまま表示されるが、show.html.erbにasideタグ(サイドバーなど補足として付け加えたいときに便利なもの)を付け加える。

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

image.png

saasに追記してフォーマット等調整。

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

スクリーンショット 2020-01-03 13.53.45.png
  

7.2 ユーザー登録フォーム

コントローラのnewアクションに空のオブジェクトを作成して@user変数を追加する

  def new
    @user = User.new
  end

  
view画面のnewに追記。
form_forはブロック付き引数で、ブロック内部では例として「email」というキーの中にユーザのバリューが入るイメージ。
「f.label」はラベルのなので削っても問題ないが、あるとユーザ視点で何を入力していいか分かりやすい。(変更イメージの参考:[rails]ActiveModelを使ったフォームのラベル名を変更する
「f.submit」は"Create my account"というボタンからこれらのデータをnewアクション→createアクションへ送る(次のアクションへ)。

<h1>Users#new</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>

formのscss追記

/* forms */

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

input {
  height: auto !important;
}

スクリーンショット 2020-01-03 16.29.08.png

そのまま作るとエラーが起きるが、「newアクション移行→createアクションがないよ」という表示なので正常。
image.png

createアクション追加&renderでビュー画面newに戻り、登録情報確認(試験的にfoobar入れてます)

  def create
    @user = User.new
    render 'new'
  end

image.png

7.3 ユーザー登録失敗

ユーザー登録の失敗に対応できるcreateアクション

オプション引数について

ハッシュのハッシュなので、userの中に[:name],[:email],[:password]があるので:userで省略。

  def create
    # オプション引数はキーワードにシンボルが入ってバリューに値が入ってる集合体
    #  → User.new(name: ..., email:, ...)
    # @user.name = params[:user][:name]
    # @user.user = params[:user][:email]
    # @user.password = params[:user][:password]
    # 1行で完結
    @user = User.new(params[:user]) 
    if @user.save #=> Validation
      # Sucess
    else
      #Failure
      render 'new'
    end
  end

image.png

エラー登場。createの直後のparamsはユーザが送る情報なのでいろいろな情報を送る(いじる)ことができるので、今後adminなどを入れていく過程で悪意あるユーザからDB書き換えたれるなどのマスアサインメント脆弱性を回避するRails4.0移行実装の機能。paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外を許可しないようにしたいので、requireなどを追加したuser_paramsメソッドをコントローラに追加してメソッドで対応する。

users_controller.rb
   def create
    @user = User.new(user_params)
    if @user.save #=> Validation
      # Sucess
    else
      #Failure
      render 'new'
    end
  end

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

Rails consoleからメソッド「.errors.full_messages」でエラー詳細表示

空のユーザ作成後save失敗から.full_messagesメソッドでエラーの詳細表示ができる。
(今回はいろいろ空だったことが原因)

2.6.3 :002 > @user = User.new
 => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil> 
2.6.3 :003 > @user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
 => false 
2.6.3 :004 > @user.save
   (0.1ms)  begin transaction
  User Exists (0.4ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  rollback transaction
 => false 
2.6.3 :005 > @user.errors.full_messages
 => ["Name can't be blank", "Email can't be blank", "Email is invalid", "Password can't be blank", "Password can't be blank", "Password is too short (minimum is 6 characters)"] 

ユーザ登録時にエラーメッセージが出るよう追記する。sharedディレクトリもviewディレクトリ下に作る。

<h1>Users#new</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>

 
パーシャル(Viewでの似たようなコードを一つでまとめてるもの)を作るがアンダーバーなので注意

$ cd app/views/
$ mkdir shared
$ c9 open _error_messages.html.erb

 
「.errors.eny?」 → save,validなど何か実行されてエラーがなければfalse(何も表示しない)、「.count」あるので1個以上あればtrueを返す。pluralizeは"error"を個数に応じて単数・複数形で判断してくれる。

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

image.png

「Password can't be blank」が2つ出る理由は、「has_secure_password」と「validates presence」で引っかかっているためのバグ。
応急処置として、has_secure_password内に下記を追記するなどがある。

 allow_nil: true

 
エラー表示のSCSS追加

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

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

   
Chromeの検証でエリア選択すると
スクリーンショット 2020-01-03 20.28.58.png

<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post">

とあり、
アクションが「/users」、メソッドが「post」から、「postリクエストを/usersに送りつける」ことが分かる。
ポストリクエストの流れは下記から確認でき、
「POST」リクエストが「/users」に送られると、「users」コントローラの「create」アクションが反応する。

$ rails routes
           Prefix Verb   URI Pattern                  Controller#Action
             root GET    /                            static_pages#home
static_pages_home GET    /static_pages/home(.:format) static_pages#home
             help GET    /help(.:format)              static_pages#help
            about GET    /about(.:format)             static_pages#about
          contact GET    /contact(.:format)           static_pages#contact
           signup GET    /signup(.:format)            users#new
            users GET    /users(.:format)             users#index
                  POST   /users(.:format)             users#create
         new_user GET    /users/new(.:format)         users#new
        edit_user GET    /users/:id/edit(.:format)    users#edit
             user GET    /users/:id(.:format)         users#show
                  PATCH  /users/:id(.:format)         users#update
                  PUT    /users/:id(.:format)         users#update
                  DELETE /users/:id(.:format)         users#destroy

「assert_no_difference」を使った、失敗時のテスト

インテグレーションテスト(結合テスト)から

$ rails generate integration_test users_signup
Running via Spring preloader in process 4226
      invoke  test_unit
      create    test/integration/users_signup_test.rb

 

自動生成されたものに追記。「signup_path」にgetリクエストを送りつける。
postリクエストをusers_pathに送り、その時にパラメータ:{ user: { name:,email:,password:,password_confirmation}}(オプション引数)を送りつける。
assert_no_differenceは呼び出す前後で値に違いがないことを主張するテストで、引数に(User.count)が入っていることから、「ユーザ数を覚えた後にデータを投稿してみて、ユーザ数が変わらないかどうかを検証するテスト」になる(公式より)。

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 t

補足(エラー 「undefined local variable or method `signup_path'」)

筆者の場合、この時点で下記のようなエラーが出ました。

$ rails t
Running via Spring preloader in process 4176
Run options: --seed 61612

# Running:

.E..........

Finished in 0.618448s, 19.4034 runs/s, 30.7221 assertions/s.

  1) Error:
UsersSignupTest#test_invalid_signup_information:
NameError: undefined local variable or method `signup_path' for #<UsersSignupTest:0x00000000065b4838>
    test/integration/users_signup_test.rb:6:in `block in <class:UsersSignupTest>'

12 runs, 19 assertions, 0 failures, 1 errors, 0 skips

 
「自分でアカウント追加して遊んだから講義とユーザ数ずれちゃったせいかな?」と考えたりしてましたが(このテストでは前後数のみで関係ない)
ルート(routes.rb)、ビュー(home.html.erb)でsignup_pathがsighup_pathにタイプミスしてました:innocent:

 

7.4 ユーザー登録成功

createアクションにリダイレクトを追加する(省略過程あり)。


def create
    @user = User.new(user_params)
     # Sucess
      # redirect_to user_path(@user.id)
      # user_pathの引数デフォルトがidなので「.id」省略可、
      # redirect_to user_path(@user)
      # さらにredirect_toのデフォルト挙動としてユーザオブジェクトを渡すとuser_pathになるので
      redirect_to @user
      #GETリクエスト(が右にいく) => "/users/#{@user.id}" => showアクションが動く
    else
      #Failure
      render 'new'
    end
  end

flash

登録完了後に表示されるページにメッセージを表示する (この場合は新規ユーザーへのウェルカムメッセージ)。
createアクション(if文直下)と、ビュー(yield直前)に追加

users_controller.rb

flash[:success] = "Welcome to the Sample App!"
application.html.erb
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>

↑(ビュー側のerb埋め込みruby)の補足

ハッシュはキー+バリューの仕組みになっていて、
eachメソッドで呼び出すと、message_type(キー:success)とmessage(バリュー:"Welcome to the Sample App!")の関係になっている。
また、classの中にmessageを埋め込んでいる。

  
 
登録動作を確認してみると、5番目のユーザで作成できてる(成功!)。
flashは一度だけなのでリロードすればメッセージ(緑色)は消える。

スクリーンショット 2020-01-04 13.42.55.png

成功時のテスト

成功時、ユーザのカウントが1増えてればokという内容。
基本的にはshowテンプレートが表示されていれば🙆‍♂️

users_signup_test.rb
  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'
  end

問題がなければ、herokuにデプロイして本番環境で確認。
スクリーンショット 2020-01-04 13.58.58.png

URLを確認すると「https://〇〇.herokuapp.com/users/1」となっており、
本番環境(heroku)では1人目のユーザであることが分かる。

7.5 プロのデプロイ

本番環境でのSSL

herokuのサブドメイン「https://〇〇.herokuapp.com(2番目)/users/1」を使っている内はサービス上SSL化(🔒Secure)されている。(Googleなどではhttpより検索順位が上位になったりするので重要であり、将来的に独自ドメインを使っていく際は自分で証明書の発行が必要になる。)

https(SSL)通信を本番環境で強制するやり方として、production(本番環境).rbに下記を追記(コメントアウト解除してtrueに)する。
これにより、もしhttpでアクセスしても問答無用でhttpsに切り替わる。

config/environments/production.rb
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

 

本番環境に適したWebサーバーを構築する

heroku推奨の設定ファイルをpumaに書き換える。

config/environments/production.rb
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/
  # deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

  
Pumaがheroku上で使うようにプロックファイル(./Procfile)(はデフォルトでないので)作成し、定義する。

/sample_app $ c9 open Procfile
web: bundle exec puma -C config/puma.rb

一応テスト走らせた後、
再度herokuへデプロイ(コミットメッセージが分かるように)して終了。

$ git add -A
$ git commit -m "Use SSL and the Puma webserver in production"    

感想ほか

講義が非常に分かりやすく全く挫折することなく終えました(講師:安川さん、ありがとうございました!!)。
教材模写(受け身)が中心だったので、忘れた部分含め2周目自力で進めて公式ドキュメント漁って読み込むなどもっと頭を悩ませる必要はあるかなと。
最後までお読み頂きありがとうございました!!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1