81 votes

Pourquoi le premier élément est toujours vide dans ma multi-sélection Rails, en utilisant un tableau incorporé ?

J'utilise Rails 3.2.0.rc2 . J'ai un Model dans lequel j'ai un fichier statique Array que je propose par le biais d'un formulaire de sorte que les utilisateurs puissent sélectionner un sous-ensemble de Array et sauvegardent leur sélection dans la base de données, stockée dans une seule colonne de la base de données. Model . J'ai utilisé serialize sur la colonne de la base de données qui stocke l'adresse de l'utilisateur. Array et Rails convertit correctement les sélections des utilisateurs en Yaml (et de nouveau en tableau lors de la lecture de cette colonne). J'utilise un formulaire à sélection multiple pour effectuer les sélections.

Le problème est que, dans l'état actuel des choses, tout fonctionne comme prévu, sauf que le tableau de sous-ensembles de l'utilisateur comporte toujours un premier élément vide lorsqu'il est envoyé au serveur.

Ce n'est pas très grave, et je pourrais écrire du code pour le supprimer après coup, mais j'ai l'impression de faire une erreur de syntaxe, car il ne me semble pas que le comportement par défaut de Rails ajoute intentionnellement cet élément vide sans raison. J'ai dû manquer quelque chose ou oublier de désactiver un paramètre quelconque. Aidez-moi à comprendre ce qui m'échappe (ou indiquez-moi une bonne documentation qui décrit ce problème de manière plus approfondie que ce que j'ai pu trouver sur Internet).

Base de données MySQL Table 'models' :

  • comprend une colonne nommée subset_array qui est un champ TEXTE

Le modèle de classe comprend les paramètres suivants :

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

Le formulaire d'édition des modèles comprend l'option de saisie suivante :

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

PUT au serveur depuis le client ressemble à quelque chose comme ça :

  • en supposant que seules les valeurs 1 et 3 sont sélectionnées.
  • "model" => { "subset_array" => ["", value1, value3] }

La mise à jour de la base de données ressemble à ceci :

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

Comme vous pouvez le voir, il y a un élément supplémentaire, vide, dans le tableau qui est envoyé et défini dans la base de données. Comment puis-je m'en débarrasser ? Est-ce qu'il y a un paramètre que j'ai oublié dans mon fichier f.select appeler ?

Merci beaucoup.)

EDITAR : Il s'agit du code HTML généré à partir du f.select déclaration. Il semble qu'une entrée cachée soit générée, ce qui pourrait être la cause de mon problème. Pourquoi cette entrée est-elle présente ?

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

68voto

Bogdan Gusiev Points 2802

Dans Rails 4 :

Vous serez en mesure de passer :include_hidden option. https://github.com/rails/rails/pull/5414/files

Une solution rapide pour le moment : vous pouvez utiliser dès maintenant dans votre modèle :

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

Cela supprimera simplement toutes les valeurs vides au niveau du modèle.

51voto

Mike A. Points 1877

Le champ caché est à l'origine du problème. Mais il est là pour une bonne raison : lorsque toutes les valeurs sont désélectionnées, vous recevez toujours un paramètre subset_array. Extrait de la documentation Rails (vous devrez peut-être faire défiler la page vers la droite pour voir tout cela) :

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

EDITAR: Le dernier paragraphe suggère que vous ne devriez pas voir le vide dans le cas où quelque chose est sélectionné, mais je pense que c'est faux. La personne qui a fait ce commit pour Rails (voir https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ) essaie de faire la même chose que Rails utilise pour les cases à cocher (comme mentionné ici : https://github.com/rails/rails/pull/1552 ), mais je ne pense pas que cela puisse fonctionner pour une boîte de sélection multiple car les paramètres envoyés forment un tableau dans ce cas et donc aucune valeur n'est ignorée.

Mon sentiment est donc que c'est un bug.

11voto

Max Points 71

Une autre solution rapide consiste à utiliser ce filtre de contrôle :

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

Cette méthode peut être réutilisée entre les contrôleurs sans toucher à la couche du modèle.

5voto

devishot Points 51

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

Je t'ai eu

La spécification HTML indique que les cases à cocher ou les sélections non cochées ne sont pas réussies, et que les navigateurs web ne les envoient donc pas. Malheureusement, cela introduit Malheureusement, cela introduit un problème : si un modèle de facture possède un indicateur de paiement et que, dans le formulaire qui modifie une facture payée, l'utilisateur décoche sa case, aucun paiement ne sera effectué. formulaire qui édite une facture payée, l'utilisateur décoche sa case, aucun paramètre n'est envoyé. Ainsi, tout idiome d'affectation en masse tel que

@invoice.update(params[:invoice]) ne mettrait pas à jour le drapeau.

Pour éviter cela, l'assistant génère un champ caché auxiliaire avant que la case à cocher proprement dite. Le champ caché porte le même nom et ses attributs imitent ceux d'une case à cocher non cochée.

De cette façon, le client envoie soit uniquement le champ caché (représentant que la case à cocher n'est pas cochée), soit les deux champs. Puisque la spécification HTML stipule que les paires clé/valeur doivent être envoyées dans le même ordre ils apparaissent dans le formulaire, et l'extraction des paramètres obtient le dernier champ caché. dernière occurrence de toute clé répétée dans la chaîne de requête, cela fonctionne pour les les formulaires ordinaires.

Pour supprimer les valeurs vides :

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

3voto

Mauro Points 344

Dans le contrôleur :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X