【謎挙動】form_tagとtext_fieldで検索フォームを作成【rails】
検索フォームを作ろうとしてform_tag
とtext_field
を使った際に謎の挙動が見られたのでメモ書き。(一応解決されました。)
環境は以下の通りです。
- rails 5.2.0
結論
まず結論。
form_tag
とtext_field
を使って検索フォームを実装する場合、以下のように書くのが正解なようです。
# tasks_controller.rb def index @tasks = Task.all end def search search_word = "content like '%" + params[:task][:content] + "%'" @tasks = Task.where(search_word) end
<!-- index.html.erb --> <body> <div> <%= form_tag search_path do %> <%= text_field :task, :content %> <%= submit_tag "検索" %> <% end %> </div> ... ... </body>
これで検索フォームに入力した単語(search_word)を持つデータがTaskテーブルからsearchアクションの@tasks
に入力されます。
問題の謎挙動
さて早速ですが問題の挙動です。
以下のようなコードを書いた時に起こりました。
# tasks_controller.rb def index @tasks = Task.all @task = Task.new end def search search_word = "content like '%" + params[:content] + "%'" @tasks = Task.where(search_word) end
まずコントローラーを上のように設定し、indexページにsearchアクションを仕込ませてparams[:content]
を検索ワード(search_word)として抜き出そうという狙いでした。(Taskテーブルにcontentカラムを持たせています。)
そして実際にindexページに書いた間違いコードが以下の二つ。
<!-- index.html.erb 間違いその1 --> <div> <%= form_tag search_path do %> <%= text_field @task, :content %> <%= submit_tag "検索" %> <% end %> </div>
<!-- index.html.erb 間違いその2 --> <body> <%= form_tag search_path @task do |f| %> <%= text_field f, :content %> <%= submit_tag "検索" %> <% end %> </body>
これらはどちらもform_tag
やtext_field
の用法としては間違いです。
しかしその2ではparams[:content]
でうまく検索ワードを抜き出せてしまいました。
なぜ「その2」では間違った記法にも関わらず正しい挙動をしたのか、また「その1」と「その2」はほとんど等価なプログラムのように見えるのになぜ「その1」では正しく挙動しないのか、わかったことを簡単にまとめます。
paramsを見てみる
まず、これらの書き方によって得られるparams
を見てみます。
searchアクションにbinding.pry
を仕込んでparams
の中身を調べます。
# index.html.erb 間違いその1 [1] pry(#<TasksController>)> params => <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"wB4+spI/twQq+OS7wGOkjP7rYuGlDN/kXYbeN9lESqFTHSU38EcWMmG00Z5cVGKl0EFcQ5HSf2qpijb8r+zCuA==", "#<Task:0x00007f9f327081d0>"=>{"content"=>"hoge"}, "commit"=>"検索", "controller"=>"tasks", "action"=>"search"} permitted: false>
# index.html.erb 間違いその2 [1] pry(#<TasksController>)> params => <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"IE7LzRafXk1idX7E5tHpQMsvs1WsRrnv1Udbcbm2aRCzTdBIdOf/eyk5S+F65i9p5YWN95iYGWEhS7O6zx7hCQ==", "content"=>"hoge", "commit"=>"検索", "controller"=>"tasks", "action"=>"search"} permitted: false>
その1ではparams
の中に"#<Task:0x00007f9f327081d0>"=>{"content"=>"hoge"}
というハッシュがネストしており、params[:content]
では"hoge"
にアクセスできない状態になっています。
一方その2ではparams
ハッシュの中にうまく"content"=>"hoge"
が格納されており、params[:content]
で"hoge"
にアクセスできていますね。
間違いその1について
<!-- index.html.erb 間違いその1 --> <div> <%= form_tag search_path do %> <%= text_field @task, :content %> <%= submit_tag "検索" %> <% end %> </div>
その1で間違っているのはtext_field
の使い方です。
<%= text_field hoge, fuga %> <%= submit_tag %>
とした場合、text_field
は 文字列 "hoge"
をキーとしたハッシュ"hoge"=>{"fuga"=>"入力文字"}
をparams
に返します。
この辺りがtext_field
のよしなに力を感じる部分で、仮にhoge
が:hoge
でも@hoge
でも@@hoge
であったとしてもいったん文字列"hoge"
としてキーにするようです。
今回の間違いその1ではhoge
に該当する部分に@task
が入っていました。
この@task
はコントローラーのindexアクションから空のクラスTask.new
を受け取ってきます。
Task.new
を文字列に変換すると
>> Task.new.to_s => #<Task:0x00007f9f327081d0>
となるので、結果としてparams
には"#<Task:0x00007f9f327081d0>"=>{"content"=>"hoge"}
というハッシュが出来上がります。
ちなみにtext_field
にはもうちょっとだけrailsのよしなに力が働いていて、キーとして与えられた文字列(例でいうhoge
やfuga
)が変数として使われていないかindexアクションまで確認に行ってくれているようです。
そのため今回の場合だと@tasks
は複数のクラスを抱えていることを認知しているため
<%= text_field @tasks, fuga %>
のような使い方をすると、@tasks
をキーとするには不適切と判断しエラーを出力します。
hoge
やfuga
は基本的にはどんな文字列でも大丈夫ですが、そのページで使用するクラス変数などは中身を参照してよしなに判断されてしまうので注意が必要です。
さすがはrailsさん、完全に初見殺し気が利いていますね。
間違いその2について
<!-- index.html.erb 間違いその2 --> <body> <%= form_tag search_path @task do |f| %> <%= text_field f, :content %> <%= submit_tag "検索" %> <% end %> </body>
続いてその2でparams[:content]
にアクセスできている理由です。
そもそも論なんですが、form_tag
ではその2のようなブロック変数|f|
は受け取らない設計になっています。
ですので「その2」のdo-end
ブロック内においてブロック変数f
はnil
として扱われています。
よって<%= text_field f, :content %>
でf
は無視され params
に直接"content"=>"hoge"
が入力されることになります。
本来の用法ではありませんがエラーを吐かずに動いてしまうため、製作者の意図に反してこのような挙動をとってしまう可能性があります。 こちらも注意が必要です。
まとめ
text_field
- 引数をいったん文字列として
params
のキーにする - 使用できる文字列かをよしなに判断してくれる
- 配列やハッシュなど複数の値を持つ場合はエラーを吐く可能性がある
form_tag
- ブロック変数は取らない
- ブロック変数を持たせても動作するが、ブロック変数は
nil
扱いとなる
思う存分にrailsマジックに翻弄された事例でした。 皆さんも十分にお気をつけください。