#はじめに
has_oneで結んだ親テーブルと子テーブルの値を
1つのフォームで同時に保存する方法を解説!
※has_manyの場合はやり方が少し異なります。ご注意ください。
音楽系のアプリを作っていて、
「Youtubeの埋め込みurlを持った子テーブルから
urlを引っ張ってきて投稿の中に表示したい!」と思い、
それを実現すべくネストしたフォームを作る中でかなり苦戦しました...
ネストしたフォームを作りたい方はお役に立てるかもしれません。
###環境
Rails 6.0.3.5
ruby 2.7.2
#モデル
belongs_to :user
has_one :portfolio, dependent: :destroy
accepts_nested_attributes_for :portfolio
belongs_to :post
PostがPortfolioをhas_oneで保持されている形ですね。
Postがhas_manyによってuserに保持されています。
accepts_nested_attributes_forをつけることによって
portrofolioをpost内にネストさせることが可能になります。
#コントローラー
def new
@post = Post.new
@post.build_portfolio
end
def create
@post = current_user.posts.build(post_params)
if @post.save
flash[:success] = '投稿しました'
redirect_to @post
else
render 'post/new'
end
end
def edit
@post.portfolio
end
def update
@post = Posr.find(params[:id])
if @post.update(post_params)
flash[:success] = '投稿内容を編集しました'
redirect_to @post
else
render 'edit'
end
end
private
def post_params
params.require(:exhibition).permit(:title, :content,
portfolio_attributes: [:first_url, :second_url, :third_url])
end
コントローラーで注意すべき点は、newアクションとeditアクションです。
@post.build_portfolio
@post.portfolio
newとeditのアクションでこの記述をしないと子要素のportfolioの値を送信するための
フォームがビューで表示されなくなってしまいます。
editではnewにはあったbuild_が外れていることに注目してください。
このbuild_はnewではportfolioを新規作成するために必要です。
しかしeditでもbuild_をつけたままにしておくと、初期化しようとするのかなんなのか
投稿の編集画面にアクセスすると投稿に元々紐付いていたportfolioを自動でdeleteしてしまいます。
僕はこの仕様の解決にかなり苦戦しました。。
editに@post.portfolioをつけないとportfolioのフォームに初期値が入らないので
必ず入れましょう。
#ビュー
<div class="post-form-area">
<div class="form-list">
<%= form_with(model: @post, local: true, class: 'form') do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :title, 'タイトル ※必須' %>
<%= f.text_field :title, class: 'form-input' %>
<%= f.label :content, '本文 ※必須' %>
<%= f.text_area :content, class: 'content form-input' %>
<%= f.fields_for :portfolio do |p| %>
<%= p.label :portfolio, 'ポートフォリオ' %>
<%= p.text_field :first_url, placeholder: 'URL(1)', class: 'pf form-input' %>
<%= p.text_field :second_url, placeholder: 'URL(2)', class: 'pf form-input' %>
<%= p.text_field :third_url, placeholder: 'URL(3)', class: 'pf form-input' %>
<% end %>
<%= f.submit '投稿', class: 'btn' %>
<% end %>
</div>
</div>
fiels_forを使うことでネストしたフォームを作成することができます。
今回はfirst_url,second_url,third_urlをportfolioテーブルにtextとして
保存するためのフォームを作りました。
引数にplaceholderを渡すことでなにも入力されていないときに
URL(1)などを薄く表示できます。
<%= content_tag :span, "[ #{@user.name} ]", class: 'title' %>
<%= content_tag :p, @postn.title, class: 'title' %>
<%= link_to '記事を編集する', edit_post_path(@post), class: 'edit-link' %>
<div class="content">
<%= safe_join(@post.content.split("\n"),tag(:br)) %>
</div>
<div class="pf-section">
<p>ポートフォリオ</p>
<%= content_tag :iframe, '', src: "https://www.youtube.com/embed/#{@post.portfolio.first_url.last(11)}",
class: 'youtube' unless @post.portfolio.first_url.blank? %>
<%= content_tag :iframe, '', src: "https://www.youtube.com/embed/#{@post.portfolio.second_url.last(11)}",
class: 'youtube' unless @post.portfolio.second_url.blank? %>
<%= content_tag :iframe, '', src: "https://www.youtube.com/embed/#{@post.portfolio.third_url.last(11)}",
class: 'youtube' unless @post.portfolio.third_url.blank? %>
</div>
子要素を表示する場合には@親要素.子要素.カラム名で表示することができます。
今回の場合は@post.portfolio.first_urlとなります。
#おわりに
以上でネストしたフォームを作れるかと思います!
もしうまくいかなかったりおかしなところがあれば
コメントでお知らせいただけると幸いです!
最後まで読んでいただきありがとうございました!