来自具有多个记录的 SimpleForm 的参数散列对于一个记录的结构与两个记录的结构不同
Params hash from a SimpleForm with multiple records has a different structure for one record than for two
我努力将其消化成一个标题。
我正在使用 SimpleForm 构建一个包含一个或多个字段集的 bulk-edit 页面 - 一个用于已构建但尚未保存的 collection ActiveRecord 模型中的每条记录。
我的表单如下所示:
= simple_form_for :courses, method: :patch do |f|
- @courses.each do |course|
= field_set_tag do
= f.simple_fields_for 'courses[]', course do |c|
= c.input :title
.row
.medium-6.columns
= c.input :start_date, as: :string, input_html: { class: 'input-datepicker' }
.medium-6.columns
= c.input :end_date, as: :string, input_html: { class: 'input-datepicker' }
= f.submit 'Save', class: 'primary button'
一条记录的参数散列如下所示:
"courses"=>{"courses"=>[{"title"=>"Course Y", "start_date"=>"2017-09-26", "end_date"=>"2017-07-31"}]}
使用数组,而对于两条记录,它看起来像这样:
"courses"=>{"courses"=>{"1"=>{"title"=>"Course X", "start_date"=>"2018-01-16", "end_date"=>"2018-07-30"}, "2"=>{"title"=>"Course Y", "start_date"=>"2017-09-26", "end_date"=>"2018-07-30"}}}
具有 stringy-integer-keyed 哈希值。
当我尝试使用强参数时,这成为一个问题。经过多次黑客攻击,我最终得到了这段代码,它适用于多条记录,但在只提交一条记录时失败:
ActionController::Parameters
.new(courses: params[:courses][:courses].values)
.permit(courses: [:title, :start_date, :end_date])
.require(:courses)
失败,param is missing or the value is empty: courses
突出显示上面的 .require(:courses)
行。
问题是 "solved" 通过协调 single-record 案例与 multiple-record 案例:
if params[:courses][:courses].is_a?(Array)
params[:courses][:courses] = { '1': params[:courses][:courses][0] }
end
但感觉应该有更简单的方法。
有没有更好的方法来编写这个 use-case 的表格?我是不是漏掉了强参数的技巧?
我正在使用 rails 5.0.5
和 simple_form 3.5.0
。
"but it feels like there should be a simpler way of doing it."
是的,使用 ajax 发送个人 create/update 请求。这可以对用户透明地完成,并提供更简单的代码和更好的用户体验。
Rails 有 fields_for
和 accepts_nested_attributes
可用于在单个请求中 create/update 多个子记录和父记录。但它确实需要一个将记录分组在一起的关联,即使这样在验证方面也会变得非常复杂和复杂。
您想对其进行设置,以便每条记录都有一个单独的表格:
- courses.each do |c|
= render partial: 'courses/_form', course: c
表格真的没什么:
# courses/_form.haml.erb
= simple_form_for course, remote: true, html: { 'data-type' => 'json', class: 'course_form'} do |f|
= c.input :title
.row
.medium-6.columns
= c.input :start_date, as: :string, input_html: { class: 'input-datepicker' }
.medium-6.columns
= c.input :end_date, as: :string, input_html: { class: 'input-datepicker' }
= f.submit 'Save', class: 'primary button'
我们不使用 js.erb
模板,而是使用 'data-type' => 'json'
并编写我们自己的处理程序,因为它更容易定位正确的形式:
$(document).on('ajax:success', '.course_form', function(event, xhr, status){
var $form = $(this);
alert('Course created');
if (this.method.post) {
// changes form to update instead.
this.method = 'patch';
this.action = xhr.getResponseHeader('Location');
}
});
$(document).on('ajax:error', '.course_form', function(event, xhr, status){
var $form = $(this);
// @todo display errors
});
创建控制器非常简单:
class CoursesController
def create
@course = Course.new(course_params)
respond_to do |format|
if @course.save(course_params)
format.json { head :created, location: @course }
else
format.json do
render json: {
errors: @course.errors.full_messages
}
end
end
end
end
def update
@course = Course.find(params[:id])
respond_to do |format|
if @course.update(course_params)
format.json { head :ok }
else
render json: {
errors: @course.errors.full_messages
}
end
end
end
end
保持你的形式,将 strong 参数更改为:
params.require(:courses).permit(
courses: [
:id,
:title,
:start_date,
:end_date
]
)
使用此代码参数应该没有索引键,@courses
只是一个数组:
# CoursesController
def new
@courses = []
# creating 3 items for example
3.times do
@courses << Course.new
end
end
def create
errors = false
@courses= []
# keep courses in the array for showing errors
courses_params[:courses].each do |params|
course = Course.new(params)
@courses << course
unless course.valid?
errors = true
end
end
if errors
render :new
else
# if no errors save and redirect
@courses.each(&:save)
redirect_to courses_path, notice: 'courses created'
end
end
事实证明,如果表单由现有填充,f.simple_fields_for 'courses[]' ...
方法仅为该字段集提供一个ID记录,并且仅在这种情况下使用字符串 ID 映射到课程哈希的参数结构。对于 "fresh" 条记录,没有 ID,课程哈希值放在普通数组中。
这段代码是 运行 在从一年到另一年的 "rolling over" 课程的上下文中 - 复制以前的课程并更改日期。这意味着每个字段集都有原始课程的 ID。
提交表单时,会创建一条新记录并使用新属性对其进行验证,正是这条没有 ID 的新记录重新填充了表单。 "it only happens when one course is submitted" 东西是一条红鲱鱼 - 测试场景的产物。
所以值得注意:f.simple_fields_for 'courses[]' ...
为新记录创建一个数组,并为现有记录的属性创建哈希映射 ID。
我努力将其消化成一个标题。
我正在使用 SimpleForm 构建一个包含一个或多个字段集的 bulk-edit 页面 - 一个用于已构建但尚未保存的 collection ActiveRecord 模型中的每条记录。
我的表单如下所示:
= simple_form_for :courses, method: :patch do |f|
- @courses.each do |course|
= field_set_tag do
= f.simple_fields_for 'courses[]', course do |c|
= c.input :title
.row
.medium-6.columns
= c.input :start_date, as: :string, input_html: { class: 'input-datepicker' }
.medium-6.columns
= c.input :end_date, as: :string, input_html: { class: 'input-datepicker' }
= f.submit 'Save', class: 'primary button'
一条记录的参数散列如下所示:
"courses"=>{"courses"=>[{"title"=>"Course Y", "start_date"=>"2017-09-26", "end_date"=>"2017-07-31"}]}
使用数组,而对于两条记录,它看起来像这样:
"courses"=>{"courses"=>{"1"=>{"title"=>"Course X", "start_date"=>"2018-01-16", "end_date"=>"2018-07-30"}, "2"=>{"title"=>"Course Y", "start_date"=>"2017-09-26", "end_date"=>"2018-07-30"}}}
具有 stringy-integer-keyed 哈希值。
当我尝试使用强参数时,这成为一个问题。经过多次黑客攻击,我最终得到了这段代码,它适用于多条记录,但在只提交一条记录时失败:
ActionController::Parameters
.new(courses: params[:courses][:courses].values)
.permit(courses: [:title, :start_date, :end_date])
.require(:courses)
失败,param is missing or the value is empty: courses
突出显示上面的 .require(:courses)
行。
问题是 "solved" 通过协调 single-record 案例与 multiple-record 案例:
if params[:courses][:courses].is_a?(Array)
params[:courses][:courses] = { '1': params[:courses][:courses][0] }
end
但感觉应该有更简单的方法。
有没有更好的方法来编写这个 use-case 的表格?我是不是漏掉了强参数的技巧?
我正在使用 rails 5.0.5
和 simple_form 3.5.0
。
"but it feels like there should be a simpler way of doing it."
是的,使用 ajax 发送个人 create/update 请求。这可以对用户透明地完成,并提供更简单的代码和更好的用户体验。
Rails 有 fields_for
和 accepts_nested_attributes
可用于在单个请求中 create/update 多个子记录和父记录。但它确实需要一个将记录分组在一起的关联,即使这样在验证方面也会变得非常复杂和复杂。
您想对其进行设置,以便每条记录都有一个单独的表格:
- courses.each do |c|
= render partial: 'courses/_form', course: c
表格真的没什么:
# courses/_form.haml.erb
= simple_form_for course, remote: true, html: { 'data-type' => 'json', class: 'course_form'} do |f|
= c.input :title
.row
.medium-6.columns
= c.input :start_date, as: :string, input_html: { class: 'input-datepicker' }
.medium-6.columns
= c.input :end_date, as: :string, input_html: { class: 'input-datepicker' }
= f.submit 'Save', class: 'primary button'
我们不使用 js.erb
模板,而是使用 'data-type' => 'json'
并编写我们自己的处理程序,因为它更容易定位正确的形式:
$(document).on('ajax:success', '.course_form', function(event, xhr, status){
var $form = $(this);
alert('Course created');
if (this.method.post) {
// changes form to update instead.
this.method = 'patch';
this.action = xhr.getResponseHeader('Location');
}
});
$(document).on('ajax:error', '.course_form', function(event, xhr, status){
var $form = $(this);
// @todo display errors
});
创建控制器非常简单:
class CoursesController
def create
@course = Course.new(course_params)
respond_to do |format|
if @course.save(course_params)
format.json { head :created, location: @course }
else
format.json do
render json: {
errors: @course.errors.full_messages
}
end
end
end
end
def update
@course = Course.find(params[:id])
respond_to do |format|
if @course.update(course_params)
format.json { head :ok }
else
render json: {
errors: @course.errors.full_messages
}
end
end
end
end
保持你的形式,将 strong 参数更改为:
params.require(:courses).permit(
courses: [
:id,
:title,
:start_date,
:end_date
]
)
使用此代码参数应该没有索引键,@courses
只是一个数组:
# CoursesController
def new
@courses = []
# creating 3 items for example
3.times do
@courses << Course.new
end
end
def create
errors = false
@courses= []
# keep courses in the array for showing errors
courses_params[:courses].each do |params|
course = Course.new(params)
@courses << course
unless course.valid?
errors = true
end
end
if errors
render :new
else
# if no errors save and redirect
@courses.each(&:save)
redirect_to courses_path, notice: 'courses created'
end
end
事实证明,如果表单由现有填充,f.simple_fields_for 'courses[]' ...
方法仅为该字段集提供一个ID记录,并且仅在这种情况下使用字符串 ID 映射到课程哈希的参数结构。对于 "fresh" 条记录,没有 ID,课程哈希值放在普通数组中。
这段代码是 运行 在从一年到另一年的 "rolling over" 课程的上下文中 - 复制以前的课程并更改日期。这意味着每个字段集都有原始课程的 ID。
提交表单时,会创建一条新记录并使用新属性对其进行验证,正是这条没有 ID 的新记录重新填充了表单。 "it only happens when one course is submitted" 东西是一条红鲱鱼 - 测试场景的产物。
所以值得注意:f.simple_fields_for 'courses[]' ...
为新记录创建一个数组,并为现有记录的属性创建哈希映射 ID。