1
1

More than 3 years have passed since last update.

Railsでfields_forとaccepts_nested_attributes_forを使ってhas_oneで関連付けしたネストしたフォームを作る

Posted at

はじめに

has_oneで結んだ親テーブルと子テーブルの値を
1つのフォームで同時に保存する方法を解説!
※has_manyの場合はやり方が少し異なります。ご注意ください。

音楽系のアプリを作っていて、
「Youtubeの埋め込みurlを持った子テーブルから
urlを引っ張ってきて投稿の中に表示したい!」と思い、
それを実現すべくネストしたフォームを作る中でかなり苦戦しました...

ネストしたフォームを作りたい方はお役に立てるかもしれません。

環境

Rails 6.0.3.5
ruby 2.7.2

モデル

models/post.rb
belongs_to :user
has_one :portfolio, dependent: :destroy
accepts_nested_attributes_for :portfolio
models/portfolio.rb
belongs_to :post

qiita_image.jpg

PostがPortfolioをhas_oneで保持されている形ですね。
Postがhas_manyによってuserに保持されています。

accepts_nested_attributes_forをつけることによって
portrofolioをpost内にネストさせることが可能になります。

コントローラー

controllers/post_controller.rb
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アクションです。

newアクション
@post.build_portfolio
editアクション
@post.portfolio

newとeditのアクションでこの記述をしないと子要素のportfolioの値を送信するための
フォームがビューで表示されなくなってしまいます。

editではnewにはあったbuild_が外れていることに注目してください。
このbuild_はnewではportfolioを新規作成するために必要です。
しかしeditでもbuild_をつけたままにしておくと、初期化しようとするのかなんなのか
投稿の編集画面にアクセスすると投稿に元々紐付いていたportfolioを自動でdeleteしてしまいます。
僕はこの仕様の解決にかなり苦戦しました。。

editに@post.portfolioをつけないとportfolioのフォームに初期値が入らないので
必ず入れましょう。

ビュー

new.html.erb
<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)などを薄く表示できます。

show.html.erb
<%= 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となります。

おわりに

以上でネストしたフォームを作れるかと思います!
もしうまくいかなかったりおかしなところがあれば
コメントでお知らせいただけると幸いです!

最後まで読んでいただきありがとうございました!

1
1
0

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
1
1