web-dev-qa-db-ja.com

Rails multi-select、埋め込み配列を使用する場合、最初の要素が常に空白になるのはなぜですか?

私はRails 3.2.0.rc2を使用しています。 Modelがあります。この中に、ユーザーがArrayのサブセットを選択し、Arrayの単一の列に格納されたデータベースに選択を保存できるようにフォームを通じて提供する静的なModelがあります。 Arrayを格納するデータベース列でシリアライズを使用し、Railsはユーザーの選択をYamlに正しく変換します(その列を読み取るときに配列に戻します)。複数選択フォーム入力を使用して選択を行っています。

私の問題は、私が現在持っている方法で、ユーザーのサブセット配列がサーバーに送信されるときに常に空白の最初の要素を持つことを除いて、すべてが期待どおりに機能することです

これは大したことではなく、事後にコードを書くことができますが、デフォルトのRails動作は、何らかの理由なく意図的にこの空白要素を追加します。何かを逃したか、何らかの設定を無効にするのを忘れたに違いありません。不足しているものを理解するのを手伝ってください(または、インターチューブで見つけることができたものよりも詳細にこれを説明するいくつかの優れたドキュメントを示してください)。

MySQLデータベーステーブル「モデル」:

  • tEXTフィールドであるsubset_arrayという名前の列が含まれます

クラスモデルには次の設定が含まれます。

  • 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>
80
robmclarty

非表示フィールドが問題の原因です。しかし、そこには正当な理由があります。すべての値が選択解除された場合でも、subset_arrayパラメーターを受け取ります。 Rails docs(このすべてを表示するには右にスクロールする必要がある場合があります):

  # 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.

EDIT:最後の段落は、何かが選択された場合に空のものを見るべきではないことを示唆していますが、間違っていると思います。 Rails( https://github.com/Rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 を参照)にこのコミットを行った人は、 Railsはチェックボックスに使用します(ここで説明したとおり: https://github.com/Rails/rails/pull/1552 )が、できるとは思わないこの場合、送信されるパラメーターは配列を形成するため、値が無視されないため、複数選択ボックスで機能します。

だから、これはバグだと思う。

51
Mike A.

In 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

これにより、モデルレベルですべての空白値が削除されます。

67
Bogdan Gusiev

別の簡単な修正方法は、このコントローラーフィルターを使用することです。

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

この方法は、モデルレイヤーに触れることなくコントローラー全体で再利用できます。

11
Max

In 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 } %>
8
Martin

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
5
devishot

コントローラー内:

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

更新前にコントローラーで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
Bruno

ページのJavascript部分にこれを書くことで動作させます:

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

私は次のように見えます:

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

これが将来頭痛の種になるかどうかはわかりませんが、今ではうまくいきます。

0
imaginabit