How can I elegantly construct a form for a model that has a polymorphic association?
Here are my models:
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
Now, what I need is a form that will allow the user to create or update Lessons. The questions is, how can I provide a select menu that offers a mix of Subjects and Categories? (To the user, on this particular form, Subjects and Categories are interchangeable, but that's not the case elsewhere.)
Ideally, this would look something like this:
views/lessons/_form.html.haml
= simple_form_for(@lesson) do |f|
= f.input :title
= f.association :topic, :collection => (@subjects + @categories)
That won't work because we'd only be specifying the topic_id, and we need the topic_types as well. But how can we specify those values?
I guess the crux of the problem is that I really want a single select menu that specifies two values corresponding to two different attributes (topic_id and topic_type). Is there any ele开发者_运维知识库gant railsy way to do this?
A few notes:
a) Single table inheritance would make this issue go away, but I'd like to avoid this, as Categories and Subjects have their own relationship… I'll spare you the details.
b) I might could pull some javascript shenanigans, yes? But that sounds messy, and if there's a cleaner way to do it, some magic form helper or something, then that's certainly preferable.
c) Though I'm using simple_form, I'm not wedded to it, in case that's complicating matters.
Thanks
If you don't wish to use STI, you can do something similar: create a new model Topic(name:string)
which will polymorphically reference Subject
or 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
In the view where you create a new Subject/Category:
<%= form_for @subject do |subject_form| %>
<%= subject_form.fields_for :topic do |topic_fields| %>
<%= topic_fields.text_field :name %>
<% end %>
<% end %>
After thinking this through, the less dirty implementation IMO would be to hire the JS shenanigans (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'}
With a sprinkle of JS (your needs may vary):
$('input:radio.topic_id').change(function() {
$('input:hidden.topic_type').val($(this).attr('data-type'));
});
Notes:
- I use a radio button to select a topic (category or subject) in a list
- The class name of each of possible topic is stored in an attribute 'data-type'
- When a radio button is selected, the class name is copied to the hidden input via JS
- Using: HTML5, jQuery, haml, simple_form
精彩评论