Как я могу элегантно построить форму для модели с полиморфной ассоциацией?

Вот мои модели:

class Lesson < ActiveRecord::Base
  belongs_to :topic, :polymorphic => true  
  validates_presence_of :topic_type, :topic_id  
end

class Subject < ActiveRecord::Base  
   has_many :lessons, :as => :topic  
end

class Category < ActiveRecord::Base  
  has_many :lessons, :as => :topic  
end

Теперь мне нужна форма, которая позволит пользователю создавать или обновлять уроки. Вопрос в том, как я могу предоставить меню выбора, которое предлагает сочетание тем и категорий? (Для пользователя в этой конкретной форме Темы и Категории взаимозаменяемы, но это не так в других местах.)

В идеале это должно выглядеть примерно так:

просмотры/уроки/_form.html.haml

= simple_form_for(@lesson) do |f|
  = f.input :title
  = f.association :topic, :collection => (@subjects + @categories)

Это не сработает, потому что мы будем указывать только id_темы, а нам нужны и типы_тем. Но как мы можем указать эти значения?

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

Несколько заметок:

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

б) Я мог бы использовать некоторые махинации с javascript, да? Но это звучит грязно, и если есть более чистый способ сделать это, какой-нибудь магический помощник формы или что-то в этом роде, то это, безусловно, предпочтительнее.

c) Хотя я использую simple_form, я не привязан к нему, если это усложняет ситуацию.

Спасибо


person doublea    schedule 11.07.2011    source источник


Ответы (2)


Если вы не хотите использовать STI, вы можете сделать нечто подобное: создать новую модель Topic(name:string), которая будет полиморфно ссылаться на Subject или Category.

class Lesson < ActiveRecord::Base
  belongs_to :topic
  validates_presence_of :topic_id  
end

class Topic < ActiveRecord::Base
  belongs_to :topicable, :polymorphic => true
end

class Subject < ActiveRecord::Base
   has_one :topic, :as => :topicable
   has_many :lessons, :through => :topic
   accepts_nested_attributes_for :topic
end

class Category < ActiveRecord::Base  
   has_one :topic, :as => :topicable
   has_many :lessons, :through => :topic
   accepts_nested_attributes_for :topic
end

В представлении, где вы создаете новую тему/категорию:

<%= form_for @subject do |subject_form| %>
  <%= subject_form.fields_for :topic do |topic_fields| %>
    <%= topic_fields.text_field :name %>
  <% end %>
<% end %>
person RocketR    schedule 11.07.2011
comment
Это хорошая идея, но я ненавижу засорять базу данных дополнительной таблицей — данные будут по сути избыточными, и мне нужно будет настроить триггер для создания новой темы для каждой новой темы или категории. (Как это сделать? Наблюдатели?) Кажется, должно быть лучшее решение на стороне просмотра. - person doublea; 12.07.2011
comment
Лишняя таблица в базе не навредит. Чтобы создать тему и тему (или категорию) вместе, вам просто нужно использовать accepts_nested_attributes_for в модели и fields_for в форме. Form создаст необходимый хэш, а модель автоматически сохранит оба объекта. - person RocketR; 12.07.2011
comment
Я столкнулся с той же проблемой, но мне не нравится это решение. Нам не нужен дополнительный стол для этого. - person gamov; 16.09.2013
comment
@gamov Буду рад, если вы опубликуете свое решение. Помните, однако, что первоначальный вопрос был о том, как это сделать без JS. - person RocketR; 16.09.2013

Поразмыслив над этим, менее грязной реализацией IMO было бы нанять махинаций с JS (b):

= f.input_field :topic_type, as: :hidden, class: 'topic_type'
- (@subjects + @categories).each do |topic|
  = f.radio_button :topic_id, topic.id, {:'data-type' => topic.class.name, class: 'topic_id'}

С небольшим количеством JS (ваши потребности могут отличаться):

$('input:radio.topic_id').change(function() {
  $('input:hidden.topic_type').val($(this).attr('data-type'));
});

Примечания:

  • Я использую переключатель, чтобы выбрать тему (категорию или тему) в списке
  • Имя класса каждой из возможных тем хранится в атрибуте «тип данных».
  • Когда выбран переключатель, имя класса копируется в скрытый ввод через JS.
  • Использование: HTML5, jQuery, haml, simple_form
person gamov    schedule 17.09.2013