Dlaczego pierwszy element jest zawsze pusty w moich wielokrotnych wyborach Rails, używając wbudowanej tablicy?

84

Używam Rails 3.2.0.rc2 . Mam plik Model, w którym mam statyczny, Arrayktóry oferuję za pośrednictwem formularza, tak aby użytkownicy mogli wybrać podzbiór Arrayi zapisać swój wybór w bazie danych, przechowywanej w jednej kolumnie w formacie Model. Użyłem serializacji w kolumnie bazy danych, która przechowuje, Arraya Railsy poprawnie konwertują wybory użytkowników na Yaml (iz powrotem na tablicę podczas czytania tej kolumny). Do zaznaczania używam pola wyboru wielokrotnego.

Mój problem polega na tym, że tak jak obecnie, wszystko działa tak, jak bym się spodziewał, z wyjątkiem tego, że tablica podzbiorów użytkownika zawsze ma pusty pierwszy element, gdy jest wysyłana na serwer.

To nie jest wielka sprawa i mógłbym napisać kod, który to wyciął po fakcie, ale czuję, że robię jakiś błąd składniowy, ponieważ nie wydaje mi się, że domyślne zachowanie Railsów celowo dodaj ten pusty element bez powodu. Musiałem coś przeoczyć lub zapomnieć o wyłączeniu jakiegoś ustawienia. Proszę, pomóż mi zrozumieć, czego mi brakuje (lub wskaż mi dobrą dokumentację, która opisuje to z większą głębią niż to, co udało mi się znaleźć na intertubach).

Tabela bazy danych MySQL „modele”:

  • zawiera kolumnę o nazwie, subset_arrayktóra jest polem TEKST

Model klasy zawiera następujące ustawienia:

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

Formularz do edycji modeli zawiera następującą opcję wprowadzania danych:

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

PUT na serwer z klienta wygląda mniej więcej tak:

  • zakładając, że wybrane są tylko value1 i value3
  • "model" => { "subset_array" => ["", value1, value3] }

Aktualizacja bazy danych wygląda następująco:

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

Jak widać, ten dodatkowy, pusty element tablicy jest wysyłany i ustawiany w bazie danych. Jak się tego pozbyć? Czy brakuje mi parametru w moim f.selectwywołaniu?

Wielkie dzięki :)

EDYCJA : To jest kod HTML wygenerowany z f.selectinstrukcji. Wygląda na to, że generowane są ukryte dane wejściowe, które mogą być przyczyną mojego problemu? Dlaczego tak jest?

<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
źródło
Czy możesz opublikować f.selectgenerowany fragment kodu HTML ? Czy to zachowanie występuje nawet podczas tworzenia, czy tylko podczas aktualizacji?
Mike A.
Dodano f.select
EDYCJĘ
@ mike-a Potwierdzone to samo zachowanie zarówno dla tworzenia, jak i aktualizacji
robmclarty
Zastanawiałem się, czy przeglądarka, której używałem, może być częścią problemu: jak interpretuje i wyraża znaczenie ukrytego tagu wejściowego o tej samej nazwie co tag select. Wypróbowałem więc moją aplikację w przeglądarce Chrome, Safari, Firefox i Opera i każda z nich dała te same wyniki.
robmclarty,
1
Zwróć uwagę, że wszystkie używane rozwiązania mają problem include_hidden: false. Po usunięciu wszystkich wartości z pola wyboru, idiomatyczny model.update(something_params)nie uwzględni tego pola. TL; DR nie będziesz w stanie uczynić tego pola pustym.
Damon Aw,

Odpowiedzi:

51

Ukryte pole jest przyczyną problemu. Ale jest z dobrego powodu: gdy wszystkie wartości są odznaczone, nadal otrzymujesz parametr subset_array. Z dokumentów Rails (być może będziesz musiał przewinąć w prawo, aby zobaczyć to wszystko):

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

EDYCJA: Ostatni akapit sugeruje, że nie powinieneś widzieć pustego w przypadku, gdy coś jest zaznaczone, ale myślę, że jest źle. Osoba, która zobowiązała się do tego w Railsach (patrz https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ), próbuje zrobić tę samą sztuczkę, której Rails używa dla pól wyboru (jak wspomniano tutaj: https://github.com / rails / rails / pull / 1552 ), ale nie sądzę, że może to działać dla pola wielokrotnego wyboru, ponieważ parametry przesyłane w tym przypadku tworzą tablicę, a więc żadna wartość nie jest ignorowana.

Więc mam wrażenie, że to błąd.

Mike A.
źródło
1
Stworzyłem przykładową aplikację, aby zademonstrować problem, próbując wymyślić, jak go właściwie rozwiązać: P
robmclarty,
1
Wydaje mi się, że błąd niekoniecznie występuje w Railsach, ale w niejednoznacznej specyfikacji i implementacji tej konkretnej funkcjonalności elementu. Jak należy agenta użytkownika wyrażania zmiany stanu nowo puste procesora formy, jeżeli puste (lub nieselektywnej) elementów formy są uważane nie być skuteczne kontrole i w ten sposób nie przedstawiony zawartością formie?
robmclarty
Więc jeśli to jest błąd, czy jest to udokumentowane w module śledzenia błędów Railsów?
bmihelac
69

W Rails 4:

Będziesz mógł przejść :include_hiddenopcję. https://github.com/rails/rails/pull/5414/files

Na razie szybkie rozwiązanie: możesz teraz użyć w swoim modelu:

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

Spowoduje to po prostu usunięcie wszystkich pustych wartości na poziomie modelu.

Bogdan Gusiev
źródło
Dzięki Bogdan. Myślę, że jest to coś, co zaimplementuję w mojej aplikacji, aby obejść ten problem. Jest to o wiele łatwiejsze do zrobienia niż próba obejścia problemów z implementacją specyfikacji HTML klienta użytkownika czy coś w tym rodzaju.
robmclarty
Przedstaw swoje obawy członkom podstawowego zespołu Rails. Są upoważnieni do przeglądania i akceptowania poprawek oraz przyjmowania odpowiedzialności za wynikające z nich problemy.
Bogdan Gusiev
Bogdanie, czy w ogóle można wyłączyć ukryte pole, jeśli jest wielokrotność? To psuje sporo rzeczy w mojej aplikacji po uaktualnieniu do wersji 3.2. Naprawdę nie podoba mi się fakt, że muszę porządkować rzeczy w kontrolerze, ponieważ magia Railsów dodaje dodatkowe puste wartości.
taelor
Zaktualizuj moją odpowiedź nadchodzącymi informacjami z Rails 4
Bogdan Gusiev
2
@Donato musisz ustawić parametr include_hidden na false (include_hidden: false)
Florian Widtmann,
14

W Railsach 4+ ustaw: include_hidden na select_tag na false

<%= form.grouped_collection_select :employee_id, Company.all, :employees, :name, :id, :name, { include_hidden: false }, { size: 6, multiple: true } %>
Jaskółka oknówka
źródło
To zdecydowanie najprostsza odpowiedź! Dzięki!
William Hampshire
11

Innym szybkim rozwiązaniem jest użycie tego filtra kontrolera:

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

W ten sposób można go ponownie wykorzystać w kontrolerach bez dotykania warstwy modelu.

Maks
źródło
dzięki.
Dodam
5

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

Mam cię

Specyfikacja HTML mówi, że niezaznaczone pola wyboru lub wybory nie powiodły się, a zatem przeglądarki internetowe ich nie wysyłają. Niestety wprowadza to problem: jeśli model faktury ma flagę opłaconą, aw formularzu, który edytuje zapłaconą fakturę, użytkownik odznacza swoje pole wyboru, żaden opłacony parametr nie jest wysyłany. Tak więc każdy idiom przypisania masowego jak

@ Faktura.update (params [: faktura]) nie zaktualizowałoby flagi.

Aby temu zapobiec, pomocnik generuje pomocnicze ukryte pole przed samym polem wyboru. Ukryte pole ma taką samą nazwę, a jego atrybuty naśladują niezaznaczone pole wyboru.

W ten sposób klient wysyła tylko ukryte pole (reprezentujące pole wyboru jest niezaznaczone) lub oba pola. Ponieważ specyfikacja HTML mówi, że pary klucz / wartość muszą być wysyłane w tej samej kolejności, w jakiej pojawiają się w formularzu, a wyodrębnianie parametrów pobiera ostatnie wystąpienie dowolnego powtórzonego klucza w ciągu zapytania, co działa w przypadku zwykłych formularzy.

Aby usunąć puste wartości:

  def myfield=(value)
    value.reject!(&:blank?)
    write_attribute(:myfield, value)
  end
devishot
źródło
3

W kontrolerze:

arr = arr.delete_if { |x| x.empty? }
Mauro
źródło
0

Naprawiłem to używając params[:review][:staff_ids].delete("")w kontrolerze przed aktualizacją.

Według mnie:

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

W moim kontrolerze:

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

Uczyniłem to, pisząc w części Javascript strony:

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

Mój wygląda bardziej następująco:

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

Nie jestem pewien, czy to przyniesie mi ból głowy w przyszłości, ale teraz działa dobrze.

imaginabit
źródło
-3

Użyj jQuery:

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

Możliwość usunięcia pustych opcji z listy rozwijanej.

user1875926
źródło