¿Por qué el primer elemento siempre está en blanco en mi selección múltiple de Rails, usando una matriz incrustada?

84

Estoy usando Rails 3.2.0.rc2 . Tengo una Model, en la que tengo una estática Arrayque ofrezco a través de un formulario de modo que los usuarios pueden seleccionar un subconjunto Arrayy guardar su selección en la base de datos, almacenada en una sola columna en formato Model. He usado serializar en la columna de la base de datos que almacena Arrayy Rails está convirtiendo correctamente las selecciones de los usuarios en Yaml (y de vuelta a una matriz al leer esa columna). Estoy usando una entrada de formulario de selección múltiple para hacer selecciones.

Mi problema es que, tal como lo tengo actualmente, todo funciona como esperaba, excepto que la matriz de subconjuntos del usuario siempre tiene un primer elemento en blanco cuando se envía al servidor.

Esto no es gran cosa, y podría escribir código para eliminar eso después del hecho, pero siento que solo estoy cometiendo algún tipo de error sintáctico, ya que no me parece que el comportamiento predeterminado de Rails lo haría intencionalmente. agregue este elemento en blanco sin alguna razón. Debo haberme perdido algo o haberme olvidado de desactivar algún tipo de configuración. Ayúdeme a comprender lo que me falta (o indíqueme alguna buena documentación que describa esto con más profundidad de lo que he podido encontrar en los intertubos).

'Modelos' de tablas de base de datos MySQL:

  • incluye una columna denominada subset_arrayque es un campo de TEXTO

El modelo de clase incluye las siguientes configuraciones:

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

El formulario para editar modelos incluye la siguiente opción de entrada:

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

PUT al servidor desde el cliente se parece a esto:

  • asumiendo que solo se seleccionan value1 y value3
  • "model" => { "subset_array" => ["", value1, value3] }

La actualización de la base de datos tiene este aspecto:

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

Como puede ver, hay un elemento en blanco adicional en la matriz que se envía y configura en la base de datos. ¿Cómo me deshago de eso? ¿Hay algún parámetro que me falta en mi f.selectllamada?

Muchas gracias apreciado :)

EDITAR : Este es el código HTML generado a partir de la f.selectdeclaración. ¿Parece que se está generando una entrada oculta que puede ser la causa de mi problema? ¿Por qué está ahí?

<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
fuente
¿Podría publicar el fragmento de HTML que se f.selectestá generando? Además, ¿este comportamiento ocurre incluso en la creación o solo en la actualización?
Mike A.
Se agregó la EDICIÓN del marcado HTML de salida generado a partir def.select
robmclarty
@ mike-a Confirmado el mismo comportamiento tanto para la creación como para la actualización
robmclarty
Me preguntaba si el navegador que estaba usando podría ser parte del problema: cómo interpreta y expresa el significado de la etiqueta de entrada oculta con el mismo nombre que la etiqueta de selección. Así que probé mi aplicación en Chrome, Safari, Firefox y Opera, y cada uno produjo los mismos resultados.
robmclarty
1
Tenga en cuenta que todas las soluciones que se utilizan include_hidden: falsevienen con un problema. Cuando eliminas todos los valores del cuadro de selección, la expresión idiomática model.update(something_params)no incluirá ese campo. TL; DR no podrá dejar el campo vacío.
Damon Aw

Respuestas:

51

El campo oculto es lo que está causando el problema. Pero está ahí por una buena razón: cuando todos los valores están deseleccionados, aún recibe un parámetro subset_array. De los documentos de Rails (es posible que deba desplazarse hacia la derecha para ver todo esto):

  # 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: El último párrafo sugiere que no debería ver el vacío en el caso cuando se selecciona algo, pero creo que está mal. La persona que hizo este compromiso con Rails (consulte https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ) está tratando de hacer el mismo truco que usa Rails para las casillas de verificación (como se menciona aquí: https://github.com / rails / rails / pull / 1552 ), pero no creo que pueda funcionar para un cuadro de selección múltiple porque los parámetros enviados forman una matriz en este caso y, por lo tanto, no se ignora ningún valor.

Entonces mi sensación es que esto es un error.

Mike A.
fuente
1
He creado una aplicación de ejemplo para demostrar el problema mientras trato de averiguar cómo manejarlo correctamente: P
robmclarty
1
Siento que el error no está necesariamente en Rails, sino en una especificación e implementación ambigua para esta funcionalidad de elemento en particular. ¿Cómo debe un usuario-agente expresar un cambio de estado de nuevo vacío al procesador de formularios si se considera que los elementos de formulario vacíos (o no seleccionados) no son controles exitosos y, por lo tanto, no se envían con el contenido del formulario?
robmclarty
Entonces, si esto es un error, ¿está documentado en el rastreador de problemas de Rails?
bmihelac
69

En Rails 4:

Podrás aprobar la :include_hiddenopción. https://github.com/rails/rails/pull/5414/files

Como solución rápida por ahora: puede usar ahora mismo en su modelo:

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

Esto simplemente eliminará todos los valores en blanco a nivel de modelo.

Bogdan Gusiev
fuente
Gracias Bogdan. Creo que este es el tipo de cosas que implementaré en mi aplicación para solucionar el problema. Esto es mucho más fácil de hacer que tratar de solucionar problemas con las implementaciones de especificaciones HTML de los agentes de usuario o algo así.
robmclarty
Lleve sus preocupaciones a los miembros del equipo central de Rails. Están autorizados a revisar y aceptar parches y asumir la responsabilidad de los problemas consiguientes.
Bogdan Gusiev
Bogdan, ¿hay alguna forma de desactivar el campo oculto, si múltiple es verdadero? Esto rompe bastantes cosas en mi aplicación cuando actualizo a 3.2. Realmente no me gusta el hecho de que tengo que limpiar cosas en el controlador debido a que la magia de Rails agrega valores vacíos adicionales.
taelor
Actualizar mi respuesta con información de Rails 4
Bogdan Gusiev
2
@Donato debe establecer include_hidden en falso (include_hidden: false)
Florian Widtmann
14

En Rails 4+ set: include_hidden en select_tag a falso

<%= form.grouped_collection_select :employee_id, Company.all, :employees, :name, :id, :name, { include_hidden: false }, { size: 6, multiple: true } %>
Martín
fuente
¡Esta es, con mucho, la respuesta más simple! ¡Gracias!
William Hampshire
11

Otra solución rápida es usar este filtro de controlador:

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

De esta forma, se puede reutilizar en los controladores sin tocar la capa del modelo.

Max
fuente
Gracias. Estoy agregando esto a mi bolsa de trucos en caso de que no quiera hacerlo en el modelo;)
robmclarty
5

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

Te tengo

La especificación HTML dice que las casillas de verificación no marcadas o las selecciones no son exitosas y, por lo tanto, los navegadores web no las envían. Desafortunadamente, esto introduce un problema: si un modelo de factura tiene una marca de pago, y en el formulario que edita una factura de pago, el usuario desmarca su casilla de verificación, no se envía ningún parámetro de pago. Entonces, cualquier modismo de asignación masiva como

@ invoice.update (params [: factura]) no actualizaría la bandera.

Para evitar esto, el ayudante genera un campo oculto auxiliar antes de la casilla de verificación. El campo oculto tiene el mismo nombre y sus atributos imitan una casilla de verificación sin marcar.

De esta manera, el cliente envía solo el campo oculto (que representa que la casilla de verificación está desmarcada) o ambos campos. Dado que la especificación HTML dice que los pares clave / valor deben enviarse en el mismo orden en que aparecen en el formulario, y la extracción de parámetros obtiene la última aparición de cualquier clave repetida en la cadena de consulta, que funciona para formularios normales.

Para eliminar valores en blanco:

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

En el controlador:

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

Lo arreglé usando el params[:review][:staff_ids].delete("") en el controlador antes de la actualización.

En mi vista:

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

En mi controlador:

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

Lo hago funcionar escribiendo esto en la parte de Javascript de la página:

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

El mío se parece más a lo siguiente:

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

No estoy seguro de si esto me va a dar dolor de cabeza en el futuro, pero ahora funciona bien.

imaginabit
fuente
-3

Utilice jQuery:

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

Opción para eliminar las opciones en blanco del menú desplegable.

usuario1875926
fuente