Perché il primo elemento è sempre vuoto nella mia selezione multipla di Rails, utilizzando un array incorporato?


84

Sto usando Rails 3.2.0.rc2 . Ho un Model, in cui ho una statica Arrayche sto offrendo attraverso un modulo in modo tale che gli utenti possano selezionare un sottoinsieme Arraye salvare la loro selezione nel database, memorizzato in una singola colonna in Model. Ho usato serializzare sulla colonna del database che memorizza Arraye Rails sta convertendo correttamente le selezioni degli utenti in Yaml (e di nuovo in un array durante la lettura di quella colonna). Sto utilizzando un modulo di selezione multipla per effettuare le selezioni.

Il mio problema è che, nel modo in cui lo ho attualmente, tutto funziona come mi aspetterei tranne che l'array del sottoinsieme dell'utente ha sempre un primo elemento vuoto quando viene inviato al server.

Questo non è un grosso problema e potrei scrivere codice per tagliarlo dopo il fatto, ma mi sento come se stessi commettendo una sorta di errore sintattico poiché non mi sembra che il comportamento predefinito di Rails sarebbe intenzionalmente aggiungi questo elemento vuoto senza motivo. Devo essermi perso qualcosa o ho dimenticato di disabilitare qualche tipo di impostazione. Per favore aiutami a capire cosa mi manca (o indicami una buona documentazione che lo descriva con maggiore profondità di quanto sono riuscito a trovare sugli intertubes).

Tabella di database MySQL 'modelli':

  • include una colonna denominata subset_arrayche è un campo TEXT

Il modello di classe include le seguenti impostazioni:

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

Il modulo per la modifica dei modelli include la seguente opzione di input:

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

PUT to server from client assomiglia a questo:

  • supponendo che siano selezionati solo valore1 e valore3
  • "model" => { "subset_array" => ["", value1, value3] }

L'aggiornamento del database ha questo aspetto:

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

Come puoi vedere, c'è questo elemento extra, vuoto, nell'array che viene inviato e impostato nel database. Come me ne sbarazzo? C'è un parametro che mi manca nella mia f.selectchiamata?

Molte grazie apprezzate :)

EDIT : questo è il codice HTML generato dall'istruzione f.select. Sembra che venga generato un input nascosto che potrebbe essere la causa del mio problema? Perché è 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>

Potresti pubblicare lo snippet HTML che f.selectsta generando? Inoltre, questo comportamento si verifica anche durante la creazione o è solo in fase di aggiornamento?
Mike A.

Aggiunta la MODIFICA del markup HTML di output generato daf.select
robmclarty

@ mike-a Confermato lo stesso comportamento sia per la creazione che per l'aggiornamento
robmclarty

Mi chiedevo se il browser che stavo usando potesse essere parte del problema: come interpreta ed esprime il significato del tag di input nascosto con lo stesso nome del tag select. Quindi ho provato la mia app in Chrome, Safari, Firefox e Opera e ognuno ha prodotto gli stessi risultati.
robmclarty

1
Nota, tutte le soluzioni che utilizzano include_hidden: falsevengono fornite con un trucco. Quando rimuovi tutti i valori dalla casella di selezione, l'idiomatico model.update(something_params)non includerà quel campo. TL; DR non potrai rendere il campo vuoto.
Damon Aw

Risposte:


51

Il campo nascosto è la causa del problema. Ma c'è per una buona ragione: quando tutti i valori sono deselezionati, ricevi ancora un parametro subset_array. Dai documenti di Rails (potresti dover scorrere verso destra per vedere tutto questo):

  # 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: L'ultimo paragrafo suggerisce che non dovresti vedere quello vuoto nel caso in cui qualcosa sia selezionato, ma penso che sia sbagliato. La persona che ha effettuato questo commit su Rails (vedere https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ) sta cercando di fare lo stesso trucco che Rails usa per le caselle di controllo (come menzionato qui: https://github.com / rails / rails / pull / 1552 ), ma non penso che possa funzionare per una casella di selezione multipla perché i parametri inviati formano un array in questo caso e quindi nessun valore viene ignorato.

Quindi la mia sensazione è che questo sia un bug.


1
Ho creato un'app di esempio per dimostrare il problema mentre cerco di capire come gestirlo correttamente: P
robmclarty

1
Mi sembra che il bug non sia necessariamente in Rails, ma in una specifica e implementazione ambigua per questa particolare funzionalità dell'elemento. Come dovrebbe un agente utente esprimere un cambiamento di stato di recente svuotare al processore modulo se vuote (o non selezionate) elementi del modulo sono considerati non essere controlli successo e pertanto non hanno presentato con il contenuto del modulo?
robmclarty

Quindi, se si tratta di un bug, è documentato in Rails issue tracker?
bmihelac

69

In Rails 4:

Sarai in grado di passare l' :include_hiddenopzione. https://github.com/rails/rails/pull/5414/files

Come soluzione rapida per ora: puoi usare subito nel tuo modello:

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

Questo cancellerà semplicemente tutti i valori vuoti a livello di modello.


Grazie Bogdan. Penso che questo sia il tipo di cosa che implementerò nella mia app per aggirare il problema. Questo è molto più facile da fare che cercare di aggirare i problemi con le implementazioni delle specifiche HTML degli user-agent o qualcosa del genere.
robmclarty

Porta le tue preoccupazioni ai membri del team principale. Sono autorizzati a rivedere e accettare le patch e ad assumersi le responsabilità per problemi conseguenti.
Bogdan Gusiev

Bogdan, c'è comunque da disattivare il campo nascosto, se il multiplo è vero? Questo interrompe un bel po 'di cose nella mia app durante l'aggiornamento a 3.2. Non mi piace davvero il fatto di dover pulire le cose nel controller a causa della magia di Rails che aggiunge valori vuoti extra.
taelor

Aggiorna la mia risposta con le informazioni in arrivo da Rails 4
Bogdan Gusiev

2
@Donato devi impostare include_hidden su false (include_hidden: false)
Florian Widtmann

14

In Rails 4+ impostare: include_hidden on select_tag su false

<%= form.grouped_collection_select :employee_id, Company.all, :employees, :name, :id, :name, { include_hidden: false }, { size: 6, multiple: true } %>

Questa è di gran lunga la risposta più semplice! Grazie!
William Hampshire

11

Un'altra soluzione rapida è utilizzare questo filtro del controller:

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

In questo modo può essere riutilizzato tra i controller senza toccare il livello del modello.


Grazie. Lo sto aggiungendo alla mia borsa dei trucchi nel caso in cui non voglio farlo nel modello;)
robmclarty

5

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

Gotcha

La specifica HTML dice che le caselle di controllo o le selezioni deselezionate non hanno esito positivo e quindi i browser Web non le inviano. Sfortunatamente questo introduce un trucco: se un modello di fattura ha un flag di pagamento e nel modulo che modifica una fattura pagata l'utente deseleziona la sua casella di controllo, non viene inviato alcun parametro a pagamento. Quindi, qualsiasi idioma di assegnazione di massa come

@ invoice.update (params [: invoice]) non aggiorna il flag.

Per evitare ciò, l'helper genera un campo nascosto ausiliario prima della casella di controllo. Il campo nascosto ha lo stesso nome e i suoi attributi imitano una casella di controllo non selezionata.

In questo modo, il client invia solo il campo nascosto (che rappresenta la casella di controllo è deselezionato) o entrambi i campi. Poiché la specifica HTML dice che le coppie chiave / valore devono essere inviate nello stesso ordine in cui appaiono nel modulo, e l'estrazione dei parametri ottiene l'ultima occorrenza di qualsiasi chiave ripetuta nella stringa di query, il che funziona per i moduli ordinari.

Per rimuovere i valori vuoti:

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

3

Nel controller:

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

0

L'ho risolto utilizzando params[:review][:staff_ids].delete("")il controller prima dell'aggiornamento.

Secondo me:

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

Nel mio controller:

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

0

Lo faccio funzionare scrivendo questo nella parte Javascript della pagina:

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

Il mio assomiglia più a quanto segue:

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

Non sono sicuro se questo mi farà venire il mal di testa in futuro, ma ora funziona bene.


-3

Usa jQuery:

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

Opzione per rimuovere le opzioni vuote dal menu a discesa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.