具有嵌套has_many和has_one的嵌套表单

时间:2014-10-31 20:19:17

标签: ruby-on-rails-4 has-many-through has-one nested-form-for cocoon-gem

我基本上遇到了与这篇文章相同的问题,但我的情况略有不同:has_many nested form with a has_one nested form within it

但正如该帖中提到的其他人所提供的答案并未解决问题。

设置关系,以便Invoice has_many项目和每个Item has_one Modifier。我正在尝试制作一个form_for Invoice,允许用户创建许多项目,每个项目都有一个修饰符。

模型

class Invoice < ActiveRecord::Base
  has_many :items
  has_many :modifiers, through: :items

  accepts_nested_attributes_for :items
end

class Item < ActiveRecord::Base
  belongs_to :invoice
  belongs_to :modifier

  accepts_nested_attributes_for :modifier
end

class Modifier < ActiveRecord::Base
  has_one :item
end

CONTROLLER

class Invoice
  def new
    @invoice = Invoice.new
  end

  def edit
  end

  ...
end

次(HAML)

invoice.html.haml:
= form_for @invoice do |f|
  = f.text_field :status

  = f.fields_for :items do |builder|
    = render partial: "items/fields", locals: { :f => builder }
  = link_to_add_association 'New Item', f, :items, partial: "items/fields", id: "add-item-button"

items/_fields.html.haml:
.nested-fields
  - @item = @invoice.items.build
  = f.fields_for :modifier, @item.build_modifier do |modifier|
    = modifier.text_field :name

让我们回顾一下发生了什么。为了构建嵌套的has_one关系,我在嵌套字段partial中构建了一个Item,以便我可以构建has_one修饰符。这是因为rails要求你在has_one关系中显式调用'build_something'(通常在控制器的new中调用它,但我只想在有人单击New Item按钮时构建)。为了创建新的发票,此代码完美运行。检查控制台,我看到关系已创建,我可以验证是否已成功创建修改器。

然而,当我回去编辑发票时,cocoon知道我已经有了一个修饰符,因此它调用partial一次为我的单个修饰符创建必要的fields_。这些字段是空的。这是有道理的,因为当茧渲染部分时,它正在构建一个带有新修饰符的新代码并将字段设置为空白。我可以确认这是发生了什么,因为一旦我正确地保存了修改器,我可以进入我的部分,删除两个构建调用,并查看编辑页面,正确显示保存的修改器信息就好了。

当然,现在我已经删除了构建调用,表单不再保存我创建的任何修改器。基本上,我需要在那里构建调用来构建新的Modifiers,但如果我想查看它们,我就不能在那里使用它们。

有没有人能解决这种情况?我发现了多个堆栈溢出问题,但没有一个能解决这个问题。

1 个答案:

答案 0 :(得分:3)

您说has_one但是在您的模型中我看到了has_many

您嵌套的部分items/_fields是错误的:您构建了一个额外的项目,并且不需要这样做。 link_to_add_association中的Coccon构建了一个要插入的新项目。

有两种方法可以做你想做的事。

1)在部分

要在您的部分中正确处理它,您可以执行以下操作(items/_fields.html.haml):

.nested-fields
  - f.object.build_modifier if f.object.new_record?  
  = f.fields_for :modifier do |modifier|
    = modifier.text_field :name 

在rails中,要引用表单的对象,您可以使用f.object。请注意这将起作用,但我们必须检查它是否是新创建的对象。或者,我们可以检查modifier是否存在。

2)使用:wrap_object选项(documentation

Cocoon允许使用新创建的对象执行一些额外的代码。所以在你的情况下会变成:

= link_to_add_association('New item', f, :items,
        :wrap_object => Proc.new { |item| item.build_modifier; item })