text_field rails form_with form_tag form_for form example collection html ruby-on-rails forms serialization multi-select

html - form_with - rails select class



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

Estoy usando Rails 3.2.0.rc2 . Tengo un Model , en el que tengo una Array estática que ofrezco a través de un formulario para que los usuarios puedan seleccionar un subconjunto de Array y guardar su selección en la base de datos, almacenada en una sola columna en el Model . He usado serializar en la columna de la base de datos que almacena el Array and Rails está convirtiendo correctamente las selecciones de los usuarios en Yaml (y de regreso a un arreglo cuando lee 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 un código para cortar eso después del hecho, pero siento que estoy cometiendo algún tipo de error sintáctico, ya que no me parece que el comportamiento predeterminado de Rails fuera intencionalmente agregue este elemento en blanco sin algún motivo. Debo haber perdido algo u olvidado deshabilitar algún tipo de ajuste. Por favor, ayúdame a entender lo que me falta (o apúntame a una buena documentación que describe esto con más profundidad de la que he podido encontrar en los intertubos).

Tabla de bases de datos MySQL ''modelos'':

  • incluye una columna llamada subset_array que es un campo TEXTO

El modelo de clase incluye la siguiente configuración:

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

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 del cliente se ve así:

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

La actualización de la base de datos se ve así:

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

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

Muchas gracias apreciado :)

EDITAR : Este es el código HTML generado de la declaración f.select . 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>


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. Desde los documentos de Rails (puede que tenga que 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 estar viendo el vacío en el caso cuando se selecciona algo, pero creo que es incorrecto. La persona que hizo este compromiso con Rails (consulte https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ) está intentando hacer el mismo truco que utiliza 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.


En Rails 4:

Podrás pasar :include_hidden opción :include_hidden . https://github.com/rails/rails/pull/5414/files

Como una 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 eliminará todos los valores en blanco a nivel de modelo.


En el conjunto Rails 4+: include_hidden on select_tag to false

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


En el controlador:

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


Lo arreglé usando los 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


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 si esto me va a causar dolor de cabeza en el futuro, pero ahora funciona bien.


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.


Use jQuery:

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

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


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

Gotcha

La especificación HTML dice que las casillas de verificación o selecciones no verificadas no son exitosas, y por lo tanto, los navegadores web no las envían. Lamentablemente, esto introduce un resultado negativo: si un modelo de factura tiene un indicador de pago, y en la forma en que se edita una factura pagada, el usuario desmarca su casilla de verificación, no se envía ningún parámetro pagado. Entonces, cualquier modismo de asignación de masa como

@ invoice.update (params [: invoice]) no actualizaría el indicador.

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 forma, 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 de 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, eso funciona para formularios ordinarios.

Para eliminar valores en blanco:

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