LoginSignup
303
321

More than 3 years have passed since last update.

【Rails】form_with/form_forについて【入門】

Last updated at Posted at 2020-01-02

どんもー、@snskOgataです。

今回はRails開発におけるヘルパーメソッドform_with/form_forについて深掘りしていこうと思います。
特にform_withに
・モデルが渡されているときと渡されていないときでの振る舞いの違い
・渡された変数が空の場合と既存のものであった時の場合

をそれぞれ話していこうと思います。

と、その前にform_withとform_forの違いについて触れておきます。

1. form_withとform_forの違いについて

form_withは元々Rails5.1以前のバージョンで使い分けられていたform_forとform_tagをどちらも同様に扱えるようにしたもの、らしいです。
それまでは、関連するモデルが用意されている場合はform_forでモデルを渡してやり、関連するモデルがない場合はurlをform_tagに渡して、それぞれフォームを作っていました。

関連するモデルがない場合→form_tag

<%= form_tag users_path do # user_pathというURIを渡している %>
  <%= text_field_tag :email %>
  <%= submit_tag %>
<% end %>

関連するモデルがある場合→form_for

<%= form_for @user do |form| # @userというインスタンス変数を渡している %>
  <%= form.text_field :email # 受け取ったモデルから作ったformを利用  %>
  <%= form.submit %>
<% end %>

form_with

これらがform_withで一緒くたに扱えるようになったみたいです。

<%= form_with url: users_path do |form| # urlを渡している %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>
<%= form_with model: @user do |form| # modelを渡している %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

Railsが5.1以上であればform_withを使うことが推奨されているようです。
参考記事:【Rails 5】(新) form_with と (旧) form_tag, form_for の違い

 
 
form_withとform_for/form_tagで大きな違いはないので、ここからは推奨されているform_withを例に説明していきます。

2. form_withの動作について

form_withは渡されたものによって、行うHTTPメソッドとアクションをそれぞれ判断してくれます。

2.1 form_with url: url_path

urlが渡されたのであれば、そのpathに対してPOSTメソッドを行います。

<%= form_with url: users_path do |form| # urlを渡している %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

users_pathに対してPOSTを行い、このコードであれば
params[:email]
のような形でtext_fieldに入力された値を取得することができます。
これによりストロングパラメータでも
params.permit(:email)
のように設定することが可能です。

2.2 form_with model: @￰model

この場合は、モデルに入っているものが①新しく作られたものの場合、と②既存のものを呼び出した場合、で処理が変わってきます

①新しく作られたものが渡された場合

newアクションから渡ってきたものがこれに当てはまります。

app/controllers/users_controller.rb
def new
  @user = User.new
end
app/views/users/new.html.erb
<%= form_with model: @user do |form| # modelを渡している %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

Railsは渡されたモデル(@￰user)の中身が空であることから、createメソッドを呼び出すことを判断します。

app/controllers/users_controller.rb
def create
  User.create(user_params)
end

private
  # ストロングパラメータ
  def user_params
    paramas.requie(:user).permit(:email)
  end

このとき気を付けなければいけないのが、paramsの中身
先ほどのurlを渡したformであればparams[:email]で取得できたのですが、今回のものはモデルを渡したことにより、
params[:user][:email]
のように階層構造になっていることに注意していください!
それによりストロングパラメータの設定では.require(:user)のように一度仲介を挟む必要が出てきます。

②既存のものが渡された場合

editアクションから渡ってきた物がこれに当てはまります。

app/controllers/users_controller.rb
def edit
  @user = User.find(params[:id]) # DBから既存のものを取得
end
app/views/users/edit.html.erb
<%= form_with model: @user do |form| # modelを渡している %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

実のところnew.html.erbとedit.html.erbのフォーム部分は全くの一緒なんです!
これによりリファクタリングを行え、フォーム部分をパーシャル(部分テンプレート)化できたりします。

さて、Railは渡されたモデル(@￰user)の中身があることからupdateメソッドを呼び出すことを判断します。

app/controllers/users_controller.rb
def update
  User.find(params[:id]).update(user_params)
end
# 以下略

書くのは省略していますが、ここでもparamsは階層構造になっており:emailを取り出すにはparams[:user][:email]のように取得する必要があります。

modelを渡したときの場合はこのようにまとめられます
スクリーンショット 2020-01-02 17.22.54.png

ここまでだと単に紛らわしくなるだけでは...と思うかもしれませんが、form_withの真価はここからもう一歩先にあります!

2.3 form_with model: [@￰modelA, @￰modelB]

railsではルーティングによってページが階層構造にすることができます。

config/routes.rb
resources :tweets do
  resources :comments only: [:index, :create]
end

これにより、tweets/:id/commentsのようにtweetのひとつに紐ついたcomments群、のようなページ設計を行うことが可能です。
このときのコメント作成の際に使えるのが、form_withに複数モデルを渡してやる、という方法です。

app/controllers/comments_controller.rb
def index
  # モデルを2つ用意
  @tweet = Tweet.find(params[:id])
  @comment = Comment.new
end
app/views/comments/index.html.erb
<%= form_with(model:[@tweet, @comment]) do |form| %>
  <%= form.text_field :content %>
  <%= form.submit %>
<% end %>

複数モデルを受け取ると、1つ目が中身あり、2つ目が中身なしということで、それぞれからtweets/:id/messagesというパスを自動で判断し、さらに中身なしということでmessages_controller.rbのcreateを呼び出します。

app/controllers/comments_controller.rb
def create
  Comment.create(comment_params)
end

  private
    def
      # モデルを渡してるから階層構造になっているため.require(:comment)を仲介させる
      # .mergeによりtweet_idを紐つける
      pramas.requier(:comment).permit[:content].merge(tweet_id: params[:id])
    end

察しの良い人はお気づきかもしれませんが、この2つモデルを渡す場合でも、2つ目のモデルに中身が入っていると、Railsが勝手に判断してupdateが行われます!
この部分を図示すると以下のようになります。
スクリーンショット 2020-01-02 18.06.09.png

これを用いれば、どれだけ深いページ構造になったとしても、同じようにモデルを複数渡してやることによって、簡単に紐付けを行いながらDBに追加していくことが可能になるというわけです!

3. まとめ

とまあこんな感じで伝えたかったことは
・form_withにモデルを渡してやると勝手に適当なメソッド呼び出してくれる。
・form_withにモデルを渡してフォームを作ると、paramsの値が階層構造になる。
→ストロングパラメータの設定には気をつけて!
・form_withに複数モデルを渡すと階層的なルーティングにも対応できるよ

ということでした。

以上で終わりです、開発での参考になってくれれば幸いです。

303
321
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
303
321