Railsチュートリアル:7章


7章。ユーザー編続き。

まずはいつも通り、ブランチ作成。

git checkout -b sign-up

リスト 7.1: サイトのレイアウトにデバッグ情報を追加する
app/views/layouts/application.html.erb

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

追加。

リスト 7.2: デバッグ表示を整形するための追加と、Sassのミックスイン.
app/assets/stylesheets/custom.scss


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

追加。

dbに6章で登録したユーザーがきちんといるのかを検証。

ec2-user:~/environment/sample_app (sign-up) $ rails console
Running via Spring preloader in process 4656
Loading development environment (Rails 5.1.6)
>> User.count
   (0.1ms)  SELECT COUNT(*) FROM "users"
=> 1
>> User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "[email protected]", created_at: "2020-05-05 05:02:56", updated_at: "2020-05-05 05:02:56", password_digest: "$2a$10$zjzfkvsimhFwLB0RuXRQxOIOZSQTyUUyPYabuX7sU37...">

リスト 7.3: Usersリソースをroutesファイルに追加する
config/routes.rb

ユーザーそれぞれのページのルーティング設定。

resources :users

追加。

リスト 7.4: ユーザー情報を表示するための仮のビュー
app/views/users/show.html.erb

手動でビューを作成

<%= @user.name %>, <%= @user.email %>

インスタンス変数があることを前提にしているらしい。

リスト 7.5: Usersコントローラのshowアクション
app/controllers/users_controller.rb

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

追加。

伝家の宝刀params[:id]

説明は、、、
ユーザーのid読み出しにはparamsを使いました。Usersコントローラにリクエストが正常に送信されると、params[:id]の部分はユーザーidの1に置き換わります。つまり、この箇所は6.1.4で学んだfindメソッドの User.find(1)と同じになります。(技術的な補足: params[:id]は文字列型の "1" ですが、findメソッドでは自動的に整数型に変換されます)。

安定のさらっと。

実際に/users/1ページにアクセス。

すると説明が。

デバッグ情報からparams[:id]の値を確認できることを確認してください (図 7.6)。


action: show
controller: users
id: '1'
このid: '1'は /users/:id から取得した値です。この値を使って

User.find(params[:id])
上のコードでid=1のユーザーを検索できる、といった仕組みになっているのです

いや、まずデバックってなんなのかしらんし。

リスト 7.6: debuggerをUsersコントローラに差し込む
app/controllers/users_controller.rb

debugger

追加。

/users/1 にアクセス

504 Gateway Time-outとでる。

ターミナルをみると

[1, 10] in /home/ec2-user/environment/sample_app/app/controllers/users_controller.rb
    1: class UsersController < ApplicationController
    2: 
    3:   def show
    4:     @user = User.find(params[:id])
    5:     debugger
=>  6:   end
    7: 
    8:   def new
    9:   end
   10: end
(byebug) 

どうやらページはみれないが、続きはできそうである
ビビッて調べたら↓のサイトが
https://rakuda3desu.net/rakudas-rails-tutoria7-1/

(byebug) @user.name
"Michael Hartl"
(byebug) @user.email
"[email protected]"
(byebug) params[:id]
"1"
(byebug)   Rendering users/show.html.erb within layouts/application
  Rendered users/show.html.erb within layouts/application (0.7ms)
  Rendered layouts/_shim.html.erb (0.3ms)
  Rendered layouts/_header.html.erb (0.9ms)
  Rendered layouts/_footer.html.erb (0.6ms)
Completed 200 OK in 291161ms (Views: 293.1ms | ActiveRecord: 0.7ms)

ターミナル上で作業だけしました。

リスト 7.7: debuggerをUsersコントローラーから取り外す
app/controllers/users_controller.rb

class UsersController < ApplicationController

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

  def new
  end
end

デバッガー削除

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

とのこと。イミフ。

もう一度、users/1にアクセスしてみた。

元通りアクセスできた。よかった。

リスト 7.8: ユーザー表示ビューに名前とGravatarを表示する
app/views/users/show.html.erb

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

書き換え。

リスト 7.9: 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

追加。

dbの登録情報を変える指示。

ec2-user:~/environment/sample_app (sign-up) $ rails console
Running via Spring preloader in process 5981
Loading development environment (Rails 5.1.6)
>> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "[email protected]", created_at: "2020-05-05 05:02:56", updated_at: "2020-05-05 05:02:56", password_digest: "$2a$10$zjzfkvsimhFwLB0RuXRQxOIOZSQTyUUyPYabuX7sU37...">
>> user.update_attributes(name: "Example User",
?> ?>                        email: "[email protected]",
?> ?>                        password: "foobar",
?> ?>                        password_confirmation: "foobar")
Traceback (most recent call last):
SyntaxError ((irb):3: syntax error, unexpected tIDENTIFIER, expecting =>)
?>                        email: "[email protected]",
                          ^~~~~
>> user.update_attributes(name: "Example User",
?> ?>                        email: "[email protected]",
?> ?>                        password: "foobar",
?> ?>                        password_confirmation: "foobar")
Traceback (most recent call last):
SyntaxError ((irb):7: syntax error, unexpected tIDENTIFIER, expecting =>)
?>                        email: "[email protected]",
                          ^~~~~
(irb):7: syntax error, unexpected ',', expecting end
...l: "[email protected]",
...                              ^
(irb):8: syntax error, unexpected ',', expecting end
...            password: "foobar",
...                              ^
(irb):9: syntax error, unexpected ')', expecting end
...assword_confirmation: "foobar")
...                              ^

エラー祭り。

いろいろ試すと、

コンソールに打ち込む

user.update_attributes(name: "Example User",
?> email: "[email protected]",
?> password: "foobar",
?> password_confirmation: "foobar")

この2~4行目を一気にコピーして打ち込むとダメで一行ずつコピーしてやるといけた。

>> user.update_attributes(name: "Example User",
?> email: "[email protected]",
?> password: "foobar",
?> password_confirmation: "foobar")
   (0.1ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "[email protected]"], ["id", 1], ["LIMIT", 1]]
  SQL (3.9ms)  UPDATE "users" SET "name" = ?, "email" = ?, "updated_at" = ?, "password_digest" = ? WHERE "users"."id" = ?  [["name", "Example User"], ["email", "[email protected]"], ["updated_at", "2020-05-05 08:06:29.274815"], ["password_digest", "$2a$10$dlKmj98ZLEtoQwRbrtM.QOBPvndZXRMfo889DsWvmGHOb8bj/J6vO"], ["id", 1]]
   (6.2ms)  commit transaction
=> true

リスト 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>

変更

リスト 7.11: 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;
}

追加。

リスト 7.14: newアクションに@user変数を追加する
app/controllers/users_controller.rb

def new
    @user = User.new
  end

追加

リスト 7.15: 新規ユーザーのためのユーザー登録フォーム
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>

リスト 7.16: ユーザー登録フォームの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.17: 図 7.12のフォームの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>

確認しました。

リスト 7.18: ユーザー登録の失敗に対応できる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

書き換え

リスト 7.19: createアクションでStrong Parametersを使う
app/controllers/users_controller.rb

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

書き換え。

ここは完全にプロゲートとは違います。

エラーメッセージに関して

>> user = User.new(name: "Foo Bar", email: "foo@invalid",
?> password: "dude", password_confirmation: "dude")
=> #<User id: nil, name: "Foo Bar", email: "foo@invalid", created_at: nil, updated_at: nil, password_digest: "$2a$10$WvMnxBE8Pzi8VitiprtRzugUhSsJMWT0oP9DFA57R1N...">
>> user.save
   (0.1ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "foo@invalid"], ["LIMIT", 1]]
   (0.1ms)  rollback transaction
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]

リスト 7.20: ユーザー登録失敗時にエラーメッセージが表示されるようにする
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>

sharedがでてきたのでまたそれをつくる。

もうこの辺は感覚でしかわからん。

mkdir app/views/shared

リスト 7.21: フォーム送信時にエラーメッセージを表示するためのパーシャル
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 %>

演習があるけどうまくいきません

ec2-user:~/environment/sample_app (sign-up) $ rails console

Running via Spring preloader in process 7727
Loading development environment (Rails 5.1.6)
>> 
>> user.errors.count
Traceback (most recent call last):
        1: from (irb):2
NameError (undefined local variable or method `user' for main:Object)
Did you mean?  super
>> 
ec2-user:~/environment/sample_app (sign-up) $ rails console
Running via Spring preloader in process 7751
Loading development environment (Rails 5.1.6)
>> user.errors.count
Traceback (most recent call last):
        1: from (irb):1
NameError (undefined local variable or method `user' for main:Object)
Did you mean?  super
>> user.errors.empty?
Traceback (most recent call last):
        2: from (irb):2
        1: from (irb):2:in `rescue in irb_binding'
NameError (undefined local variable or method `user' for main:Object)
>> user.errors.any?
Traceback (most recent call last):
        2: from (irb):3
        1: from (irb):3:in `rescue in irb_binding'
NameError (undefined local variable or method `user' for main:Object)

もうこのままいく。

ここでなぞの演習

>> helper.pluralize(1, "error")
=> "1 error"
>> helper.pluralize(5, "error")
=> "5 errors"
>> helper.pluralize(2, "woman")
=> "2 women"
>> helper.pluralize(3, "erratum")
=> "3 errata"

複数形になるらしい。

リスト 7.22: エラーメッセージにスタイルを与えるためのCSS
app/assets/stylesheets/custom.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;
  }
}

追加

ページアクセスして、同じ内容いれてみた。

The form contains 4 errors.
Email is invalid
Password can't be blank
Password can't be blank
Password is too short (minimum is 6 characters)

エラーがひとつおおい&重複

なぞだけど進む。