Polymorphic Associations... confusing. Do I need them?


#1

Hello -

I’m writing a rails app that lets people easily put together online
tutorials. Each tutorial is a single webpage, and consists primarily of
an introduction, a series of worked examples and practice questions, and
then finally a summary.

So, what I’m trying to do is classify these examples and practice
questions together, as “segments” belonging to a tutorial, so that I
could easily sort them in a drag-and-drop list, display their titles in
a table of contents, etc. However, I also want the examples and the
practice questions to be kept as separate models, because they’ll be
used separately later on.

I thought that polymorphic associations might be the way to do this, but
the examples I’m finding of it are all for one model being able to
belong to several other models, which is the opposite of what I’m
looking for - I want two or more models to belong to another model in
the same manner, so that they can all easily be handled together.

Should I be looking at polymorphic associations or something else?

Thanks!
Chris


#2

Seems like what would work is a habtm relationships (and related
tables) between your Tutorial, and your Example and Question models.
That way, an example can belong to different tutorials, and a tutorial
can have multiple examples. (And the same with questions.)

On Apr 28, 8:14 pm, Chris H. removed_email_address@domain.invalid


#3

Sorry, no, I’m looking for questions and examples to each belong_to one
tutorial, and a given tutorial has_many examples and has_many questions.
What I’m really looking for is a way to refer to questions and examples
together in certain situations.

I’m wondering if I can do something like this (crazy fantasy code):

class Tutorial < ActiveRecord::Base
has_many :questions, :as => :sections
has_many :examples, :as => :sections
end

That way, questions and examples could be represented as different
sections of a tutorial. Then, when I want to display a table of contents
for a tutorial, I could write:

<% for section in @sections do %>
<%= section.title %>
<% end %>

And get:
(an example title)
(a question title)
(an example title)
(an example title)
(a question title)
(a question title)
(an example title)

In the order they appear. I’d also need a “position” attribute on the
sections for each tutorial, also, to make sure they appear in the right
order.

Does that make sense? I think single table inheritance might do this,
but I’m planning on using examples and questions for a bunch of other
things, so I don’t think it would work that well. I’m thinking about
defining a whole new “section” model, and using that, but I don’t know
yet.

Thanks!
Chris

François Montel wrote:

Seems like what would work is a habtm relationships (and related
tables) between your Tutorial, and your Example and Question models.
That way, an example can belong to different tutorials, and a tutorial
can have multiple examples. (And the same with questions.)

On Apr 28, 8:14�pm, Chris H. removed_email_address@domain.invalid


#4

Daniel B. wrote:

2009/4/29 Chris H. removed_email_address@domain.invalid

has_many :examples, :as => :sections
And get:
sections for each tutorial, also, to make sure they appear in the right
order.

Does that make sense? I think single table inheritance might do this,
but I’m planning on using examples and questions for a bunch of other
things, so I don’t think it would work that well. I’m thinking about
defining a whole new “section” model, and using that, but I don’t know
yet.

I must be missing something here, but given that you have separate
models [Tutorial, Question, Example] then what’s wrong with:

Tutorial << ActiveRecord::Base
has_many :questions
has_many :example
end

Question << << ActiveRecord::Base
belongs_to :tutoiral
end

Example << ActiveRecord::Base
belongs_to :tutorial
end

Then given that Ruby is a duck typed language just put the questions and
examples together in one array and as long as you stick to the common
API for your ToC, or whatever it will just work:

toc = tutorial.questions.concat(tutorial.examples)

toc.each do |item|
puts item.class
puts item.name
end


#5

Robert W. wrote:

Tutorial << ActiveRecord::Base
has_many :questions
has_many :example
end

Oops, sorry for the typo. Should be:
has_many :examples


#6

2009/4/29 Chris H. removed_email_address@domain.invalid

has_many :examples, :as => :sections
And get:
sections for each tutorial, also, to make sure they appear in the right
order.

Does that make sense? I think single table inheritance might do this,
but I’m planning on using examples and questions for a bunch of other
things, so I don’t think it would work that well. I’m thinking about
defining a whole new “section” model, and using that, but I don’t know
yet.

You could define a Section model which belongs_to your Tutorial model
and
has a position attribute so you can determine the order.

In your Tutorial model
has_many :sections , :order => ‘sections.position ASC’

You then set up your Example and Question models so that they have a
polymorphic has_many association with the Section model.

In Section:
belongs_to :sectionable , :polymorphic => true

In Question or Example:
has_many :sections , :as => :sectionable

Now you can say:

@tutorial.sections.each do |section|
  section=section.sectionable
  case section
  when Example
    puts section.some_example_method
  when Question
    puts section.some_question_method
  else
    puts 'what!'
  end
end

Has the nice benefit that you can recycle your examples and questions in
next year’s tutorial if you’re lazy :slight_smile:


Daniel B.

http://blog.web17.com.au
http://github.com/danielbush/sifs/tree/master
http://github.com/danielbush


#7

2009/4/29 Robert W. removed_email_address@domain.invalid

but I’m planning on using examples and questions for a bunch of other
things, so I don’t think it would work that well. I’m thinking about
defining a whole new “section” model, and using that, but I don’t know
yet.

I must be missing something here, but given that you have separate
models [Tutorial, Question, Example] then what’s wrong with:

I might be missing something too and over-complicating things.
The Section model in my previous post has a position attribute and
allows
Chris to create a tutorial of ordered sections where each section is
linked
to an Example or Question.
I’m not sure how you guarantee that with your version, although maybe
with
some effort you could.
Because the Examples and Questions are has_many Sections, they are also
reusable in a future tutorial; it is the sections that are tied
specifically
to a given tutorial instance.

Anyway, it seemed like the Section abstraction was a nice way to show
off
polymorphic routes.


Daniel B.

http://blog.web17.com.au
http://github.com/danielbush/sifs/tree/master
http://github.com/danielbush


#8

2009/4/30 Daniel B. removed_email_address@domain.invalid

sections for each tutorial, also, to make sure they appear in the right

Anyway, it seemed like the Section abstraction was a nice way to show off
polymorphic routes.

oh crap, I mean “polymorphic associations”.
The word polymorphic is getting too polymorphic…


Daniel B.

http://blog.web17.com.au
http://github.com/danielbush/sifs/tree/master
http://github.com/danielbush


#9

Ah, this looks great! I’ll try it out today.

Thanks, Dan!


#10

Not sure how experienced you are with polymorphic assocs. I omitted
some
details chief of which is that you need two fields in your sections
table:
sectionable_id and sectionable_type. There’s a shortcut for doing this
in a
rails migration
t.references :sectionable , :polymorphic => true
or something like that.

2009/4/30 Chris H. removed_email_address@domain.invalid

Ah, this looks great! I’ll try it out today.

Thanks, Dan!

Posted via http://www.ruby-forum.com/.


Daniel B.

http://blog.web17.com.au
http://github.com/danielbush/sifs/tree/master
http://github.com/danielbush


#11

Yeah, I was able to figure it out and everything’s working great, just
like I wanted. Thanks again!

Daniel B. wrote:

Not sure how experienced you are with polymorphic assocs. I omitted
some
details chief of which is that you need two fields in your sections
table:
sectionable_id and sectionable_type. There’s a shortcut for doing this
in a
rails migration
t.references :sectionable , :polymorphic => true
or something like that.

2009/4/30 Chris H. removed_email_address@domain.invalid

Ah, this looks great! I’ll try it out today.

Thanks, Dan!

Posted via http://www.ruby-forum.com/.


Daniel B.

http://blog.web17.com.au
http://github.com/danielbush/sifs/tree/master
http://github.com/danielbush


#12

Hi again -

I’m having another problem now. I’m trying to set up a nested model form
for the setup above (so on the edit tutorial screen, someone can edit
the contents of all the examples and questions at once). In tutorial.rb
I’ve already set up:

accepts_nested_attributes_for :sections, :allow_destroy => true

Right now, the part of the edit view that handles sections looks like
this:

<% for section in @sections %>
<% if section.sectionable_type == “Example” %>
<% f.fields_for :examples, section.sectionable do |g| %>


<%= g.label :title %><br >
<%= g.text_field :title %>



<%= g.label :content %><br >
<%= g.text_area :content %>


<% end %>
<% elsif section.sectionable_type == “Question” %>
<% f.fields_for :questions, section.sectionable do |g| %>


<%= g.label :title %><br >
<%= g.text_field :title %>



<%= g.label :prompt %><br >
<%= g.text_area :prompt %>



<%= g.label :answer %><br >
<%= g.text_area :answer %>


<% end %>
<% end %>
<% end %>

When I try updating some fields and submitting the form, I get a
“unknown attribute: example” error, and the params hash only contains
the information for the last example and question in the list. Looking
at the html the above generates, everything looks like the following:

Title<br >

Content<br >
Fuga tempora aperiam…

So… I suppose I need to differentiate the input fields by the id of
the section (or id and type of the sectionable) being edited, but I’m
not sure how to do it, or why Rails isn’t doing that for me. My code
looks like the example in the API one-to-many fields_for example
(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#M001573),but
the polymorphic association must be throwing me off somewhere, and I’m
not sure where.

Does anyone have any ideas?