0%

如何在一個form裡面寫入兩個model的資料?(nested_form)

在剛學習rails的時候,表單通常都是單純的一個model填完欄位就送出,直到實際專案在進行時,才遇到需要將兩個model放在同一個form裡面,同時可以儲存資料到個別model的資料庫的需求。這時候就需要用到本篇文章要介紹的主角:Nested form

在我們募資網站的專案例子中,project跟givebacks(回饋商品)是一對多的關係,而我們想在project new頁面的form裡面,同時放入giveback的欄位,可使用fields_for,將兩個model放在同一個form裡面。

1
2
3
4
5
6
7
8
9
10
#project.rb
class Project < ApplicationRecord
belongs_to :user
has_many :givebacks
end

#giveback.rb
class Giveback < ApplicationRecord
belongs_to :project
end

按照官方文件的說明,我大概將它分成四個步驟:

  1. 建立一個attribute writer方法到project model裡面

    1
    2
    3
    4
    5
    6
    #project.rb
    class Project < ApplicationRecord
    belongs_to :user
    has_many :givebacks
    accepts_nested_attributes_for :givebacks
    end
  2. 用fields_for方法建立nested form(請注意:fields_for後面接的是符號)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #_form.html.erb

    <%= form_for(project) do |form| %>
    <div class="project-title">
    <%= form.label :title, '專案標題' %>
    <%= form.text_field :title%>
    </div>

    ·········(略)···········
    #下面是giveback的欄位
    <div>
    <%= form.fields_for :givebacks do |givebacks_form| %>
    <%= givebacks_form.label :回饋商品名稱 %>
    <%= givebacks_form.text_field :title %>
    </div>
    <div>
    <%= givebacks_form.label :價格 %>
    <%= givebacks_form.text_field :price %>
    </div>

    <% end %>
    <%= form.submit %>
    <% end %>
  3. 將giveback的資料加到permit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #projects_controller.rb
    def project_params
    params.require(:project).permit(
    :title,
    :summary,
    :content,
    :image,
    :target_amount,
    :user_id,
    :due_date,
    :status,
    :content_image,
    :category_id,
    givebacks_attributes: [:id, :title, :price, :deliver_time, :_destroy, :image])
    end

    註:id也要被permit過去,否則在update project資料時,giveback因為沒有id的關係,會被判斷成我們要create一個新的,一直製造新的giveback實體。

  4. 在project controller的new方法裡面建立givebacks的實體

    1
    2
    3
    4
    5
    #projects_controller.rb
    def new
    @project = Project.new
    @project.givebacks.build
    end

    這時候填完資料按下送出,應該就可以看到兩個model的資料同時寫入資料庫了。

備註:在第四個步驟中,我們只能建立一個giveback的實體,如果需要要建立多個實體及資料,可使用times的方法。

1
2
3
4
def new
@project = Project.new
5.times {@project.givebacks.build}
end

如何刪除fields_for的資料(後續更新用js來做)

  1. 在fields_for裡面設定刪除的checkbox選項

    1
    2
    3
    4
    <div>
    <%= givebacks_form.check_box :_destroy %>
    <%= givebacks_form.label :_destroy, '刪除回饋商品' %>
    </div>
  2. 在project的model裡面設定allow_destroy

    1
    2
    3
    4
    5
    class Project < ApplicationRecord
    belongs_to :user
    has_many :givebacks
    accepts_nested_attributes_for :givebacks, allow_destroy: true
    end

    這只是目前我理出的概略步驟,其他詳細說明及用法可參考官方文件:

https://guides.rubyonrails.org/form_helpers.html#building-complex-forms