Работая над приложением Rails 3, я столкнулся с проблемой вложенных форм.
Одна коллекция - это набор предопределенных объектов (созданных с помощью db: seed).
В другой коллекции должна быть форма, позволяющая выбрать несколько элементов.
Пример лучше длинного описания, так что вот оно.
Предположим, у вас есть 2 модели: пользовательская и групповая.
Предположим, есть несколько групп: Пользователь, Администраторы, Гости, ...
Вы хотите, чтобы у ваших пользователей было несколько групп, и поэтому вам нужна промежуточная таблица: членство.
Код модели очевиден:
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
accepts_nested_attributes_for :memberships, :allow_destroy => true
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
Контроллер не нужно менять.
Представление, однако, является более сложным.
Я хочу показать список флажков, чтобы выбрать несколько групп из предопределенных.
Я использую здесь специальное поле _destroy
с обратным значением для уничтожения, когда оно фактически не проверено (и, таким образом, добавление пользователя в группу, когда оно проверено)
%p
= f.label :name
%br
= f.text_field :name
%ul
= f.fields_for :memberships, @groups do |g|
%li
- group = g.object
= g.hidden_field :group_id, :value => group.id
= g.check_box :_destroy, {:checked => @user.groups.include?(group)}, 0, 1
= g.label :_destroy, group.name
Однако, это не работает должным образом, потому что форма g
всегда будет создавать ввод с произвольным идентификатором после каждой группы (и даже нарушать макет, включая его после </li>
):
<input id="user_memberships_attributes_0_id" name="user[memberships_attributes][0][id]" type="hidden" value="1" />
<input id="user_memberships_attributes_1_id" name="user[memberships_attributes][1][id]" type="hidden" value="2" />
# ...
Знание синтаксиса вложенных атрибутов следующее:
{:group_id => group.id, :_destroy => 0} # Create
{:group_id => group.id, :_destroy => 0, :id => membership.id} # Update
{:group_id => group.id, :_destroy => 1, :id => membership.id} # Destroy
{:group_id => group.id, :_destroy => 1} # Do nothing
Отправка каждый раз, когда идентификатор не будет работать, потому что он попытается обновить несуществующую запись, вместо того, чтобы создавать ее, и попытаться уничтожить, когда запись не существует.
Текущее решение, которое я нашел, состоит в том, чтобы удалить все идентификаторы, которые в любом случае неверны (это должны быть идентификаторы членства, а не простые индексы), и добавить реальный идентификатор, когда у пользователя уже есть группа.
(это вызывается в контроллере перед созданием и обновлением)
def clean_memberships_attributes
if membership_params = params[:user][:memberships_attributes]
memberships = Membership.find_all_by_user_id params[:id]
membership_params.each_value { |membership_param|
membership_param.delete :id
if m = memberships.find { |m| m[:group_id].to_s == membership_param[:group_id] }
membership_param[:id] = m.id
end
}
end
end
Это кажется неправильным и добавляет много логики в контроллер, просто чтобы контролировать плохое поведение помощника вида fields_for
.
Другим решением является создание всех форм html самостоятельно, попытка имитировать соглашения Rails и избежать проблемы id, но это действительно шумно в коде, и я считаю, что есть лучший способ.
- Это способ заставить
fields_for
работать лучше?
- Есть ли какой-нибудь помощник более подходящий?
- Я ошибаюсь где-то в этом вопросе?
- Как бы вы достигли этого?
Спасибо