Warum ist das erste Element in meiner Rails-Mehrfachauswahl mithilfe eines eingebetteten Arrays immer leer?

84

Ich benutze Rails 3.2.0.rc2 . Ich habe eine Model, in der ich eine Statik habe, Arraydie ich über ein Formular anbiete, so dass Benutzer eine Teilmenge von auswählen Arrayund ihre Auswahl in der Datenbank speichern können, die in einer einzelnen Spalte in gespeichert ist Model. Ich habe serialize für die Datenbankspalte verwendet, in der das gespeichert Arrayist, und Rails konvertiert die Benutzerauswahl korrekt in Yaml (und zurück in ein Array, wenn diese Spalte gelesen wird). Ich verwende eine Mehrfachauswahl-Formulareingabe, um eine Auswahl zu treffen.

Mein Problem ist, dass, so wie ich es derzeit habe, alles so funktioniert, wie ich es erwarten würde, außer dass das Subset-Array des Benutzers immer ein leeres erstes Element hat, wenn es an den Server gesendet wird.

Dies ist keine große Sache, und ich könnte Code schreiben, um das nachträglich auszuschneiden, aber ich habe das Gefühl, dass ich nur einen syntaktischen Fehler mache, da es mir nicht so vorkommt, als ob das Standardverhalten von Rails absichtlich wäre Fügen Sie dieses leere Element ohne Grund hinzu. Ich muss etwas verpasst oder vergessen haben, eine Einstellung zu deaktivieren. Bitte helfen Sie mir zu verstehen, was mir fehlt (oder verweisen Sie mich auf eine gute Dokumentation, die dies ausführlicher beschreibt als das, was ich auf den Zwischenrohren finden konnte).

MySQL-Datenbanktabelle 'Modelle':

  • enthält eine Spalte mit dem Namen subset_arrayTEXT

Das Klassenmodell enthält die folgenden Einstellungen:

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

Das Formular zum Bearbeiten von Modellen enthält die folgende Eingabemöglichkeit:

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

Das PUT vom Client auf den Server sieht ungefähr so ​​aus:

  • Angenommen, nur Wert1 und Wert3 sind ausgewählt
  • "model" => { "subset_array" => ["", value1, value3] }

Das Datenbank-Update sieht folgendermaßen aus:

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

Wie Sie sehen können, wird dieses zusätzliche leere Element im Array gesendet und in der Datenbank festgelegt. Wie werde ich das los? Gibt es einen Parameter, den ich in meinem f.selectAnruf vermisse ?

Vielen Dank geschätzt :)

BEARBEITEN : Dies ist der generierte HTML-Code aus der f.selectAnweisung. Es sieht so aus, als würde eine versteckte Eingabe generiert, die die Ursache für mein Problem sein könnte. Warum ist das da?

<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>
robmclarty
quelle
Könnten Sie das HTML-Snippet posten, das f.selectgeneriert wird? Tritt dieses Verhalten auch beim Erstellen auf oder handelt es sich nur um ein Update?
Mike A.
EDIT des Ausgabe-HTML- f.select
Markups hinzugefügt,
@ Mike-a Bestätigt das gleiche Verhalten für erstellen und aktualisieren
Robmclarty
Ich fragte mich, ob der von mir verwendete Browser möglicherweise Teil des Problems ist: Wie interpretiert und drückt er die Bedeutung des versteckten Eingabe-Tags mit demselben Namen wie das Select-Tag aus? Also habe ich meine App in Chrome, Safari, Firefox und Opera ausprobiert und jeweils die gleichen Ergebnisse erzielt.
Robmclarty
1
Beachten Sie, dass alle verwendeten Lösungen include_hidden: falsemit einem Gotcha geliefert werden. Wenn Sie alle Werte aus dem Auswahlfeld entfernen, enthält die Redewendung model.update(something_params)dieses Feld nicht. TL; DR Sie können das Feld nicht leer machen.
Damon Aw

Antworten:

51

Das versteckte Feld verursacht das Problem. Aber es gibt einen guten Grund: Wenn alle Werte abgewählt sind, erhalten Sie immer noch einen Parameter subset_array. In den Rails-Dokumenten (möglicherweise müssen Sie nach rechts scrollen, um all dies zu sehen):

  # 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: Der letzte Absatz schlägt vor, dass Sie den leeren nicht sehen sollten, wenn etwas ausgewählt ist, aber ich denke, dass es falsch ist. Die Person, die dieses Commit für Rails vorgenommen hat (siehe https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ), versucht, denselben Trick auszuführen, den Rails für Kontrollkästchen verwendet (wie hier erwähnt: https://github.com) / Rails / Rails / Pull / 1552 ), aber ich glaube nicht, dass es für ein Mehrfachauswahlfeld funktionieren kann, da die übergesendeten Parameter in diesem Fall ein Array bilden und daher kein Wert ignoriert wird.

Mein Gefühl ist also, dass dies ein Fehler ist.

Mike A.
quelle
1
Ich habe eine Beispiel-App erstellt , um das Problem zu demonstrieren, während ich versuche, herauszufinden, wie man es richtig
handhabt
1
Ich denke, der Fehler liegt nicht unbedingt in Rails, sondern in einer mehrdeutigen Spezifikation und Implementierung für diese spezielle Elementfunktionalität. Wie sollte ein User-Agent eine Zustandsänderung ausdrückt die neu leert zum Formular - Prozessor , wenn leer (oder nicht ausgewählt) Formelemente betrachtet werden nicht als erfolgreich Kontrollen und somit nicht mit den Formularinhalten eingereicht?
Robmclarty
Wenn dies also ein Fehler ist, ist er im Rails Issue Tracker dokumentiert?
Bmihelac
69

In Rails 4:

Sie können die :include_hiddenOption übergeben. https://github.com/rails/rails/pull/5414/files

Als schnelle Lösung für den Moment: Sie können jetzt in Ihrem Modell verwenden:

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

Dadurch werden nur alle leeren Werte auf Modellebene gelöscht.

Bogdan Gusiev
quelle
Danke Bogdan. Ich denke, so etwas werde ich in meiner App implementieren, um das Problem zu umgehen. Dies ist viel einfacher als der Versuch, Probleme mit den HTML-Spezifikationsimplementierungen der Benutzeragenten oder Ähnlichem zu umgehen.
Robmclarty
Bringen Sie Ihre Bedenken zu den Mitgliedern des Rails-Kernteams. Sie sind berechtigt, Patches zu überprüfen und zu akzeptieren und die Verantwortung für daraus resultierende Probleme zu übernehmen.
Bogdan Gusiev
Bogdan, gibt es überhaupt eine Möglichkeit, das versteckte Feld auszuschalten, wenn mehrere wahr sind? Dies führt beim Upgrade auf 3.2 zu erheblichen Problemen in meiner App. Ich mag die Tatsache wirklich nicht, dass ich Dinge im Controller aufräumen muss, weil Rails Magic zusätzliche leere Werte hinzufügt.
Taelor
Aktualisiere meine Antwort mit den kommenden Informationen von Rails 4
Bogdan Gusiev
2
@ Donato müssen Sie include_hidden auf false setzen (include_hidden: false)
Florian Widtmann
13

In Rails 4+ setzen Sie: include_hidden auf select_tag auf false

<%= form.grouped_collection_select :employee_id, Company.all, :employees, :name, :id, :name, { include_hidden: false }, { size: 6, multiple: true } %>
Martin
quelle
Dies ist bei weitem die einfachste Antwort! Vielen Dank!
Kyle Krzeski
11

Eine weitere schnelle Lösung ist die Verwendung dieses Controller-Filters:

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

Diese Methode kann über Controller hinweg wiederverwendet werden, ohne die Modellebene zu berühren.

Max
quelle
Vielen Dank. Ich füge dies in meine
Trickkiste ein,
5

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

Erwischt

Die HTML-Spezifikation besagt, dass nicht aktivierte Kontrollkästchen oder Auswahlen nicht erfolgreich sind und daher von Webbrowsern nicht gesendet werden. Leider führt dies zu einem Problem: Wenn ein Rechnungsmodell über ein bezahltes Flag verfügt und der Benutzer in dem Formular, in dem eine bezahlte Rechnung bearbeitet wird, das Kontrollkästchen deaktiviert, wird kein bezahlter Parameter gesendet. Also, jede Massenzuweisungssprache mag

@ invoice.update (params [: rechnung]) würde das Flag nicht aktualisieren.

Um dies zu verhindern, generiert der Helfer vor dem Kontrollkästchen ein zusätzliches verstecktes Feld. Das ausgeblendete Feld hat denselben Namen und seine Attribute ahmen ein deaktiviertes Kontrollkästchen nach.

Auf diese Weise sendet der Client entweder nur das ausgeblendete Feld (das Kontrollkästchen ist deaktiviert) oder beide Felder. Da die HTML-Spezifikation besagt, dass Schlüssel / Wert-Paare in derselben Reihenfolge gesendet werden müssen, in der sie im Formular angezeigt werden, und die Parameterextraktion das letzte Auftreten eines wiederholten Schlüssels in der Abfragezeichenfolge erhält, funktioniert dies für normale Formulare.

So entfernen Sie leere Werte:

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

In der Steuerung:

arr = arr.delete_if { |x| x.empty? }
Mauro
quelle
0

Ich habe es params[:review][:staff_ids].delete("")vor dem Update mit dem im Controller behoben.

Aus meiner Sicht:

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

In meinem Controller:

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

Ich mache es möglich, indem ich dies in den Javascript-Teil der Seite schreibe:

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

Meins sieht eher so aus:

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

Ich bin mir nicht sicher, ob mir das in Zukunft Kopfschmerzen bereiten wird, aber jetzt funktioniert es einwandfrei.

imaginabit
quelle
-3

Verwenden Sie jQuery:

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

Option zum Entfernen leerer Optionen aus der Dropdown-Liste.

user1875926
quelle