具有 Rails form_with 和强参数的三重嵌套形式
Triple-nested form with Rails form_with and strong parameters
我是 运行 Rails 6.0.0.rc1 并且无法使三重嵌套表单正常工作。我有 products
有 options
有 option_values
。因此,一个产品可能有一个名为 "Color" 的选项和一个名为 "Red." 的选项值 我想在产品表单的 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
选项值模型:
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 %>
产品控制器:
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
首先,您需要将表单更改为在 option
中嵌套 option_values
,并在选项值中添加 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
更新:
因为我试图跟进 this Nested Form Railscast,最大的问题是我没有意识到 Ryan Bates 正在使用的版本是 "Edit",而不是 "New",所以我通过控制台添加了产品、选项和值,并让表单使用以下代码:
_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 的唯一区别是在控制器中使用了强参数,因此您只需要像这样嵌套它们:
产品控制器
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
选项控制器
def option_params
params.require(:option).permit(:account_id, :name, option_values_attributes [:id, :account_id, :name, :_destroy])
end
选项值控制器
def option_value_params
params.require(:option_value).permit(:account_id, :option_id, :name)
end
我不会在控制器中构建嵌套对象,而是像 Railscast 剧集中那样使用 Javascript 或她的回答中建议的 Cocoon gem as Vasilisa。
只是想分享最终有效的代码,以防其他人遇到类似问题。我认为 Railscast 虽然很旧,但仍然是对 Rails 中嵌套表单的一个很好的介绍,但您只需要了解使用 form_with 和强参数所需的更改。非常感谢 Vasilisa 帮我解决了这个问题。
您在遵循 Rails Nested Form Railscast 时需要注意的主要 "gotchas" 是:
- form_with 与旧的 rails form_tag
有一些不同的语法
- 确保您在创建表单块时没有任何拼写错误或名称问题,因为它们嵌套了两次
- 与控制器中的嵌套参数相同,请注意语法和拼写错误
- 请注意,Ryan Bates 正在使用未通过他正在构建的表单添加的数据进行演示,因此如果您想继续操作,则需要在控制台中创建一些数据
- 使用强参数,您必须明确列出
:_destroy
作为参数,以便他的 "Remove" 复选框起作用
我是 运行 Rails 6.0.0.rc1 并且无法使三重嵌套表单正常工作。我有 products
有 options
有 option_values
。因此,一个产品可能有一个名为 "Color" 的选项和一个名为 "Red." 的选项值 我想在产品表单的 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
选项值模型:
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 %>
产品控制器:
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
首先,您需要将表单更改为在 option
中嵌套 option_values
,并在选项值中添加 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
更新:
因为我试图跟进 this Nested Form Railscast,最大的问题是我没有意识到 Ryan Bates 正在使用的版本是 "Edit",而不是 "New",所以我通过控制台添加了产品、选项和值,并让表单使用以下代码:
_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 的唯一区别是在控制器中使用了强参数,因此您只需要像这样嵌套它们:
产品控制器
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
选项控制器
def option_params
params.require(:option).permit(:account_id, :name, option_values_attributes [:id, :account_id, :name, :_destroy])
end
选项值控制器
def option_value_params
params.require(:option_value).permit(:account_id, :option_id, :name)
end
我不会在控制器中构建嵌套对象,而是像 Railscast 剧集中那样使用 Javascript 或她的回答中建议的 Cocoon gem as Vasilisa。
只是想分享最终有效的代码,以防其他人遇到类似问题。我认为 Railscast 虽然很旧,但仍然是对 Rails 中嵌套表单的一个很好的介绍,但您只需要了解使用 form_with 和强参数所需的更改。非常感谢 Vasilisa 帮我解决了这个问题。
您在遵循 Rails Nested Form Railscast 时需要注意的主要 "gotchas" 是:
- form_with 与旧的 rails form_tag 有一些不同的语法
- 确保您在创建表单块时没有任何拼写错误或名称问题,因为它们嵌套了两次
- 与控制器中的嵌套参数相同,请注意语法和拼写错误
- 请注意,Ryan Bates 正在使用未通过他正在构建的表单添加的数据进行演示,因此如果您想继续操作,则需要在控制台中创建一些数据
- 使用强参数,您必须明确列出
:_destroy
作为参数,以便他的 "Remove" 复选框起作用