In a frenzy of Bluetooth-enabled coding from the local diner, I have implemented double polymorphic associations in the has_many_polymorphs plugin. This lets you create a multi-class directed graph in ActiveRecord with great ease.
show, don’t tell
For example, say we wanted to model glue. We want to be able to glue PopsicleSticks
and Leaves
to themselves, but also to Paper
. We will make a join model called Adhesion
for this:
class Adhesion < ActiveRecord::Base
belongs_to :glue_receiver, :polymorphic => true
belongs_to :glue_applier, :polymorphic => true
acts_as_double_polymorphic_join :glue_receivers =>
[:popsicle_sticks, :leaves, :papers],
:glue_appliers =>
[:popsicle_sticks, :leaves]
end
This means that a PopsicleStick
will have a glue_receivers
polymorphic collection that can accept anything in the :glue_receivers
list, above. A Paper
, though, will not have a .glue_receivers
method. It will have a .glue_appliers
method, but it is not itself a :glue_applier
. This is tricky. Just remember that the lists in the acts_as
call are the types of children for that relationship, not the types of parents.
Also, using the individual typed collections (e.g. PopsicleStick#leaves
) will always get you the children that belong to the record, instead of the parents that own the record. In this case it will fetch the Leaves
from a PopsicleStick
object’s .glue_receivers
.
warning! class-reloading laser in use!
The API is a little different because there is no parent class that can act as a controller. Things are instead centralized in the join model.
This means that you have to reference the join class (either with load()
, by evaluating its bare class name, or another way) before the associations will be injected into the “real” classes. Rails’ selective reloading in the development environment can lead to trouble—a modified class will get reloaded and forget its relationship to the join. Keep this in mind.
crazy clown collections
Combining this with the other plugin features leads to controlled chaos. Self-referential directed double-polymorphic namespaced STI associations are (at least according to my tests) fully supported.
Please try it out and let me know if you have trouble. I really appreciate the filing of bugs.
How would you do the following: I started with Categories habtm Products in a catalogue website where Categories acts_as_nested_set (drill down navigation). But then I thought it would be nice if each category and product could also have several Tabs of related information (tabular navigation, moving across instead of drilling down). Hence if a category or product needs more explanation, we can add a Tab to the Category or Product model. The tabs themselves can contain lists of related products or categories, or other nested tabs….
Is your plugin appropriate for this scenario? Not quite sure how to define the associations yet. Seems to me it would be something along the lines of:
Category has many children categories (through acts_as_nested_set), has many products, optionally has many tabs, and could belong to many tabs.
Product has and belongs to many categories, can optionally have and belong to many tabs.
Tabs can have and belong to Categories, Products or Tabs (in case we want to have nested tabnav inside a tab)
Is this even possible? Or would it cause some recursive association blow-up?
This is quite possible.
(More or less.)
This will give your
Categories
,Products
, andTabs
all a collectionparent_tabs
(for upwards navigation) and a collectiontabs
(for downwards navigation), and eachTab
can contain any number of other items.You may want to rethink such a rigid organization strategy though. Why not just relate products and categories to other products and categories, and build the tabs on the fly?
I want to say thanks for the great plugin.
I found a serious bug…
[Snipped]
Thanks, Robin. I’ll look into it in a few days. I moved your bug report to Rubyforge.