【问题标题】:Params hash from a SimpleForm with multiple records has a different structure for one record than for two来自具有多条记录的 SimpleForm 的参数散列对于一条记录与两条记录具有不同的结构
【发布时间】:2017-11-07 17:52:01
【问题描述】:

我很难把它消化成一个标题。

我正在使用 SimpleForm 构建一个包含一个或多个字段集的批量编辑页面 - 一个用于已构建但尚未保存的 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"}}}

使用字符串整数键控哈希。

当我尝试使用强参数时,这会成为一个问题。经过多次黑客攻击,我最终得到了这段代码,它适用于多条记录,但仅在提交一条时失败:

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) 行。

通过协调单记录情况与多记录情况来“解决”问题:

if params[:courses][:courses].is_a?(Array)
  params[:courses][:courses] = { '1': params[:courses][:courses][0] }
end

但感觉应该有更简单的方法。

有没有更好的方法来为这个用例编写表单?我错过了具有强参数的技巧吗?

我正在使用rails 5.0.5simple_form 3.5.0

【问题讨论】:

  • 有点摸不着头脑,但如果将= f.simple_fields_for 'courses[]', course do |c| 替换为= f.simple_fields_for :courses, course do |c|,可能会导致每次都使用相同类型的参数散列。
  • "但感觉应该有更简单的方法。" - 是的,使用 ajax 发送单独的创建/更新请求。
  • 但是您可以使用.fetch 而不是.require,但它仍然会导致无法维护的混乱。
  • @TomAranda 我想我们已经尝试过了——它只会导致页面上的最后一个字段集被提交

标签: ruby-on-rails ruby simple-form simple-form-for


【解决方案1】:

“但感觉应该有更简单的方法。”

是的,使用 ajax 发送单独的创建/更新请求。这可以对用户透明地完成,并提供更简单的代码和更好的用户体验。

Rails 具有fields_foraccepts_nested_attributes,可用于在单个请求中创建/更新多个子记录和父记录。但它确实需要一个将记录组合​​在一起的关联,即使这样在验证方面也会变得非常麻烦和复杂。

您想设置它以便为每条记录提供一个单独的表单:

- 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

【讨论】:

  • 这是一个不错的方法,谢谢。我认为我不会将它用于这项工作,但它肯定会在将来有用。将是进行就地编辑的好方法!
【解决方案2】:

保留您的表单,将强参数更改为:

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

【讨论】:

  • 遗憾的是,强参数代码仅适用于哈希数组。在 ID 散列到属性散列的情况下,它是无效的。还是谢谢!
【解决方案3】:

事实证明,f.simple_fields_for 'courses[]' ... 方法在表单由现有记录填充时为该字段集提供 ID,并且字符串 ID 的 params 结构映射到课程哈希仅在这种情况下使用。对于“新鲜”的记录,没有 ID,课程哈希被放置在一个普通数组中。

这段代码是在将课程从一年“滚动”到另一年的上下文中运行的 - 复制以前的课程并更改日期。这意味着每个字段集都有原始课程的 ID。

提交表单后,创建了一条新记录并使用新属性进行了验证,正是这条没有 ID 的新记录重新填充了表单。 “只有在提交一门课程时才会发生”这件事是一个红鲱鱼 - 测试场景的产物。

值得注意的是:f.simple_fields_for 'courses[]' ... 为新记录创建一个数组,并为现有记录创建一个哈希映射 ID 到属性。

【讨论】:

    猜你喜欢
    • 2021-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多