Почему в моем множественном выборе 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

PUT на сервер от клиента выглядит примерно так:

  • предполагая, что выбраны только value1 и value3
  • "model" => { "subset_array" => ["", value1, value3] }

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

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

Как видите, в массиве, который отправляется и устанавливается в базе данных, есть этот дополнительный пустой элемент. Как мне от этого избавиться? Есть ли параметр, который мне не хватает в моем f.selectвызове?

Большое спасибо оценили :)

РЕДАКТИРОВАТЬ : это сгенерированный 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>
Robmclarty
источник
Не могли бы вы опубликовать f.selectгенерируемый фрагмент HTML ? Кроме того, такое поведение происходит даже при создании или только при обновлении?
Майк А.
Добавлено РЕДАКТИРОВАНИЕ выходной 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.

РЕДАКТИРОВАТЬ: последний абзац предполагает, что вы не должны видеть пустой в случае, когда что-то выбрано, но я думаю, что это неправильно. Человек, совершивший эту фиксацию в 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
Обновите мой ответ предстоящей информацией из Rails 4
Богдан Гусиев 06
2
@Donato, вы должны установить для include_hidden значение false (include_hidden: false)
Флориан Видтманн
14

В Rails 4+ установите: include_hidden в 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
Девишот
источник
3

В контроллере:

arr = arr.delete_if { |x| x.empty? }
Мауро
источник
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(); 

Возможность удалить пустые параметры из раскрывающегося списка.

user1875926
источник