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の処理の流れ大追跡¶
新しい学生のデータを入力するとどうなるか追いかけてみよう。
ブラウザで
http://localhost:3000/students/newにアクセスする。ルーティング(
scaffoldによって作られたリソースルーティング)に従ってstudentsコンロトーラのnewメソッドが呼び出される。app/controllers/students_controller.rbのdef newのところを見よ。@student = Student.newにより、学生の新しいインスタンスができて@studentに記憶される。レンダリングの指定がないので、デフォルトの
new.html.erbが呼び出される。app/views/students/new.html.erbを見よ。render 'form'により_form.html.erbが呼び出される。app/views/students/_forms.html.erbを見よ。form_with ... do |form|からendまでがHTMLのform要素を生成する。_form.html.erbの実行結果がnew.html.erbのrender 'form'のところに挿入され、new.html.erb全体の実行結果がブラウザの画面に表示される。ユーザがデータを入力してSubmitボタンを押す。するとブラウザは
http://localhost:3000/studentsにPOSTメソッドでアクセスする。ルーティングによって
studentsコントローラのcreateメソッドが呼び出される。app/controllers/students_controller.rbのdef createのところを見よ。paramsにはフォームに入力したデータがハッシュの形で入っている。student_paramsはハッシュからnameとfaculty_idだけを取り出す。これを使って学生のインスタンスを新しく作る。saveでデータベースへの保存が成功すればformat.htmlの部分を実行する。redirect_toは、ブラウザに別のページへのリダイレクトを指示するレスポンスを返す。ブラウザが
redirect_toで指示されたURLでリクエストを出す。ルーティングによって
studentsコントローラのshowメソッドが呼び出される。app/controllers/students_controller.rbの2行目にbefore_action :set_studentと書いてあるので、showメソッドを実行する前にset_studentメソッドが実行される。def set_studentのところを見よ。params[:id]にはURLで指定された番号が入っている。findでその番号のデータを探して@studentに入れる。def showのところを見よ。何も書いていないので、すぐにデフォルトのビューが呼び出される。app/views/students/show.html.erbを見よ。show.html.erbの実行結果がブラウザの画面に表示される。この時、noticeメソッドによってredirect_toで指定したメッセージが表示される。
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 で取り出したインスタンスのそれぞれに対して、 id と name の組がメニューの項目になる。メニューの値が先で、表示文字列が後(ハッシュで書く時とは逆)なことに注意。
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のデフォルトのスタイルでは (Rails7.1ではインライン要素になったので、何もしなくてよい。)label がブロック要素になっているので、縦に並ぶ。横に並べたい時は app/assets/stylesheets/scaffold.scss を修正。
課題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.create は Faculty.new と save を一度にやるメソッド。
10.7. 今日のまとめ¶
課題19
最終課題のアプリケーションを、そろそろ出来る部分から作り始めよ。まだコードの提出は不要。
レポートは最後にやっつけで書く人が多いが、作りながら少しずつ書きためていく方がよい。構成はだいたい次のような感じで。
最初にどのようなものを作ろうと思ったか、動機や目標や背景。
できたアプリケーションの使い方の説明。
アプリケーションの内部構造の説明。
思った通りのものができたか、何が難しかったか。
動機や目標や背景はすでに書けるはず。今書いたものが全部できる必要はなく、できなかった部分については4でできなかった理由を書けばよい。今週の提出はレポートの1(になるはず)の部分。