web-dev-qa-db-fra.com

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

J'utilise Rails 3.2.0.rc2 . J'ai un Model, dans lequel j'ai un Array statique que je propose via un formulaire permettant aux utilisateurs de sélectionner un sous-ensemble de Array et d'enregistrer leur sélection dans la base de données, stockée dans une seule colonne dans Model. J'ai utilisé sérialiser sur la colonne de base de données qui stocke les Array et Rails convertit correctement les sélections des utilisateurs en Yaml (et de nouveau dans un tableau lors de la lecture de cette colonne). J'utilise une entrée de formulaire à sélection multiple pour effectuer des sélections.

Mon problème est que, comme je l'ai actuellement, tout fonctionne comme je m'y attendais, sauf que le tableau de sous-ensemble de l'utilisateur a toujours un premier élément vide lorsqu'il est envoyé au serveur.

Ce n'est pas un gros problème, et je pourrais écrire du code pour le supprimer après coup, mais j'ai l'impression que je fais juste une sorte d'erreur syntaxique car il ne me semble pas que la valeur par défaut Rails ajouterait intentionnellement cet élément vide sans raison. J'ai dû manquer quelque chose ou oublier de désactiver une sorte de paramètre. Aidez-moi à comprendre ce qui me manque (ou pointez-moi vers une bonne documentation qui décrit cela avec plus de profondeur que ce que j'ai pu trouver sur les intertubes).

Modèles de table de base de données MySQL:

  • inclut une colonne nommée subset_array qui est un champ TEXT

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 d'entrée suivante:

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

PUT to server from client ressemble à ceci:

  • en supposant que seuls value1 et value3 sont sélectionnés
  • "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 cet élément supplémentaire, vide, dans le tableau envoyé et défini dans la base de données. Comment puis-je m'en débarrasser? Y a-t-il un paramètre qui me manque dans mon f.select appel?

Merci beaucoup :)

MODIFIER : 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 peut être la cause de mon problème? Pourquoi est-ce là?

<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

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. À partir des documents Rails (vous devrez peut-être faire défiler 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.

EDIT: Le dernier paragraphe suggère que vous ne devriez pas voir celui vide dans le cas où quelque chose est sélectionné, mais je pense que c'est faux. La personne qui a fait ce commit à Rails (voir https://github.com/Rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ) essaie de faire la même astuce 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 travailler pour une zone de sélection multiple car les paramètres envoyés sur forment un tableau dans ce cas et donc aucune valeur n'est ignorée.

Donc, je pense que c'est un bug.

51
Mike A.

Dans Rails 4:

Vous pourrez passer :include_hidden option. https://github.com/Rails/rails/pull/5414/files

Comme solution rapide pour l'instant: 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.

67
Bogdan Gusiev

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

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 sur plusieurs contrôleurs sans toucher la couche modèle.

11
Max

Dans Rails 4+ set: include_hidden sur select_tag à 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

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

@ facture.update (paramètres [: facture]) ne mettrait pas à jour l'indicateur.

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

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

Pour supprimer des valeurs vides:

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

Dans le contrôleur:

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

Je l'ai corrigé en utilisant la params[:review][:staff_ids].delete("") dans le contrôleur avant la mise à jour.

À mon avis:

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

Dans mon contrôleur:

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

Je le fais fonctionner en écrivant ceci dans la partie Javascript de la page:

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

Le mien ressemble plus à ce qui suit:

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

Je ne sais pas si cela va me causer des maux de tête à l'avenir, mais maintenant cela fonctionne bien.

0
imaginabit