Чому перший елемент завжди є порожнім у моєму Rails мульти-виділення, використовуючи вбудований масив?


84

Я використовую Rails 3.2.0.rc2 . У мене є файл Model, в якому я маю статику, Arrayяку пропоную через форму, таку, що користувачі можуть вибрати підмножину Arrayта зберегти свій вибір у базі даних, що зберігається в одному стовпці в Model. Я використовував серіалізацію в стовпці бази даних, який зберігає, Arrayа Rails правильно перетворює виділення користувачів у Yaml (і повертається до масиву при читанні цього стовпця). Я використовую введення форми з декількома виборами, щоб робити вибір.

Моя проблема полягає в тому, що, як у мене зараз, все працює так, як я очікував, за винятком того, що масив підмножини користувача завжди має порожній перший елемент, коли він надсилається на сервер.

Це не є великою проблемою, і я міг би написати код, щоб це вирішити, але я відчуваю, що я просто роблю якусь синтаксичну помилку, оскільки мені не здається, що поведінка Rails за замовчуванням навмисно додати цей порожній елемент без будь-якої причини. Напевно, я щось пропустив або забув відключити якусь установку. Будь ласка, допоможіть мені зрозуміти, чого мені не вистачає (або вкажіть мені якусь хорошу документацію, яка описує це з більшою глибиною, ніж те, що мені вдалося знайти на міжтрубках).

"Моделі" таблиці бази даних MySQL:

  • включає стовпець, названий subset_arrayтекстовим полем

Модель класу включає такі налаштування:

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

Форма для редагування моделей включає такий варіант введення:

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

ПУТ на сервер з клієнта виглядає приблизно так:

  • припускаючи, що вибрано лише значення1 та значення3
  • "model" => { "subset_array" => ["", value1, value3] }

Оновлення бази даних виглядає так:

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

Як бачите, у базі даних надсилається та встановлюється цей додатковий порожній елемент у масиві. Як я можу від цього позбутися? Чи є параметр, якого я втрачаю у своєму f.selectдзвінку?

Велике спасибі оцінено :)

EDIT : Це сформований HTML-код із f.selectвиписки. Схоже, створюється прихований вхід, який може бути причиною моєї проблеми? Чому це там?

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

Не могли б ви розмістити фрагмент HTML, який f.selectгенерує? Крім того, така поведінка трапляється навіть під час створення, чи це просто оновлення?
— Mike A.

Додано EDIT вихідної розмітки HTML, створеної зf.select
— robmclarty

@ mike-a Підтверджена однакова поведінка як для створення, так і для оновлення
— robmclarty

Я подумав, чи може браузер, яким я користувався, бути частиною проблеми: як він інтерпретує та виражає значення прихованого вхідного тегу з тим самим іменем, що і тег select. Тож я спробував свій додаток у Chrome, Safari, Firefox та Opera, і кожен з них дав однакові результати.
— robmclarty

1
Зверніть увагу, що всі рішення, які використовуються, include_hidden: falseпостачаються із затримкою. Коли ви видалите всі значення з поля вибору, ідіоматичне model.update(something_params)поле не включатиме цього поля. TL; DR ви не зможете заповнити поле порожнім.
— Деймон Ав

Відповіді:


51

Приховане поле є причиною проблеми. Але це є з поважної причини: коли всі значення скасовано, ви все одно отримуєте параметр subset_array. З документації Rails (можливо, вам доведеться прокрутити праворуч, щоб побачити все це):

  # 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: Останній абзац передбачає, що ви не повинні бачити порожній у випадку, коли щось вибрано, але я думаю, що це неправильно. Людина, яка зробила це зобов'язання для Rails (див. Https://github.com/rails/rails/commit/faba406fa15251cdc9588364d23c687a14ed6885 ), намагається виконати той самий фокус, який Rails використовує для прапорців (як згадано тут: https://github.com / rails / rails / pull / 1552 ), але я не думаю, що це може працювати для багаторазового поля вибору, оскільки параметри, що надсилаються, утворюють масив у цьому випадку, тому жодне значення не ігнорується.

Тож я відчуваю, що це помилка.


1
Я створив приклад програми, щоб продемонструвати проблему, намагаючись зрозуміти, як правильно з нею впоратися: P
— robmclarty

1
Я відчуваю, що помилка не обов’язково в Rails, а в неоднозначній специфікації та реалізації для даного функціоналу елемента. Як агент користувача повинен висловити зміна стану знову спорожнити у вигляді процесор , якщо порожні (або не вибрані елементи форми), вважаються НЕ бути успішним управлінням і , таким чином , НЕ представляються з вмістом форми?
— robmclarty

Отже, якщо це помилка, чи це задокументовано у трекері випуску Rails?
— bmihelac

69

У Rails 4:

Ви зможете пройти :include_hiddenваріант. https://github.com/rails/rails/pull/5414/files

Як швидке виправлення на даний момент: ви можете використовувати прямо зараз у своїй моделі:

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

Це просто видалить усі порожні значення на рівні моделі.


Дякую Богдане. Я думаю, що це те, що я буду впроваджувати у своєму додатку, щоб вирішити проблему. Зробити це набагато простіше, ніж намагатися вирішити проблеми з реалізацією специфікацій HTML користувача-агентів або щось інше.
— robmclarty

Доведіть свою стурбованість до основних членів команди. Вони уповноважені переглядати та приймати виправлення та нести відповідальність за наступні проблеми.
— Богдан Гусєв

Богдане, чи можна взагалі вимкнути приховане поле, якщо кратна істина? Це розбиває досить багато речей у моєму додатку під час оновлення до 3.2. Мені дуже не подобається той факт, що мені доводиться чистити речі в контролері через магію Rails, додаючи зайві порожні значення.
— taelor 05.03.13

Оновіть мою відповідь актуальною інформацією від Rails 4
— Богдан Гусєв

2
@Donato ви повинні встановити include_hidden як false (include_hidden: false)
— Флоріан Відттман

14

У Rails 4+ встановіть: include_hidden on select_tag до false

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

Це, безумовно, найпростіша відповідь! Дякую!
— Вільям Хемпшир

11

Ще одним швидким виправленням є використання цього фільтра контролера:

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

Цей спосіб можна використовувати повторно на контролерах, не торкаючись шару моделі.


Дякую. Я додаю це в мій трюк на випадок, якщо я не хочу робити це в моделі;)
— robmclarty

5

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

Маю

Специфікація HTML говорить, що невстановлені прапорці або позначки не є успішними, і, отже, веб-браузери не надсилають їх. На жаль, це вводить логіку: якщо модель рахунка-фактури має оплачений прапорець і у формі, що редагує платний рахунок-фактуру, користувач знімає прапорець, жоден платний параметр не надсилається. Отже, будь-яка ідіома масового призначення

@ invoice.update (params [: invoice]) не оновлює прапор.

Щоб запобігти цьому, помічник генерує допоміжне приховане поле перед самим прапорцем. Приховане поле має однакову назву, а його атрибути імітують не встановлений прапорець.

Таким чином, клієнт надсилає лише приховане поле (позначення прапорця не встановлено), або обидва поля. Оскільки специфікація HTML говорить, що пари ключ / значення повинні надсилатися в тому ж порядку, в якому вони з'являються у формі, а вилучення параметрів отримує останнє входження будь-якого повторного ключа в рядку запиту, який працює для звичайних форм.

Щоб видалити порожні значення:

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


0

Я виправив це за допомогою params[:review][:staff_ids].delete("")в контролері перед оновленням.

На мій погляд:

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

У моєму контролері:

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

0

Я змушую це працювати, написавши це в частині сторінки Javascript:

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

Шахта схожа на наступне:

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

Не впевнений, що це призведе до головного болю в майбутньому, але зараз це працює нормально.


-3

Використовуйте jQuery:

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

Можливість видалити порожні параметри зі спадного меню.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.