Help us understand the problem. What is going on with this article?

Railsチュートリアル 第7章 ユーザー登録 - ユーザー登録フォーム

More than 1 year has passed since last update.

As-Is;To-Be

ユーザー登録機能に関し、現時点の状態をまとめると以下の通りになります。

  • ユーザープロフィールページがひとまず形になった
  • ユーザー登録フォームは未だ手つかず

すなわち、「/users/new ないしは/signup というページにスタブのみが存在しており、Webブラウザを使ったユーザー登録がまだできない」という状況です。

この状態から、「Railsチュートリアルで示されているユーザー登録フォームのモックアップのようなユーザー登録フォームを完成させ、Webブラウザを使ってユーザー登録できるようにする」というところまでがこの文章の範囲です。

forms_forを使用する

Usersコントローラのnewアクションに@user変数を紐付ける

過去の学習の中で、config/routes.rbには、既に「/signup を、Usersコントローラのnewアクションに紐付ける」というルーティングを定義していました。次に必要となるのは、「Usersコントローラのnewアクションを、User(モデル)オブジェクトのインスタンスである@userに紐付ける」という手順です。

変更対象のコードはapp/controllers/users_controller.rbです。以下の変更を行っていきます。

app/controllers/users_controller.rb
  class UsersController < ApplicationController

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

    def new
+     @user = User.new
    end
  end

只今users#newに定義された動作は、「Userオブジェクトの新規インスタンスを生成し、それを@userとする」という動作ですね。

ユーザー登録フォームのHTMLコードおよびSCSSを記述する

ユーザー登録フォームのHTMLコードは以下となります。「フォーム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| %>
      <%= 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;
+ }
...略

この段階で、ユーザー登録フォームをWebブラウザで表示した結果は以下のとおりです。それらしいフォームが出来上がっていますね。

スクリーンショット 2019-10-17 12.45.49.png

演習 - form_forを使用する

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

app/views/users/new.html.erbを、以下のように変更します。

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.label :nome %>
-       <%= f.text_field :name %>
+       <%= f.text_field :nome %>
        ...略
      <% end %>
    </div>
  </div>
  • HTTP側のレスポンスは「500 Internal Server Error」
  • Rails側のエラーメッセージはActionView::Template::Error (undefined methodnome' for #<User:0x00007fbdc16d0408>`

以上のようになります。フォームの定義そのものを@userから引いているので、Userオブジェクトに存在しない:nomeという属性が引けなくてエラーを返しています。

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

app/views/users/new.html.erbを、以下のように変更します。

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 |foobar| %>
      <%= foobar.label :name %>
      <%= foobar.text_field :name %>

      <%= foobar.label :email %>
      <%= foobar.email_field :email %>

      <%= foobar.label :password %>
      <%= foobar.password_field :password %>

      <%= foobar.label :password_confirmation, "Confirmation" %>
      <%= foobar.password_field :password_confirmation %>

      <%= foobar.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

こうした用途にfoobarという名前が好ましくない理由としては、以下のようなものが考えられます。

  • ブロック内でイテレータとして用いる一時変数の名前は、短いことが望ましい
    • 一般に、変数の長さは生存期間と正の相関をもたせることが好まれる
  • foobarという名前は、ブロックスコープの一時変数の名前として使うには長すぎる
    • そうした変数の名前には、ijなどといった名前が使われるのが一般的
    • foobarは、その長さからして、メソッドスコープの生存期間を持っていることが期待されるであろう

このあたりのルール設定については、名著として名高いリーダブルコードが参考になるかと思います。

フォームHTML

フォームの内容を定義するためのブロック

フォームHTMLの根幹となるのは、form_forからendまでのブロックです。

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

重要なのは以下のポイントです。

  • @userをフォームの処理対象のデータとする
  • 1つの変数をとるブロックである
    • 変数(イテレータ)オブジェクトの名前はfである
  • fオブジェクトには、HTMLフォームの様々な要素を返すメソッドが定義されている
    • labeltext_fieldpassword_fieldなど

ブロック内でフォームの諸アイテムを定義する

ラベルと入力領域の組

<%= f.label :name %>
<%= f.text_field :name %>

例えば上記のコードは、Userモデルのname属性を設定するために用いる、ラベルと(単一行)テキスト入力領域の組を生成します。上記のコードに対応するHTMLは以下の通りです。

<label for="user_name">Name</label>
<input type="text" name="user[name]" id="user_name" data-kpxc-id="user_name">

:nameの他、:email:passwordpassword_confirmationに対しても、同様にラベルとテキスト入力領域の組が定義されています。

また、Railsチュートリアル本文では、以下のポイントについても言及されています。

  • type="text"type="email"の違い
    • いずれも入力された内容をそのまま表示する単一行の入力領域である
    • type="text"の場合、スマートフォンなどで表示すると通常のキーボードが表示される
    • type="email"の場合、スマートフォンなどで表示するとEメールアドレス入力に特化したキーボードが表示される
  • type="password"の場合、入力した内容が隠蔽される単一業の入力領域となる

フォーム内諸要素のname属性

<input type="text" name="user[name]" id="user_name" data-kpxc-id="user_name">
<input type="email" name="user[email]" id="user_email" data-kpxc-id="user_email" kpxc-username-field="true">
<input type="password" name="user[password]" id="user_password" data-kpxc-id="user_password" kpxc-password-generator="true" kpxc-pwgen-next-field-id="user_password_confirmation" kpxc-pwgen-next-is-password-field="false" kpxc-pwgen-next-field-exists="true" kpxcfields-onchange="true">
<input type="password" name="user[password_confirmation]" id="user_password_confirmation" data-kpxc-id="user_password_confirmation" kpxc-password-generator="true" kpxc-pwgen-next-field-exists="false" kpxcfields-onchange="true" kpxc-username-field="true">

上記はフォームHTMLのinput要素のみを取り出したものです。モデルオブジェクトとの関係において、これらinput要素の中で最も重要なのはname属性です。name属性の値は、当該フォームのsubmit時に生成されるクエリ文字列において、クエリパラメータの名前として用いられます(クエリ文字列およびクエリパラメータについては後で解説を入れます)。

form要素そのもの

<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post" kpxcform-initialized="true" kpxcusername="user_email" kpxcpassword="user_password">
</form>

上記はフォームHTMLのform要素のみを取り出したものです。下記の埋め込みRubyに対応します。

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

また、Usersコントローラのapp/views/users/new.html.erbに対応する部分は下記になります。

def new
  @user = User.new
end

モデルオブジェクトとの関係において重要なポイントは以下となります。

  • form要素の生成に@userが使われている
    • Railsにより、Userクラスのオブジェクトであることが認識される
  • このフォームのメソッドはpostであり、このフォームで生成されるHTTPリクエストの種類はPOSTである
    • Usersコントローラにより、app/views/users/new.html.erbに対応する@userUser.newであることが示されている
    • Railsは、newで生成された@userに対し、postメソッドでフォームを構築すべきと自動で判断する
    • RESTの原則において、「新しいオブジェクトを生成する」という処理に対応するHTTPリクエストはPOSTであるため、RESTfulなアーキテクチャとして正しい挙動をする
  • form要素のaction属性は/usersである
    • このフォームでsubmitされたときの処理は、「/usersPOSTリクエストを送出する」というものになる

Railsで内部的に用いられるために自動で追加される要素

<input name="utf8" type="hidden" value="&#x2713;">
<input type="hidden" name="authenticity_token" value="wwiu2NvcdY59JxI/Eq96qLM88Oe8oAsBPMM7RQ1JT4xO47Pt+wSscw8fK40/S+DAa8h5ziATCnlV5gxUWDsiag==">

type="hidden"である2つの要素は、Railsで内部的に用いられるために自動で追加される要素です。以下の役割を持ちます。

  • Webブラウザが正しい文字コードを送信できるようにする
    • HTTPリクエストの内容のutf8属性の値に&#x2713;(チェックマーク ✓)を設定する
  • データベースに対する攻撃を防ぐためのトークンを送出する

submitで生成されるHTTPリクエスト

フォームのsubmitボタンが押されると、フォームの入力内容からHTTPリクエストが生成されます。今回のフォームHTMLにおいては、生成されるHTTPリクエストは、以下のような特徴を持ちます。

  • 生成されるHTTPリクエストの種類はPOST
    • form要素のmethod属性で定義される
    • RESTfulアーキテクチャを前提とした場合、何らかの新規オブジェクトが生成されることが期待される
  • HTTPリクエストの本体(body)内にクエリ文字列が格納される

クエリ文字列とは

  • クエリ文字列は、1つ以上のクエリパラメータで構成される
    • 複数のクエリパラメータがある場合、&でつなぐ
  • クエリパラメータは名前=値という文字列である
  • クエリパラメータの名前はフォーム各部品要素のタグのname属性によって与えられる
  • input要素の場合、クエリパラメータの値はinputタグのvalue属性によって与えられる
  • type=textinput要素においては、実際に入力領域に入力された値

演習 - フォームHTML

1. Learn Enough HTML to Be DangerousではHTMLをすべて手動で書き起こしていますが、なぜformタグを使わなかったのでしょうか? 理由を考えてみてください。

考えられる理由としては以下です。

  • 別のシステムに送出するためのHTTPリクエストを生成する必要がない
    • 単にHTMLのチュートリアルであり、HTTPリクエストの生成を要するシステムではない
  • 必要がない機能を実装すると危険性が発生する
rapidliner00
* 現在のところは、エンジニアに憧れる非エンジニア * エンジニア的な業務効率化・改善に興味あり
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away