3

I have the following:

class Menu < ActiveRecord::Base
  has_many :menu_headers
  # has_many :menu_headers, :conditions => {:parent_id => 0} - was trying 
  # to set parent_id to 0 for top level 
  has_many :menu_items, :through => :menu_headers
end

class MenuHeader < ActiveRecord::Base
  belongs_to :menu
  has_many :menu_items
  acts_as_tree
end

class MenuItem < ActiveRecord::Base
  #belongs_to :menu
  has_one :menu, :through => :menu_header
  belongs_to :menu_header 
end

EDIT #3 - seed data sample Here is for example, how would be seeding (would like to get both mi_1 and mi_2)

m_1=Menu.create({:name => "Dinner Menu test", :location_id => 145})
c_1=m_1.menu_headers.create({:name => "White Wine"})
c_2=c_1.children.create({:name => "Sauvignon Blanc"})
mi_1=c_2.menu_items.create({:header => "SB menu item #1"})
mi_2=c_1.menu_items.create({:header => "SB menu item #2"})
m_1.menu_items # returns only mi_2; would like both mi_2 and mi_1

end EDIT #3

The problem is that I can't do the following to return all the menu_items:

m=Menu.find(5)
m.menu_items

for the has_many :through in Menu. This will get ONLY the menu-items of the top-level and not deeper levels. I tried add menu_id to the menu_headers but this forced me to put in the commented_out line which got me back to only getting the top level headers. Is there a way to say get me all the deeper levels of the menu_header so that the above works?

Is there a workaround to this or something else? I'm pretty much stuck with acts_as_tree so using something like awesome_nested_set isn't really in the cards.

thx

EDITS - a couple of issues in the comments below; I wrote this at 4am

EDIT #2
I am able to get all children via this:

class MenuHeader < ActiveRecord::Base
   ...
   # only gets all children of the current menu_header_id
   def all_children
     all = []
     self.children.each do |menu_header|
       all << menu_header
       root_children = menu_header.all_children.flatten
       all << root_children unless root_children.empty?
     end
     return all.flatten
   end

end

I would like to be able to call all_children and get menu_items on the main Menu item. Perhaps integrating the above with a call on the main Menu item and then just storing a cached copy in the menus table when there is an update to a menu_item.

Will look into Ancestry but am hesitant to move to another gem since other code is dependent on this. If could be done quickly, might be ok but this is a fairly complex object with many other pieces and acts_as_tree is fairly straightforward.

Edit #4 - Here is sample data:

menus
+----+-------------+------------------+
| id | location_id | name             |
+----+-------------+------------------+
|  1 |         145 | item  cocktails  |
+----+-------------+------------------+

menu_headers                            
+----+----------------------+-----------+---------+
| id | name                 | parent_id | menu_id |
+----+----------------------+-----------+---------+
|  1 | Wines By The Glass   |         0 |       1 |
|  2 | WHITE WINES          |         1 |    NULL |
|  3 | WHITE WINES child #1 |         2 |    NULL |
|  4 | WHITE WINES child #2 |         2 |    NULL |
|  5 | WHITE WINES child #3 |         2 |    NULL |
|  6 | RED WINES            |         0 |       1 |
+----+----------------------+-----------+---------+

menu_items
+----+----------------------------------------------------+----------------+
| id | header                                             | menu_header_id |
+----+----------------------------------------------------+----------------+
|  1 | SAUVIGNON BLANC item #1                            |              2 |
|  2 | MONTEPULCIANO                                      |              6 |
+----+----------------------------------------------------+----------------+

thx

timpone
  • 19,235
  • 36
  • 121
  • 211
  • Not entirely clear what you're wanting and getting. Are you wanting `Menu.find(5).menu_items` to return the items for Menu number 5 (I'd expect it to), or for all the `Menu` objects which are descendants of number 5 (I'd not expect it to, out of the box)? – Chowlett Nov 28 '11 at 13:44
  • the former; Menu.find(5).menu_items should return all menu_items for Menu id=5 – timpone Nov 28 '11 at 13:48
  • Hmm. What if you change `MenuItem` to `belongs_to :menu, :through => :menu_headers`? – Chowlett Nov 28 '11 at 14:09
  • hmm... my mistake - I updated to what it is in the model `has_one, :through => :menu_headers`. I tried `belongs_to :menu, :through => menu_headers` but no such luck – timpone Nov 28 '11 at 14:16
  • Ah, hang on. `MenuHeader` needs to `belong_to :menu` – Chowlett Nov 28 '11 at 14:17
  • ugh... sorry was 4 in the morning when I started writing question and that was a mistake. The acts_as_tree is also on menu_header not on menu – timpone Nov 28 '11 at 14:27
  • I'm afraid your Edit #2 has confused me further (I know that 4am feeling). Can you post some sample data and what you expect to retrieve? Also, have you set `menu_id` on all of the `MenuHeader`s in a given tree? – Chowlett Nov 28 '11 at 14:49
  • Added some seed data, I have done both adding menu_id and leaving menu_id equal to null which is the default. adding more info now – timpone Nov 28 '11 at 15:35
  • The sample code as you've written it is as I would expect. But you say you don't get both even if you've done `c_2=c_1.children.create({:name => "Sauvignon Blanc", :menu => m_1})`? – Chowlett Nov 28 '11 at 15:53
  • Menu_id is not populated (see above) but if i populate and set the :condition => {:parent_id => 0}, then on the m.menu_items call, it only goes after menu_headers with a parent_id of 0 irrespective of the menu_id value. ugh.... – timpone Nov 28 '11 at 16:00
  • Ohhh! Light dawns. I think I have an answer for you. – Chowlett Nov 28 '11 at 17:04

2 Answers2

0

You can try ancestry, in my opinion it's better than acts_as_tree

Kir
  • 7,981
  • 12
  • 52
  • 69
  • I'm a little bit stuck with acts_as_tree unless I can get a way to convert it fairly quickly. Let me take a look - would anestry support what I'm trying to do? – timpone Nov 28 '11 at 14:41
  • I guess the issue I see with ancestry is whether I could use it in conjunction with has_many :through. For example, this doesn't looks good https://github.com/stefankroes/ancestry/issues/49 – timpone Nov 28 '11 at 14:49
0

Try this.

class Menu < ActiveRecord::Base
  has_many :menu_headers, :conditions => {:parent_id => 0}
  has_many :menu_sub_headers, :class_name => 'MenuHeader'
  has_many :menu_items, :through => :menu_sub_headers
end

class MenuHeader < ActiveRecord::Base
  belongs_to :menu
  has_many :menu_items
  acts_as_tree
end

class MenuItem < ActiveRecord::Base
  has_one :menu, :through => :menu_header
  belongs_to :menu_header 
end

That should give:

m1=Menu.create(:name => "Dinner Menu test", :location_id => 145)
c1=m1.menu_headers.create(:name => "White Wine")
c2=c1.children.create(:name => "Sauvignon Blanc", :menu => m1)
mi1=c2.menu_items.create(:header => "SB menu item #1")
mi2=c1.menu_items.create(:header => "SB menu item #2")
m_1.menu_items # should return mi1 and mi2
m_1.menu_headers # should return just c1
m_1.menu_sub_headers # should return c1 and c2
Chowlett
  • 45,935
  • 20
  • 116
  • 150
  • So, it looks like using both strategies - let acts_as_tree do it's thing and then create a second relationship to handle the has_many :through? – timpone Nov 28 '11 at 23:09
  • Pretty much. The `acts_as_tree` is fairly incidental; the main point is that you've got two `Menu`<->`MenuHeader` relations - one comes with a condition to narrow down to just the top-level, while the other selects everything so `:through` works. Does that do the job for you? – Chowlett Nov 29 '11 at 09:26