********** フォーム ********** フォームによるデータ送信の仕組みについて知らない人は、まず次の資料を読むこと。 * 参考資料: `フォームデータの送信 - MDN Web Docs `_ =========================== Railsにおけるフォーム機能 =========================== フォームを作るためには、次の二つの作業が必要である。 * フォーム画面を表示するビューを作る。 * フォームからのデータを受け取って処理をするコントローラのメソッドを作る。 ビューを作る ------------ フォームの例として、 ``scaffold`` が作った、新しい学生のデータを入力するページ http://localhost:3000/students/new を見てみよう。このページのビュー ``app/views/students/new.html.erb`` をエディタで開いてブラウザ画面と比べてみよ。 * フォームの部分は ``render 'form'`` と書いてある。 * ``render`` は他のERBファイルを呼び出す。呼び出されるERBファイルはパーシャルと呼ぶ。 * 参考資料: `パーシャルを使用する - Rails Guides `_ * この場合は ``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引数に画面に表示したい文字列を書く。省略可。 フォームのデータを受け取る -------------------------- 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`` を修正する必要がある( :ref:`permit` のところで説明する)。 * 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行目)。 ========================= Railsの処理の流れ大追跡 ========================= 新しい学生のデータを入力するとどうなるか追いかけてみよう。 1. ブラウザで ``http://localhost:3000/students/new`` にアクセスする。 2. ルーティング( ``scaffold`` によって作られたリソースルーティング)に従って ``students`` コンロトーラの ``new`` メソッドが呼び出される。 ``app/controllers/students_controller.rb`` の ``def 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.erb`` の ``render 'form'`` のところに挿入され、 ``new.html.erb`` 全体の実行結果がブラウザの画面に表示される。 8. ユーザがデータを入力してSubmitボタンを押す。するとブラウザは ``http://localhost:3000/students`` にPOSTメソッドでアクセスする。 9. ルーティングによって ``students`` コントローラの ``create`` メソッドが呼び出される。 ``app/controllers/students_controller.rb`` の ``def create`` のところを見よ。 10. ``params`` にはフォームに入力したデータがハッシュの形で入っている。 ``student_params`` はハッシュから ``name`` と ``faculty_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`` で指定したメッセージが表示される。 .. image:: form-flow1.png .. image:: form-flow2.png .. image:: form-flow3.png ============== 選択肢を出す ============== ``scaffold`` が作った ``_form.html.erb`` では、学部を指定するのに ``form.number_field`` を使って番号を入力するようになっているが、学部の番号なんか覚えているわけがないので、学部名の選択肢から選ぶようにしたい。メニュー形式の入力要素は ``FormBuilder`` (変数 ``form`` に入っているやつ)の ``select`` メソッドで作れる。 .. code-block:: erb <%= form.select :faculty_id, {'総合政策' => 1, '環境情報' => 2} %> しかし、学部名と学部番号の情報はデータベースに入っているので、それと同じことをここでもう一回書くのは避けるべきである。モデルを使って選択肢を出すには次のようにする。 .. code-block:: erb <%= form.collection_select :faculty_id, Faculty.all, :id, :name %> これは、 ``Faculty.all`` で取り出したインスタンスのそれぞれに対して、 ``id`` と ``name`` の組がメニューの項目になる。メニューの値が先で、表示文字列が後(ハッシュで書く時とは逆)なことに注意。 .. _permit: ======================== 入力可能な項目名の指定 ======================== 最初に ``scaffold`` で作った時には、名前と学部しかなかったので、このフォームには学年を入力するところがない。学年を入力できるようにしてみよう。 まず、 ``app/views/students/_form.html.erb`` に入力欄を追加する。 .. code-block:: erb
<%= form.label :grade, style: "display: block" %> <%= form.number_field :grade %>
次に、 ``app/controllers/students_controller.rb`` で、学年の入力データをデータベースに書き込んでもよいという許可をする。 .. note:: 昔はブラウザから送られてきたデータを何も考えずにデータベースに書き込んでいたが、そうすると、本来はユーザが書き換えてはいけないカラムも書き換えられてしまう。これを悪用した攻撃が可能なので、今はブラウザからのデータを書き込んでもよいカラム名を指定するようになった。 * ``params.require(モデル名).permit(カラム名)`` で指定する。 * ``scaffold`` で作った場合は、自動的にできている(この場合は ``student_params`` メソッドの中)ので、それを修正する。 .. code-block:: ruby def student_params params.require(:student).permit(:name, :faculty_id, :grade) end ============== ラジオボタン ============== 学年は 1, 2, 3, 4 のどれかなので、数値入力ではなくラジオボタンにしよう。 ``grade`` の値を1にするラジオボタンは次のように書く。 .. code-block:: erb <%= form.radio_button :grade, 1 %> これだけだとボタンしか表示されないので、普通は横に ``form.label`` で人間に分かる表示をする。ただし、ラジオボタンは同じ項目名でたくさんボタンがあるので、項目名だけではどのラベルがどのボタンに対応しているかわからない。そのため、 ``form.label`` は次のように ``value:`` をキーにして対応するボタンの値も書く。 .. code-block:: erb <%= form.label :grade, '1年', value: 1 %> .. |ss| raw:: html .. |se| raw:: html |ss| ちなみにRailsのデフォルトのスタイルでは ``label`` がブロック要素になっているので、縦に並ぶ。横に並べたい時は ``app/assets/stylesheets/scaffold.scss`` を修正。 |se| (Rails7.1ではインライン要素になったので、何もしなくてよい。) .. admonition:: 課題18 :class: exercise ``app/views/students/_forms.html.erb`` を修正し、学年をラジオボタンで入力するようにせよ。だらだら書き並べずに繰り返しで書くこと。 :ref:`【解答例】 ` ============================ データベースをリセットする ============================ 動作をテストする時にはデータを入力するが、調子にのっていろいろ入力すると、それが全部データベースに溜まっていくので、うざい。時々ゴミを一掃して何も無い状態からやり直したくなるが、それには、データベースを作り直す次のコマンドを使う。 :: % rails db:reset しかし、学部や科目のデータは常に入っていないと困る。そのような固定的なデータは ``db/seeds.rb`` で作るようにしておくと、データベースを作り直す時には自動的に実行される。 .. code-block:: ruby 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`` を一度にやるメソッド。 ============== 今日のまとめ ============== `ここまでのソースコードはこちら `_ `課題18実行後 `_ .. admonition:: 課題19 :class: exercise 最終課題のアプリケーションを、そろそろ出来る部分から作り始めよ。まだコードの提出は不要。 レポートは最後にやっつけで書く人が多いが、作りながら少しずつ書きためていく方がよい。構成はだいたい次のような感じで。 1. 最初にどのようなものを作ろうと思ったか、動機や目標や背景。 2. できたアプリケーションの使い方の説明。 3. アプリケーションの内部構造の説明。 4. 思った通りのものができたか、何が難しかったか。 動機や目標や背景はすでに書けるはず。今書いたものが全部できる必要はなく、できなかった部分については4でできなかった理由を書けばよい。今週の提出はレポートの1(になるはず)の部分。