为什么使用嵌入式数组在我的Rails多重选择中第一个元素总是空白?


84

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

我的问题是,按照我目前的方式,一切都会按我预期的那样工作,只是用户的子集数组在发送到服务器时始终具有空白的第一个元素。

这没什么大不了的,我可以写出代码以消除这种情况,但是我觉得我只是在犯某种语法错误,因为在我看来,默认的Rails行为不是故意的无需任何理由即可添加此空白元素。我一定错过了某件事,或者忘记了禁用某种设置。请帮助我了解我所缺少的内容(或为我提供一些好的文档,这些文档比在管间上可以找到的更深入地描述了此内容)。

MySQL数据库表“模型”:

  • 包含一个名为subset_arrayTEXT字段的列

类模型包括以下设置:

  • 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通话中是否缺少参数?

非常感谢:)

编辑:这是从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代码段吗?此外,即使在创建时也会发生此行为,还是仅在更新时发生?
Mike A.

添加了从f.select
robmclarty 2012年

@ mike-a确认了创建和更新的相同行为
robmclarty 2012年

我想知道我使用的浏览器是否可能是问题的一部分:它如何解释和表达与选择标签同名的隐藏输入标签的含义。因此,我在Chrome,Safari,Firefox和Opera中试用了我的应用,结果均相同。
robmclarty 2012年

1
请注意,所有使用的解决方案include_hidden: false都附带了陷阱。当您从选择框中删除所有值时,惯用语model.update(something_params)将不包括该字段。TL; DR,您将无法使该字段为空。
2014年

Answers:


51

隐藏字段是引起问题的原因。但这是有充分的理由的:取消选择所有值后,您仍然会收到一个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),但我认为它不适用于多重选择框,因为在这种情况下,通过参数发送的参数会形成数组,因此不会忽略任何值。

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


1
我创建了一个示例应用程序来演示该问题,同时我试图弄清楚如何正确处理它:P
robmclarty 2012年

1
我觉得该bug不一定在Rails中,而是在对该特定元素功能的模棱两可的规范和实现中。用户代理应该如何表达状态的改变的新空如果空的(或不选择)形式的元素被认为形式处理器成功的控制,从而与所述形式内容提交?
robmclarty 2012年

因此,如果这是一个错误,是否在Rails问题跟踪器中进行了记录?
bmihelac 2012年

69

在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规范实现等问题要容易得多。
robmclarty 2012年

向Rails核心团队成员传达您的疑虑。他们有权查看和接受补丁程序,并对后续问题负责。
Bogdan Gusiev

博格丹,如果多个为真,是否有必要关闭隐藏字段?升级到3.2时,这会破坏我的应用程序中的很多内容。我真的不喜欢这样的事实,因为Rails魔术添加了额外的空值,我必须清理控制器中的内容。
taelor 2013年

使用Rails 4
Bogdan Gusiev

2
@Donato,您必须将include_hidden设置为false(include_hidden:false)
Florian Widtmann

13

在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 } %>

到目前为止,这是最简单的答案!谢谢!
威廉汉普郡

11

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

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

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


谢谢。我将其添加到我的技巧包中,以防万一我不想在模型中这样做;)
robmclarty 2012年

5

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

Gotcha

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

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

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

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

要删除空白值:

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

3

在控制器中:

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

0

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

0

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

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

我的看起来更像是:

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

不知道这是否将来会让我头疼,但现在效果很好。


-3

使用jQuery:

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

从下拉菜单中删除空白选项的选项。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.