Что было бы хорошим подходом к неограниченному количеству вложенных форм добавления/редактирования в приложении/скаффолде rails?

Обзор/Модели

Скажем, например, у вас есть довольно большая система с довольно общими моделями (в этом примере мы будем использовать три)

  • Местоположение (общие поля адреса, тип, строка 1, строка 2, город и т. д.)
  • Компании (общие поля компании, тип, название и т. д.)
  • Контакты (общие поля контактов, тип, имя, должность и т. д.)

Пример (+ скриншот rails_admin)

Я обнаружил, что при создании подобных систем я всегда сталкиваюсь с одной и той же проблемой. Например, я нажимаю кнопку «Добавить» в компании (так что теперь я частично загрузил форму и т. Д.), Я частично добавляю компанию и обнаруживаю, что нужного мне местоположения нет в системе. Я хочу быстро открыть модальное окно, добавить местоположение, а затем обновить выбор через jquery или что-то в этом роде. Отлично, ничего сложного, и это было сделано в таких системах, как rails_admin (см. скриншот ниже):

http://www.server1-breakfrom.com/nestedaddexample.jpg

Это все хорошо, когда имеешь дело с одним уровнем вложенности, и, вообще говоря, все хорошо, когда имеешь дело с этим как с редкостью (поскольку можно запрограммировать способность как и когда). Тем не менее, я делаю систему, где это почти должно быть фреймворком само по себе, поскольку мне это нужно более чем в 50% моделей. Мне нужна возможность динамически добавлять различные параметры в модель/контроллер, а формы динамически генерировать соответствующие кнопки.

Дополнительные вопросы

  1. Модальные окна в модальных окнах или поверх них. При добавлении контакта вы нажимаете, чтобы добавить его компанию, в этой новой форме добавления компаний вы хотите добавить местоположение: бум, модальное поверх модального.
  2. Какой текст в jquery добавить к выбору. Вам нужно знать, как обновить элемент выбора и, возможно, найти все соответствующие элементы выбора на экране. При добавлении компании будет использоваться ее идентификатор и, возможно, имя, но для местоположения потребуется использовать идентификатор вместе с, возможно, строкой 1, строкой 2 и городом для текста.
  3. Проверка в модальном окне (но я предполагаю, что мы можем использовать какой-то jquery, так как мы уже очень зависим от него)

Расширение вопроса

Итак, в дополнение к вопросу: не усложняю ли я ситуацию и легко ли решить мою проблему (я должен подчеркнуть, что 3 модели являются чисто примерами, у меня есть много моделей, которые должны ссылаться, например, на компанию, это не просто контакт, так что «просто запрограммируй, как тебе нужно» не получится!).

Или я должен просто разобрать rails_admin и вытащить нужный мне бит? (Опять же, они не решили проблему многократного вложения, поэтому я чувствую, что лучше начать с нуля?).


person bensmithbwd    schedule 24.02.2013    source источник


Ответы (2)


Я столкнулся с похожей ситуацией. Мое решение может иметь проблемы с производительностью в вашем сценарии, но не для меня, но это система с низким уровнем одновременного использования, всего 300 пользователей.

Что я сделал, так это использовал одно представление/форму MEGA AJAX! и используйте JavaScript для отображения/скрытия по мере необходимости. Этот метод требует, чтобы вы НЕ использовали помощники тегов формы (т. е. text_field_tag ​​вместо f.text_field), и ВЫ должны взять под контроль имена элементов.

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

<%= form_for @mega, :remote=>true do %>
  <div id='main_part'>
    <%= render :partial => "main_part", :object=>@mega  %>
  </div
  <div id='subpart1'>
    <%= render :partial => "subpart1" , :object=>@foobar , :locals=>{:id=>@foobar.id} %>
  </div
  <div id='subpart2'>
    <%= render :partial => "subpart2" , :object=>@barfoo , :locals=>{:id=>@barfoo.id} %>
  </div
<% end %>

Пример части формы, при этом нужно различать кнопки отправки:

<%= label_tag "main_part[name]" ,"Name" %>
<%= text_field_tag "main_part[name]" , main_part.name %>
<%= submit_tag "UPDATE", :name=>'main_part' %>

<%= hidden_tag_field "subpart1_id" , id %>
<%= label_tag "subpart1[city]" ,"City" %>
<%= text_field_tag "subpart1[city]" , subpart1.city%>
<%= submit_tag "ADD", :name=>'subpart1' %>

Итак, теперь вам понадобится мега-контроллер, потому что эта одна форма отправляет сообщение в одно действие контроллера. Этот контроллер будет выглядеть как обычный контроллер только для вашей модели верхнего уровня, но он должен будет выполнять обслуживание ВСЕХ моделей.

Это действие контроллера должно выяснить, какая кнопка отправки была нажата, т.е.

def update
  if params[:main_part]
    # since I controlled the parameter naming I know what's in params[:main_part] 
    # which is main_part[:name]
    @mega = MainThing.find(params[:id])
    @mega.attributes = params[:main_part]
    @mega.save
    # only for main_part is the id valid,  in every other case you have to 
    # manually extract the id
  elsif params[:subpart1]
    @subpart1_id = params[:subpart1_id]
    @foobar = FooBar.find(@subpart1_id)
    @foobar.attrubutes = params[:subpart1]
    @foobar.save
  else
  end
end

Поскольку мегаформа удалена => правда, вам нужно создать файл javascript, который перезагрузит все части вашей формы, поэтому в app/views/megas/update.js.erb:

$('#main_part').html('<%= escape_javascript(render :partial=> "main_part", :object=>@mega) %>');
$('#subpart1').html('<%= escape_javascript(render :partial=> "subpart1", :object=>@foobar :locals=>{:id=>@foobar.id) %>');

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

class MegaController < ApplicationController
  before_filter :load, :only=>[:edit]
  def load
     @mega = Mega.find(params[:id])
     @foobar = @mega.foobar
     @barfoo = @foobar.barfoo
  end

Но вы также можете НЕ делать этого, а вместо этого создавать отдельные файлы javascript и отображать их конкретно в контроллере, т.е.

def update
  if params[:main_part]
    # do whatever...
    render :action=>'update_set1', :handler=>[:erb], :formats=>[:js]
  elsif params[:subpart1]
    # do whatever
    render :action=>'update_set1', :handler=>[:erb], :formats=>[:js]        
  elsif params[:subpart2]
    # do whatever
    render :action=>'update_set2', :handler=>[:erb], :formats=>[:js]        
  end
end

Файл app/views/mega/update_set1.js.erb будет обновлять только части, затронутые обновлением до @mega или @foobar, update_set2.js.erb обновит части, затронутые обновлением, до @barfoo.

Последний пункт, ваша форма удалена => правда, как вы выходите? Предполагая, что у вас есть:

<%= submit_tag 'Cancel', :name=>'cancel' %>

Затем в контроллере вы должны сделать что-то вроде:

 def update
   if params[:cancel]
      render :js=> "window.location = '/'"
   else
      # whatever....
   end
 end

Последним шагом является добавление javascript для отображения/скрытия разделов формы по мере необходимости, упражнение, оставленное читателю....

ОБНОВИТЬ

class Mega < ActiveRecord::Base
   def self.get_param_name
      self.class.name
   end

   def self.get_id_name
      "#{self.class.name}_id"
   end
end

class MyModel < Mega
end

затем в мегаконтроллере:

def edit
   @mymodel = MyModel.find(params[MyModel.get_id_name])
end
person RadBrad    schedule 24.02.2013
comment
Спасибо, что пролили свет на различные методы, которые я могу использовать @RadBrad. Единственная проблема, которую я вижу, заключается в том, что она все еще не так динамична, как мне бы этого хотелось (я прекрасно понимаю, что мне, вероятно, нужно что-то очень сложное). Для добавления контакта и предоставления возможности кому-либо одновременно добавлять компанию и местоположение, этот подход хорошо работает из того, что я тестировал, однако вам нужно запустить процесс снова и создать новый набор «мега» файлов, когда пользователь хочет начать процесс с добавления компании и наличия только местоположения в качестве подраздела. - person bensmithbwd; 25.02.2013
comment
разве это не было бы просто вопросом jquery, скрывающим div? Возможно, логика, которую вы используете, чтобы решить, какие частичные части будут повторно визуализированы? - person RadBrad; 25.02.2013
comment
Итак, в основном ваше решение говорит о том, что у нас есть X-модели, и каждая из этих моделей, которые нам могут понадобиться, войдет в нашу мега-форму AJAX. Итак, динамическая сторона работы завершена (давайте пока забудем о производительности!). Мы можем указать (в том же духе скрытия и отображения), какая модель является основной для добавления, но есть ли простой способ избежать написания огромного контроллера (я предполагаю, что мы должны добавлять к этому огромному контроллеру каждый раз, когда мы добавить новую модель)? Можем ли мы иметь мегаконтроллер, который затем подключает различные контроллеры, которые ему нужны? - person bensmithbwd; 25.02.2013
comment
Как насчет мегамодели, от которой унаследованы все ваши модели? С одним мегаконтроллером. Подмодели будут наследовать методы, которые генерируют, я не знаю, как их назвать, control_stubs, относительно имени подкласса, тогда мегаконтроллеру нужно знать только имя новой модели. - person RadBrad; 25.02.2013
comment
Да, теперь это имеет больше смысла, я новичок в Rails, перехожу с PHP, поэтому мне нужно будет немного протестировать и вернуться с более убедительными ответами, я думаю (поскольку я действительно хочу попытаться получить твердый динамический ответ на эту проблему). К сожалению, я не могу проголосовать за ваш ответ, потому что у меня нет необходимых 15 представителей, но я сделаю это, как только сделаю это, так как это было очень полезно! - person bensmithbwd; 25.02.2013
comment
Я добавил обновление, чтобы объяснить идею мегаконтроллера и модели с самоописанием. - person RadBrad; 25.02.2013

Решение проблемы

После долгих тестов и просмотра кода, любезно предоставленного RadBrad, я случайно наткнулся на Gem с ответом почти точно на то, что я ожидал от вопроса/фразы «динамические вложенные формы (с jQuery)». «Неограниченный», кажется, был неудачным выбором слова для исходного вопроса, но, надеюсь, этот текст поможет поисковым системам лучше подобрать вопрос.

Драгоценный камень

Этот Gem называется Cocoon, и его можно найти здесь: https://github.com/nathanvda/cocoon.

Дополнительно

В качестве бонуса он построен с использованием jQuery, совместим с simple_form, formtastic, а также очень хорошо вписывается в Twitter Bootstrap!

Дополнительные примечания

Я не проверял его полностью, и, честно говоря, проверка уже выглядит незначительной проблемой, но я обновлю этот ответ, если обнаружу что-то, что не работает должным образом.

Примеры проектов github, предоставленные Cocoon, довольно устарели, и вам нужно будет сделать много повторных настроек в файле Gem, чтобы заставить их работать (но это выполнимо!). Будьте уверены, что основной Gem (на момент написания этой статьи) обновлен.

person bensmithbwd    schedule 26.02.2013