多态关联嵌套表单问题:为何authors字段输入框不显示?
我有Book、Nonfiction、Author三个类,希望通过Nonfiction的表单初始化Book和Author,但对应的文本框无法显示。将fields_for的参数从:authors改为:author后,文本框就能显示了,请问这是什么原因?
类定义
class Book < ApplicationRecord belongs_to :bookable, polymorphic: true has_many :authors accepts_nested_attributes_for :authors end class Author < ApplicationRecord end class Nonfiction < ApplicationRecord has_many :books, as: :book_table # 注意:此处关联名应为:bookable,与Book的多态关联对应 accepts_nested_attributes_for :books end
Book控制器
# GET /books/new def new @book = Book.new @book.authors.build end ... # POST /books or /books.json def create @bookable = load_book_table @book = @bookable.books.new(book_params) if @book.save! ... end end ... def book_params params.require(:book).permit(:book_name, :book_isbn, :book_description, :book_price, authors_attributes: [:author_name]) end
Nonfiction视图
<%= form_with(model: [@nonfiction, Book.new]) do |form|%> <div class="field"> <%= form.label :book_name %> <%= form.text_field :book_name %> <br> <%= form.fields_for :authors do |author_form| %> <%= author_form.label :author_name %> <%= author_form.text_field :author_name%> <% end %> <br> <%= form.label :book_isbn %> <%= form.text_field :book_isbn %> <br> <%= form.label :book_description %> <%= form.text_field :book_description %> <br> <%= form.label :book_price %> <%= form.text_field :book_price %> </div> <div class="actions"> <%= form.submit %> </div> <% end %>
原因解析
fields_for对has_many关联的渲染规则
Book模型定义的是has_many :authors的一对多关联,fields_for :authors只有在Book实例的authors集合中存在至少一个Author对象时,才会渲染对应的表单字段。但你在视图里直接用Book.new创建实例,这个新实例并没有像Book控制器new方法那样预先调用@book.authors.build构建关联的Author对象,所以fields_for :authors不会渲染任何内容。改用
:author时的特殊处理
当你把参数改成:author时,Rails找不到Book模型中对应的belongs_to :author关联,会把它当成虚拟嵌套属性处理。对于这种不存在的关联/属性,fields_for会默认渲染表单字段,不需要预先构建对象,所以文本框会显示出来——但这不符合你的关联设计,提交后也无法正确保存Author数据。
正确解决方法
方法1:在视图里预先构建关联对象
修改form_with的模型实例,通过tap方法构建关联的Author:
<%= form_with(model: [@nonfiction, Book.new.tap { |book| book.authors.build }]) do |form|%> <!-- 原有字段 --> <%= form.fields_for :authors do |author_form| %> <%= author_form.label :author_name %> <%= author_form.text_field :author_name%> <% end %> <!-- 原有字段 --> <% end %>
方法2:在控制器里初始化带关联的Book实例
在Nonfiction的控制器方法(比如new或edit)里初始化Book并构建authors:
def new @nonfiction = Nonfiction.new @book = @nonfiction.books.build @book.authors.build end
然后视图里使用预先初始化好的@book:
<%= form_with(model: [@nonfiction, @book]) do |form|%> <!-- 原有字段 --> <% end %>
额外修正:关联名错误
Nonfiction模型里的多态关联名写错了,需与Book里的bookable对应:
class Nonfiction < ApplicationRecord has_many :books, as: :bookable # 将:book_table改为:bookable accepts_nested_attributes_for :books end
内容的提问来源于stack exchange,提问作者cd4user

