【问题标题】:Why is the first element always blank in my Rails multi-select, using an embedded array?为什么我的 Rails 多选中的第一个元素总是空白,使用嵌入式数组?
【发布时间】:2012-02-14 07:11:21
【问题描述】:

我正在使用 Rails 3.2.0.rc2。我有一个Model,其中我有一个静态Array,我通过一个表单提供,这样用户可以选择Array 的一个子集并将他们的选择保存到数据库中,存储在单个Model 中的列。我在存储Array 的数据库列上使用了序列化,Rails 正确地将用户的选择转换为 Yaml(并在读取该列时返回数组)。我正在使用多选表单输入进行选择。

我的问题是,按照我目前的方式,一切正常,除了用户的子集数组在发送到服务器时总是有一个空白的第一个元素。

这没什么大不了的,我可以在事后编写代码来消除它,但我觉得我只是在犯某种语法错误,因为在我看来,默认的 Rails行为会无缘无故地故意添加这个空白元素。我一定错过了什么或忘记禁用某种设置。请帮助我了解我所缺少的内容(或向我指出一些很好的文档,这些文档比我在 intertubes 上找到的内容更深入地描述了这一点)。

MySQL 数据库表“模型”:

  • 包括一个名为 subset_array 的列,它是一个 TEXT 字段

类模型包括以下设置:

  • serialize :subset_array
  • ALL_POSSIBLE_VALUES = [value1, value2, value3, ...]

模型编辑表单包含以下输入选项:

  • f.select :subset_array, Model::ALL_POSSIBLE_VALUES, {}, :multiple => true, :selected => @model.subset_array

PUT 从客户端到服务器看起来像这样:

  • 假设只选择了 value1 和 value3
  • "model" => { "subset_array" => ["", value1, value3] }

数据库更新如下:

  • UPDATE 'models' SET 'subset_array' = '--- \n- \"\"\n- value1\n- value3\n'

如您所见,数组中有这个额外的空白元素被发送并设置在数据库中。我该如何摆脱它?我的f.select 调用中是否缺少某个参数?

非常感谢 :)

EDIT:这是从f.select 语句生成的HTML 代码。看起来好像正在生成一个隐藏的输入,这可能是我的问题的原因?为什么会在那里?

<input name="model[subset_array][]" type="hidden" value>
<select id="model_subset_array" multiple="multiple" name="model[subset_array][]" selected="selected">
    <option value="value1" selected="selected">Value1</option>
    <option value="value2">Value2</option>
    <option value="value3" selected="selected">Value3</option>
    <option...>...</option>
</select>

【问题讨论】:

  • 您能发布f.select 生成的HTML sn-p 吗?此外,这种行为是在创建时发生还是仅在更新时发生?
  • 添加了从f.select生成的输出HTML标记的编辑
  • @mike-a 确认创建和更新的行为相同
  • 我想知道我使用的浏览器是否可能是问题的一部分:它如何解释和表达与选择标签同名的隐藏输入标签的含义。所以我在 Chrome、Safari、Firefox 和 Opera 中尝试了我的应用程序,每个都产生了相同的结果。
  • 请注意,所有使用include_hidden: false 的解决方案都带有一个陷阱。当您从选择框中删除所有值时,惯用的 model.update(something_params) 将不包含该字段。 TL;DR,您将无法将该字段设为空。

标签: html ruby-on-rails forms serialization multi-select


【解决方案1】:

在 Rails 4 中:

您将能够通过:include_hidden 选项。 https://github.com/rails/rails/pull/5414/files

作为现在的快速修复:您现在可以在模型中使用:

before_validation do |model|
  model.subset_array.reject!(&:blank?) if model.subset_array
end

这只会删除模型级别的所有空白值。

【讨论】:

  • 谢谢博格丹。我认为这是我将在我的应用程序中实施以解决该问题的那种事情。这比尝试解决用户代理的 HTML 规范实现等问题要容易得多。
  • 将您的担忧传达给 Rails 核心团队成员。他们被授权审查和接受补丁,并对由此产生的问题承担责任。
  • Bogdan,如果多个为真,是否有关闭隐藏字段的方法?升级到 3.2 时,这会破坏我的应用程序中的很多东西。我真的不喜欢我必须清理控制器中的东西,因为 Rails 魔术添加了额外的空值。
  • 用来自 Rails 4 的最新信息更新我的答案
  • @Donato 你必须将 include_hidden 设置为 false (include_hidden: false)
【解决方案2】:

隐藏字段是导致问题的原因。但它的存在是有充分理由的:当取消选择所有值时,您仍然会收到一个 subset_array 参数。来自 Rails 文档(您可能需要向右滚动才能看到所有这些内容):

  # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
  # web browsers do not send any value to server. Unfortunately this introduces a gotcha:
  # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
  # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
  # any mass-assignment idiom like
  #
  #   @user.update_attributes(params[:user])
  #
  # wouldn't update roles.
  #
  # To prevent this the helper generates an auxiliary hidden field before
  # every multiple select. The hidden field has the same name as multiple select and blank value.
  #
  # This way, the client either sends only the hidden field (representing
  # the deselected multiple select box), or both fields. Since the HTML specification
  # says key/value pairs have to be sent in the same order they appear in the
  # form, and parameters extraction gets the last occurrence of any repeated
  # key in the query string, that works for ordinary forms.

编辑: 最后一段建议您在选择某些内容时不应该看到空的,但我认为这是错误的。向 Rails 提交此提交的人(请参阅https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885)正在尝试使用 Rails 用于复选框的相同技巧(如此处所述:https://github.com/rails/rails/pull/1552),但我认为它不适用于多选框因为在这种情况下发送的参数形成一个数组,因此不会忽略任何值。

所以我的感觉是这是一个错误。

【讨论】:

  • 我创建了an example app 来演示这个问题,同时我试图弄清楚如何正确处理它:P
  • 我觉得这个错误不一定在 Rails 中,而是在这个特定元素功能的模棱两可的规范和实现中。如果空的(或未选择的)表单元素被认为是successful controls,用户代理应该如何向表单处理器表达新空的状态变化因此没有与表单内容一起提交?
  • 那么,如果这是一个错误,它是否记录在 Rails 问题跟踪器中?
  • 下面有人回答了这个问题;在 Rails 4+ 中,您需要使用 include_hidden: false
【解决方案3】:

在 Rails 4+ 中,将 select_tag 上的 :include_hidden 设置为 false

<%= form.grouped_collection_select :employee_id, Company.all, :employees, :name, :id, :name, { include_hidden: false }, { size: 6, multiple: true } %>

【讨论】:

  • 这是迄今为止最简单的答案!谢谢!
  • 是的,这就是答案。希望 SO 可以让您将已接受或流行的答案标记为已过时。
【解决方案4】:

另一个快速解决方法是使用这个控制器过滤器:

def clean_select_multiple_params hash = params
  hash.each do |k, v|
    case v
    when Array then v.reject!(&:blank?)
    when Hash then clean_select_multiple_params(v)
    end
  end
end

这种方式可以跨控制器重复使用,而无需触及模型层。

【讨论】:

  • 谢谢。如果我不想在模型中这样做,我会将其添加到我的技巧包中;)
【解决方案5】:

http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-check_box

遇到问题

HTML 规范说未选中的复选框或选择不成功, 因此网络浏览器不会发送它们。不幸的是,这引入了 一个陷阱:如果一个发票模型有一个付费标志,并且形式为 编辑已付款发票 用户取消选中其复选框,未付款 参数发送。所以,任何像

这样的质量分配习语

@invoice.update(params[:invoice]) 不会更新标志。

为了防止这种情况,助手会在之前生成一个辅助隐藏字段 非常复选框。隐藏字段具有相同的名称及其 属性模仿未选中的复选框。

这样,客户端要么只发送隐藏字段(代表 复选框未选中),或两个字段。由于 HTML 规范说键/值对必须以相同的顺序发送 它们出现在表单中,参数提取排在最后 查询字符串中出现任何重复键,适用于 普通形式。

删除空白值:

  def myfield=(value)
    value.reject!(&:blank?)
    write_attribute(:myfield, value)
  end

【讨论】:

    【解决方案6】:

    在控制器中:

    arr = arr.delete_if { |x| x.empty? }
    

    【讨论】:

      【解决方案7】:

      我在更新前使用控制器中的params[:review][:staff_ids].delete("") 修复了它。

      在我看来:

      = form_for @review do |f|
        = f.collection_select :staff_ids, @business.staff, :id, :full_name, {}, {multiple:true}
      = f.submit 'Submit Review'
      

      在我的控制器中:

      class ReviewsController < ApplicationController
        def create
        ....
          params[:review][:staff_ids].delete("")
          @review.update_attribute(:staff_ids, params[:review][:staff_ids].join(","))
        ....
        end
      end
      

      【讨论】:

        【解决方案8】:

        我通过在页面的 Javascript 部分中编写它来使其工作:

        $("#model_subset_array").val( <%= @model.subset_array %> );
        

        我的看起来更像以下:

        $("#modela_modelb_ids").val( <%= @modela.modelb_ids %> );
        

        不确定这是否会在将来让我头疼,但现在它工作正常。

        【讨论】:

          【解决方案9】:

          使用 jQuery:

          $('select option:empty').remove(); 
          

          从下拉列表中删除空白选项的选项。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-10-08
            • 2020-10-03
            • 2022-01-13
            • 2013-12-30
            • 1970-01-01
            • 2014-12-27
            • 2022-06-13
            • 1970-01-01
            相关资源
            最近更新 更多