具有Rails form_with和强大参数的三重嵌套表单

时间:2019-05-30 17:48:33

标签: ruby-on-rails ruby forms nested-forms strong-parameters

我正在运行Rails 6.0.0.rc1,但无法使三重嵌套表单正常工作。我有products,有options,有option_values。因此,一个产品可能有一个名为“颜色”的选项和一个名为“红色”的选项值。我想在“产品”表单的classic nested form中创建所有这些文件。

该表格有效,我可以保存带有期权的产品,但不能保存提交时的期权价值。我不确定为什么在尝试将fields_for选项值嵌入fields_for选项内时为何不起作用。

我在这里做错了什么?我觉得我缺少明显的东西,但无法弄清楚。 (可能不相关,但是请注意,我需要将每个对象的范围限制为account_id,而我的用户has_one :account是隐藏字段的原因。)

这是我的产品型号:

class Product < ApplicationRecord
  belongs_to :account

  has_many :options, dependent: :destroy
  accepts_nested_attributes_for :options, allow_destroy: true

  validates :account_id,  presence: true
  validates :name,        presence: true
end

选项模型:

class Option < ApplicationRecord
  belongs_to :account
  belongs_to :product

  has_many :option_values, dependent: :destroy
  accepts_nested_attributes_for :option_values, allow_destroy: true

  validates :account_id,  presence: true
  validates :name,        presence: true
end

OptionValue模型:

class OptionValue < ApplicationRecord
  belongs_to :account
  belongs_to :option

  validates :account_id,  presence: true
  validates :name,        presence: true
end

这是产品表单:

<%= form_with(model: product, local: true) do |f| %>
  <%= f.fields_for :options do |options_form| %>
    <fieldset class='form-group'>
      <%= options_form.hidden_field :account_id, value: current_user.account.id %>
      <%= options_form.label :name, 'Option' %>
      <%= options_form.text_field :name, class: 'form-control' %>
    </fieldset>

    <%= f.fields_for :option_values do |values_form| %>
      <fieldset class='form-group'>
        <%= values_form.label :name, 'Value' %>
        <%= values_form.text_field :name, class: 'form-control' %>
      </fieldset>
    <% end %>
  <% end %>
<% end %>

ProductsController:

class ProductsController < ApplicationController
  def new
    @product = Product.new
    @product.options.build
  end

  def create
    @account = current_user.account
    @product = @account.products.build(product_params)

    respond_to do |format|
      if @product.save
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  private 
    def product_params
      params.require(:product).permit(
        :account_id, :name,
        options_attributes: [
          :id, :account_id, :name, :_destroy,
          option_values_attributes:[:id, :account_id, :name, :_destroy ]
        ]
      )
    end
end

2 个答案:

答案 0 :(得分:1)

首先,您需要更改表单以将option_values嵌套在option内,并将account_id字段添加到选项值:

<%= form_with(model: product, local: true) do |f| %>
  <%= f.fields_for :options do |options_form| %>
    <fieldset class='form-group'>
      <%= options_form.hidden_field :account_id, value: current_user.account.id %>
      <%= options_form.label :name, 'Option' %>
      <%= options_form.text_field :name, class: 'form-control' %>
    </fieldset>

    <%= options_form.fields_for :option_values do |values_form| %>
      <fieldset class='form-group'>
        <%= values_form.hidden_field :account_id, value: current_user.account.id %>
        <%= values_form.label :name, 'Value' %>
        <%= values_form.text_field :name, class: 'form-control' %>
      </fieldset>
    <% end %>
  <% end %>
<% end %>

此外,您还需要在控制器中构建嵌套记录。另一种选择是通过javascript动态构建它们(例如,查看cocoon gem)。要构建3个选项,每个选项具有3个值:

def new 
  @account = current_user.account 
  # it is better to create associated product 
  @product = @account.products.new 
  3.times do 
    option = @product.options.build 
    3.times { option.option_values.build } 
  end 
end

答案 1 :(得分:0)

更新:

因为我尝试遵循this Nested Form Railscast,所以最大的问题是我没有意识到Ryan Bates使用的版本是“编辑”,而不是“新”,因此我添加了产品,通过控制台输入选项和值,并使用该代码处理表单:

_form.html.erb

  <%= f.fields_for :options do |builder| %>
    <%= render 'option_fields', f: builder %>
  <% end %>

_option_fields.html.erb

<fieldset class='form-group'>
  <%= f.hidden_field :account_id, value: current_user.account.id %>

  <%= f.label :name, 'Option' %>
  <%= f.text_field :name, class: 'form-control' %>
  <br>
  <%= f.check_box :_destroy, class: 'form-check-input' %>
  <%= f.label :_destroy, 'Remove Option' %>

  <small id="optionHelp" class="form-text text-muted">
    (e.g. "Size" or "Color")
  </small>

  <%= f.fields_for :option_values do |builder| %>
    <%= render 'option_value_fields', f: builder %>
  <% end %>

</fieldset>

_option_value_fields.html.erb

<fieldset class='form-group'>
  <%= f.hidden_field :account_id, value: current_user.account.id %>
  <%= f.label :name, 'Value' %>
  <%= f.text_field :name, class: 'form-control' %>
  <br>
  <%= f.check_box :_destroy, class: 'form-check-input' %>
  <%= f.label :_destroy, 'Remove Value' %>

  <small id="optionValueHelp" class="form-text text-muted">
    (e.g. "Small, Medium, Large" or "Red, Green, Blue")
  </small>
</fieldset>

此外,与Railscast的唯一区别是在控制器中使用了强大的参数,因此您只需要像这样嵌套它们:

ProductsController

def product_params
      params.require(:product).permit(:account_id, :name, options_attributes [:id, :account_id, :name, :_destroy, option_values_attributes: [:id, :account_id, :name, :_destroy]])
end

OptionsController

def option_params
     params.require(:option).permit(:account_id, :name, option_values_attributes [:id, :account_id, :name, :_destroy])
end

OptionValuesController

def option_value_params
     params.require(:option_value).permit(:account_id, :option_id, :name)
end

与其在控制器中构建嵌套对象,不如在Railscast情节中使用Javascript或在答案中建议使用Cocoon gem,如Vasilisa来实现。

只想共享实际上最终可以工作的代码,以防其他人遇到类似问题。我认为Railscast尽管很老,但仍然是Rails中嵌套表单的很好的介绍,但是您只需要了解使用form_with和strong参数所需的更改。非常感谢Vasilisa帮助我解决了这个问题。

遵循Rails Nested Form Railscast时需要注意的主要“陷阱”是这样的:

  • form_with的语法与旧版rails form_tag的语法不同
  • 在创建表单块时,请确保您没有任何错字或名称问题,因为它们被嵌套两次
  • 与控制器中的嵌套参数相同,只是要注意您的语法和拼写错误
  • 请注意,瑞安·贝茨(Ryan Bates)正在使用未通过他所建立的表单添加的数据进行演示,因此,如果您要遵循此步骤,则需要在控制台中创建一些数据
  • 使用强大的参数,您必须明确列出:_destroy作为参数,以便其“删除”复选框起作用