Как правильно написать вложенную форму с отношением N-N - PullRequest
4 голосов
/ 02 сентября 2010

Работая над приложением 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 работать лучше?
  • Есть ли какой-нибудь помощник более подходящий?
  • Я ошибаюсь где-то в этом вопросе?
  • Как бы вы достигли этого?

Спасибо

1 Ответ

1 голос
/ 03 февраля 2011

Надеюсь, я вас правильно понял?

Группы предопределены, и вы хотите иметь возможность добавить пользователя в группу. На экране редактирования пользователя вы показываете все или некоторые из предопределенных групп. Вы хотите добавить пользователя, установив флажок и сохранив запись. Если вы снимите флажок, членство этого пользователя в группе без отметки должно быть равно нулю.

Вот как я делаю это с компаниями и проектами:

Class Company
  has_many :datasets
  has_many :projects, :through => :datasets

Class Project
  has_many :datasets
  has_many :companies, :through => :datasets


<% for company in Company.all %>
  <tr>
    <td>
      <%= check_box_tag 'project[company_ids][]', company.id, @project.companies.include?(company) %>
    </td>
      </tr>
<% end %>

Я перечисляю все компании и отмечаю те, которые я хочу включить в проект.

Скажите, пожалуйста, это вам уже помогает? Я полагаю, что вы используете хамл в вашем примере. Я не очень привык к этой записи.

Если вы хотите использовать подмножество, вы можете использовать область действия:

scope :recent, Company.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight)

Тогда вы можете использовать эту область как метод .all:

Company.recent

Помогает ли это?

...