10. フォーム

フォームによるデータ送信の仕組みについて知らない人は、まず次の資料を読むこと。

10.1. Railsにおけるフォーム機能

フォームを作るためには、次の二つの作業が必要である。

  • フォーム画面を表示するビューを作る。

  • フォームからのデータを受け取って処理をするコントローラのメソッドを作る。

10.1.1. ビューを作る

フォームの例として、 scaffold が作った、新しい学生のデータを入力するページ http://localhost:3000/students/new を見てみよう。このページのビュー app/views/students/new.html.erb をエディタで開いてブラウザ画面と比べてみよ。

  • フォームの部分は render 'form' と書いてある。

  • render は他のERBファイルを呼び出す。呼び出されるERBファイルはパーシャルと呼ぶ。

  • この場合は app/views/students/_form.html.erb (パーシャルのファイル名は _ で始まる)の処理結果がここに入る。

  • student: @student はパーシャルへ渡すデータの指定。 @student の値が student という変数に格納される。

それでは app/views/students/_form.html.erb の内容を見てみる。

  • 1行目、 form_with はフォーム全体を作るためのメソッド。

    • 変数 student には @student に入っていた Student クラスのインスタンスが入っている。

    • 変数 form には、フォームを生成するためのクラス(FormBuilder)のインスタンスが入る。

  • 2行目から12行目はエラーメッセージを出すための仕組み。とりあえず今は考えない。

  • 15行目、 form.label はHTMLの label 要素を生成する。

    • 第1引数にモデルのカラム名を書く。

    • 第2引数以降には画面に表示したい文字列とオプションを書く。文字列を省略するとカラム名が表示される。

  • 25行目、 form.submit はSubmitボタンを生成する。

    • 第1引数に画面に表示したい文字列を書く。省略可。

10.1.2. フォームのデータを受け取る

HTMLのソースを見ると、出来上がったフォームの最初のところに action="/students" method="post" という指定がある。このフォームで送られるデータはどこが受け取るかというと、

% rails routes -c students

でルーティングの表を出して、当てはまるURLパターンとVerb(メソッドと言うとRubyのメソッドと紛らわしいのでVerbと言う)を探せばよい。そうすると、 students コントローラの create メソッドであることが分かる。

エディタで app/controllers/students_controller.rb を見てみる。

  • create メソッドは23行目にある。

  • 24行目で新しい Student のインスタンスを作っている。

    • student_params はフォームに入力されたデータからカラム名に対応するものだけを取り出すメソッド。67行目で定義されている。

    • 最初に scaffold で作った時は学年を指定していなかったので、学年を入力したい場合は student_params を修正する必要がある( 入力可能な項目名の指定 のところで説明する)。

  • 26行目の respond_to は、出力をHTMLとJSONで切り替えるためのもの。

    • 普通にブラウザからリクエストを出した時は format.html { } の部分が実行される。

    • とりあえず format.json { } の部分は気にしなくてよい。自分でメソッドを書く時は、JSONが必要なければそもそも respond.to とか format.html とか書かなくてよい。

  • 28行目、 redirect_to はブラウザに別のページへのリダイレクトを指示する。

    • 第1引数はURL。普通はヘルパーメソッドを書く。 student_url は、 rails routes で表示されるPrefixが student のURIパターン /students/:id を返す。パラメータ :id の部分は、引数の学生インスタンスの番号が入る。

    • 第2引数はハッシュの形でオプションを書く。ここでは notice: でリダイレクトされた先のページで表示したいメッセージを指定している。 * リダイレクトすると、ブラウザからの新しいリクエストの処理になるので、メッセージをインスタンス変数に記憶しておいてもダメ。リクエストをまたがって記憶しておく仕組み(flash)を使う。 * 当然、リダイレクトされた先のページで notice のメッセージを表示するようにビューを書いておく必要がある( app/views/students/show.html.erb の1行目)。

10.2. Railsの処理の流れ大追跡

新しい学生のデータを入力するとどうなるか追いかけてみよう。

  1. ブラウザで http://localhost:3000/students/new にアクセスする。

  2. ルーティング( scaffold によって作られたリソースルーティング)に従って students コンロトーラの new メソッドが呼び出される。 app/controllers/students_controller.rbdef new のところを見よ。

  3. @student = Student.new により、学生の新しいインスタンスができて @student に記憶される。

  4. レンダリングの指定がないので、デフォルトの new.html.erb が呼び出される。 app/views/students/new.html.erb を見よ。

  5. render 'form' により _form.html.erb が呼び出される。 app/views/students/_forms.html.erb を見よ。

  6. form_with ... do |form| から end までがHTMLの form 要素を生成する。

  7. _form.html.erb の実行結果が new.html.erbrender 'form' のところに挿入され、 new.html.erb 全体の実行結果がブラウザの画面に表示される。

  8. ユーザがデータを入力してSubmitボタンを押す。するとブラウザは http://localhost:3000/students にPOSTメソッドでアクセスする。

  9. ルーティングによって students コントローラの create メソッドが呼び出される。 app/controllers/students_controller.rbdef create のところを見よ。

  10. params にはフォームに入力したデータがハッシュの形で入っている。 student_params はハッシュから namefaculty_id だけを取り出す。これを使って学生のインスタンスを新しく作る。

  11. save でデータベースへの保存が成功すれば format.html の部分を実行する。

  12. redirect_to は、ブラウザに別のページへのリダイレクトを指示するレスポンスを返す。

  13. ブラウザが redirect_to で指示されたURLでリクエストを出す。

  14. ルーティングによって students コントローラの show メソッドが呼び出される。

  15. app/controllers/students_controller.rb の2行目に before_action :set_student と書いてあるので、 show メソッドを実行する前に set_student メソッドが実行される。 def set_student のところを見よ。

  16. params[:id] にはURLで指定された番号が入っている。 find でその番号のデータを探して @student に入れる。

  17. def show のところを見よ。何も書いていないので、すぐにデフォルトのビューが呼び出される。 app/views/students/show.html.erb を見よ。

  18. show.html.erb の実行結果がブラウザの画面に表示される。この時、 notice メソッドによって redirect_to で指定したメッセージが表示される。

_images/form-flow1.png _images/form-flow2.png _images/form-flow3.png

10.3. 選択肢を出す

scaffold が作った _form.html.erb では、学部を指定するのに form.number_field を使って番号を入力するようになっているが、学部の番号なんか覚えているわけがないので、学部名の選択肢から選ぶようにしたい。メニュー形式の入力要素は FormBuilder (変数 form に入っているやつ)の select メソッドで作れる。

<%= form.select :faculty_id, {'総合政策' => 1, '環境情報' => 2} %>

しかし、学部名と学部番号の情報はデータベースに入っているので、それと同じことをここでもう一回書くのは避けるべきである。モデルを使って選択肢を出すには次のようにする。

<%= form.collection_select :faculty_id, Faculty.all, :id, :name %>

これは、 Faculty.all で取り出したインスタンスのそれぞれに対して、 idname の組がメニューの項目になる。メニューの値が先で、表示文字列が後(ハッシュで書く時とは逆)なことに注意。

10.4. 入力可能な項目名の指定

最初に scaffold で作った時には、名前と学部しかなかったので、このフォームには学年を入力するところがない。学年を入力できるようにしてみよう。

まず、 app/views/students/_form.html.erb に入力欄を追加する。

<div>
  <%= form.label :grade, style: "display: block" %>
  <%= form.number_field :grade %>
</div>

次に、 app/controllers/students_controller.rb で、学年の入力データをデータベースに書き込んでもよいという許可をする。

注釈

昔はブラウザから送られてきたデータを何も考えずにデータベースに書き込んでいたが、そうすると、本来はユーザが書き換えてはいけないカラムも書き換えられてしまう。これを悪用した攻撃が可能なので、今はブラウザからのデータを書き込んでもよいカラム名を指定するようになった。

  • params.require(モデル名).permit(カラム名) で指定する。

  • scaffold で作った場合は、自動的にできている(この場合は student_params メソッドの中)ので、それを修正する。

def student_params
  params.require(:student).permit(:name, :faculty_id, :grade)
end

10.5. ラジオボタン

学年は 1, 2, 3, 4 のどれかなので、数値入力ではなくラジオボタンにしよう。 grade の値を1にするラジオボタンは次のように書く。

<%= form.radio_button :grade, 1 %>

これだけだとボタンしか表示されないので、普通は横に form.label で人間に分かる表示をする。ただし、ラジオボタンは同じ項目名でたくさんボタンがあるので、項目名だけではどのラベルがどのボタンに対応しているかわからない。そのため、 form.label は次のように value: をキーにして対応するボタンの値も書く。

<%= form.label :grade, '1年', value: 1 %>

ちなみにRailsのデフォルトのスタイルでは label がブロック要素になっているので、縦に並ぶ。横に並べたい時は app/assets/stylesheets/scaffold.scss を修正。 (Rails7.1ではインライン要素になったので、何もしなくてよい。)

課題18

app/views/students/_forms.html.erb を修正し、学年をラジオボタンで入力するようにせよ。だらだら書き並べずに繰り返しで書くこと。

【解答例】

10.6. データベースをリセットする

動作をテストする時にはデータを入力するが、調子にのっていろいろ入力すると、それが全部データベースに溜まっていくので、うざい。時々ゴミを一掃して何も無い状態からやり直したくなるが、それには、データベースを作り直す次のコマンドを使う。

% rails db:reset

しかし、学部や科目のデータは常に入っていないと困る。そのような固定的なデータは db/seeds.rb で作るようにしておくと、データベースを作り直す時には自動的に実行される。

Faculty.create(name: '総合政策')
Faculty.create(name: '環境情報')
Course.create(name: '体育1', credit: 1, compulsory: true)
Course.create(name: 'プログラミング言語論', credit: 2, compulsory: false)

なお、 Faculty.createFaculty.newsave を一度にやるメソッド。

10.7. 今日のまとめ

ここまでのソースコードはこちら

課題18実行後

課題19

最終課題のアプリケーションを、そろそろ出来る部分から作り始めよ。まだコードの提出は不要。

レポートは最後にやっつけで書く人が多いが、作りながら少しずつ書きためていく方がよい。構成はだいたい次のような感じで。

  1. 最初にどのようなものを作ろうと思ったか、動機や目標や背景。

  2. できたアプリケーションの使い方の説明。

  3. アプリケーションの内部構造の説明。

  4. 思った通りのものができたか、何が難しかったか。

動機や目標や背景はすでに書けるはず。今書いたものが全部できる必要はなく、できなかった部分については4でできなかった理由を書けばよい。今週の提出はレポートの1(になるはず)の部分。