Site Navigation With Polymorphic Has Many Through

Hi, seem to keep running into a wall here. I can’t find any resources on site navigation that can deal with any model being in the nav, allow nesting, and can dynamically update.

So I thought about it for a while, and decided on a MenuItems class, which contained the position of the child in relation to it’s siblings, where the parent and the child were polymorphic. Then a given childable object can find it’s parentable by going through the menu_items table.

I want my nav to be able to do things like this:
–Category1
----SubCategory1
------Product1
------Product2
----Product3
–Category2
----Product4
–Page1
–Page2
–Page3

This is the current setup:

MODELS

class MenuItem < ActiveRecord::Base
belongs_to :childable , :polymorphic => true
belongs_to :parentable , :polymorphic => true

acts_as_list :scope => :parentable_id
end

class Category < ActiveRecord::Base
has_one :parent_menu_item , :as => :parentable , :class_name => ‘MenuItem’
has_many :child_menu_items , :as => :childable , :class_name => ‘MenuItem’

has_one :parentable , :through => :parent_menu_item
has_many :childables , :through => :child_menu_items
end

class SubCategory < ActiveRecord::Base
has_many :child_menu_items , :as => :childable , :class_name => ‘MenuItem’
has_one :parent_menu_item , :as => :parentable , :class_name => ‘MenuItem’

has_one :parent , :through => :parent_menu_item
has_many :children , :through => :child_menu_items
end

class Page < ActiveRecord::Base
has_one :parent_menu_item , :as => :parentable , :class_name => ‘MenuItem’
has_one :parent , :through => :parent_menu_item
end

SCHEMA:
ActiveRecord::Schema.define(:version => 20100525184637) do

create_table “categories”, :force => true do |t|
t.datetime “created_at”, :null => false
t.datetime “updated_at”, :null => false
end

create_table “menu_items”, :force => true do |t|
t.integer “position”, :null => false
t.integer “parentable_id”, :null => false
t.string “parentable_type”, :null => false
t.integer “childable_id”, :null => false
t.string “childable_class”, :null => false
t.datetime “created_at”, :null => false
t.datetime “updated_at”, :null => false
end

create_table “pages”, :force => true do |t|
t.datetime “created_at”, :null => false
t.datetime “updated_at”, :null => false
end

create_table “sub_categories”, :force => true do |t|
t.datetime “created_at”, :null => false
t.datetime “updated_at”, :null => false
end

end

I have had a lot of trouble with it, this is the best I’ve gotten so far, but I am still getting the error:
ActiveRecord::HasManyThroughAssociationPolymorphicError: Cannot have a has_many :through association ‘Category#childables’ on the polymorphic object ‘Childable#childable’.

Is there a way to make this work? Is there a better way to do this?

Josh,
We're doing something similar, except that instead of using childable
and parentable, we're using awesome_nested_set. It enables you to
have sub_categories within sub_categories, etc., and its interface is
pretty intuitive and efficient:

http://wiki.github.com/collectiveidea/awesome_nested_set/awesome-nested-set-cheat-sheet

Tilde Equals

I appreciate the suggestion, I had previously used awesomer nested set on a project. I had two issues with it. The first was that the reason for using it was so that you could query for any level of children with one db query, but when I then went to ask each element for it’s children, I found that it was re-querying, so this purpose was unrealized. (possibly this behaviour has been resolved?)

The way to get around it was to transform the results into something usable, ie hashes of hashes, or arrays of arrays, etc. But this was not provided by the plugin, and coupled a lot of code. In the end, I spent about 8 hours writing a method that would iterate through the results one time, accepting lambdas for what to do before entering a deeper level, after leaving it, and what should be done to display a single element. It got pretty ugly, and may have been an example of premature optimization, probably caching would have made the large number of database queries irrelevant, but the real reason I had to do that at all was to mimic a tree like structure, which is what a many to many association already provides.

So I don’t really see much benefit to the nested sets data structure. Also, I’m not sure how well it integrates with polymorphism. In my case, Categories and SubCategories will have different attributes, and Pages will be drastically different than either of these. They all need to be in the navigation. In my previous attempt, I queried for each object that the menu item was representing, but again, that seems inefficient. To be fair, though, when I tried last, i was still very new to Ruby and Rails.

I thought a polymorphic has_many :through would keep things clean, give me the tree structure I need for creating nested

    in my html, and dry up the associations between elements. Of course, it doesn’t have the inherent ordering that nested sets do, which is why I also have acts_as_list positions on the join table.

    At present, I’m probably going to give this plugin a try: http://github.com/fauna/has_many_polymorphs/tree/bcd9626411c9d0658ab527f1e6a0d0622e4f6e15

    If it goes well, I’ll report back here.
    If my above statements represented an incorrect assessment, please correct me.
    If there is some obvious solution that I am missing, please guide me to it.

    -Josh