accepts_nested_attributes_for
是一個蠻常會用到的語法
簡單的來說,就是可以透過這個語法,在更新 data 的時候,同時更新其他 model 裡的 data
所以並不是每個 model 都必須要有 controller 才能夠做更新的動作
這裏有兩種情境
One-to-one
1
2
3
4
| class Book < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author
end
|
1
2
3
4
| params = { book: { name: 'Harry Potter', author_attributes: { name: 'J. K. Rowling' } } }
book = Book.create(params[:book])
book.author.name # => 'J. K. Rowling'
|
透過 params 更新 Book 的 name
同時透過 author_attributes: { name: 'J. K. Rowling' }
更新 Author 的 name
當然 update 同樣適用
1
2
3
| params = { book: { author_attributes: { name: 'J. K. Rowling' } } }
book.update params[:book]
book.author.name # => 'J. K. Rowling'
|
另外值得注意的是
1
| accepts_nested_attributes_for :author, :allow_destroy => true, :reject_if => :all_blank
|
:allow_destroy => true
可以在表單增加一個 _destroy 核選塊來表示刪除
:reject_if => :all_blank
表示在什麼條件下就當作沒有要動作, all_blank 表示如果資料都是空的,就不執行
One-to-many
1
2
3
4
| class Book < ActiveRecord::Base
has_many :pages
accepts_nested_attributes_for :page
end
|
1
2
3
4
5
6
7
8
9
10
11
| params = { book: {
name: 'Harry Potter', page_attributes: [
{ title: "Philosopher's Stone" },
{ title: "Chamber of Secrets" }
]
}}
book = Book.create(params[:book])
book.pages.length # => 2
book.pages.first.title # => 'Philosopher's Stone'
book.pages.last.title # => 'Chamber of Secrets'
|
rails5
在 rails 5 中,要用 accepts_nested_attributes_for
必須在 belongs_to
加上 options: true
或是 required: false
1
2
3
4
5
6
7
8
| class Item < ApplicationRecord
has_many :item_options
accepts_nested_attributes_for :item_options, allow_destroy: true
end
class ItemOption < ApplicationRecord
belongs_to :items, required: false
end
|
必須有 :id
才能夠 update,再加上 :_destroy
就能夠在參數加上 _destroy:1 (or true)
去做刪除 (model 必須要 allow_destroy: true
)
1
2
3
4
5
6
| class ItemsController < ApplicationController
private
def item_params
params.require(:item).permit(:name, item_options_attributes: [:id, :value, :position, :_destroy])
end
end
|
通常會搭配 fields_for 來嵌入到同一個表單
One-to-one
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <%= form_for @book do |b| %>
<%= b.label :name %>
<%= b.text_field : name %>
<%= b.fields_for :author do |a| %>
<%= a.label :name %>
<%= a.text_field : name %>
<% unless a.object.new_record? %>
<%= a.label :_destroy, 'Remove:' %>
<%= a.check_box :_destroy %>
<% end %>
<% end %>
<%= b.submit %>
<% end %>
|
這樣就能夠透過原本是 @book 的表單,裡面再放入 author 的欄位進行更新。
One-to-many
one-to-many 會比較麻煩,因為當新增的時候,並不知道要新增幾個,因此會動用到 jquery 的動態新增,就是可以在表單上面一直增加欄位。
不過幸好有 gem 可以解決了,以下有幾個 gem
strong parameter
最後記得要加 strong parameter
one-to-one 和 one-to-many 都要
1
2
3
4
| def book_params
params.require(:book)
.permit(:name, pages_attributes:[:title, :_destroy, :id])
end
|
每個 gem strong parameter 的方式都有點不太一樣,記得要看一下
helper
book_helper.rb
1
2
3
4
5
| def setup_author(book)
book.build_author if book.author.blank?
#one-to-one 用 build_author , one-to-many 可以用 authors.build or authors.new
book
end
|
如果一開始沒設定的話,在 book 表單上是看不到 author 的欄位,因為一開始還沒 build
因此要給它一個判斷,如果是 nil 就先 build_author 給它
接著用 setup_author(@book)
來置換 form_for 中的 @book
1
2
3
4
5
| <%= form_for setup_author(@book), :url => books_path do |b| %>
...
<% end %>
|
官方文件: