I have a class that represents a simple tree (but usually it''s flat). It appears that the belongs_to relationship is returning the wrong ID. The class looks like: class AssetNode < ActiveRecord::Base belongs_to :asset_node # parent has_many :asset_nodes # children has_and_belongs_to_many :assets # leaf nodes The schema looks like: CREATE TABLE "asset_nodes" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "node_type" integer, "impact_factor" float DEFAULT 1.0, "asset_node_id" integer, "service_id" integer, "created_at" datetime, "updated_at" datetime); and a query of the DB looks like: sqlite> select * from asset_nodes; 833850115|1|88.8||1217222815|2009-10-15 12:52:26|2009-10-15 12:52:26 833850116|2|99.9|833850115||2009-10-15 12:52:26|2009-10-15 12:52:26 For a simple test, I fetch the asset node represented by that last row, then fetch its parent, by following the asset_node association. But it takes two passes to get the right node: def test_should_compute_impact_factor asset = assets(:va2) an8 = asset.asset_nodes[0] puts "an8 asset node is #{an8} #{an8.id}, IF is # {an8.impact_factor}" puts " parent asset_node_id is #{an8.asset_node_id}" an7 = an8.asset_node puts "an7 is #{an7} #{an7.id}, IF is #{an7.impact_factor}" puts " parent asset_node_id is #{an7.asset_node_id}" anx = an7.asset_node puts "an7''s parent (which should not exist) is #{anx} #{anx.id}, IF is #{anx.impact_factor}" I expect the an7 asset_node to be returned when I fetch an8.asset_node, but instead I get: an8 asset node is #<AssetNode:0x1030ce3e8> 833850116, IF is 99.9 parent asset_node_id is 833850116 an7 is #<AssetNode:0x1030a9278> 833850116, IF is 99.9 parent asset_node_id is 833850115 an7''s parent (which should not exist) is #<AssetNode:0x1030a5bf0> 833850115, IF is 88.8 When I look at the SQL queries and responses, they seem correct, but I see that an8 is selected twice, which surprises me: -- Find "parent" asset nodes for asset va2 SELECT * FROM "asset_nodes" INNER JOIN "asset_nodes_assets" ON "asset_nodes".id = "asset_nodes_assets".asset_node_id WHERE ("asset_nodes_assets".asset_id = 1011883891 ); -- This is an8, pointing up to an7 833850116|2|1.0|833850115||2009-10-13 18:54:49|2009-10-13 18:54:49| 833850116|1011883891 -- Find parent asset node for above (but this is really re-selecting an8!) SELECT * FROM "asset_nodes" WHERE ("asset_nodes"."id" = 833850116); -- This is an8 again! 833850116|2|1.0|833850115||2009-10-13 18:54:49|2009-10-13 18:54:49 -- Find next parent SELECT * FROM "asset_nodes" WHERE ("asset_nodes"."id" = 833850115) ; -- This is an7, with no parent asset 833850115|1|1.0||1217222815|2009-10-13 18:54:49|2009-10-13 18:54:49 What''s going on here? Is it me, or is there a bug somewhere? -Russ
RussK wrote:> I have a class that represents a simple tree (but usually it''s flat). > It appears that the belongs_to relationship is returning the wrong > ID. The class looks like:If you need a basic tree structure, why not just use the acts_as_tree plugin? http://github.com/rails/acts_as_tree/ Example: class Category < ActiveRecord::Base acts_as_tree :order => "name" end It''s much simpler and gives you a lot of convenience methods for traversing the tree. -- Posted via http://www.ruby-forum.com/.
Robert Walker wrote:> RussK wrote: >> I have a class that represents a simple tree (but usually it''s flat). >> It appears that the belongs_to relationship is returning the wrong >> ID. The class looks like: > > If you need a basic tree structure, why not just use the acts_as_tree > plugin? > > http://github.com/rails/acts_as_tree/ > > Example: > class Category < ActiveRecord::Base > acts_as_tree :order => "name" > end > > It''s much simpler and gives you a lot of convenience methods for > traversing the tree.Actually, for this use case (as for most!) awesome_nested_set would be better than acts_as_tree. Among other things, it lets you get all descendants with a single query, whereas with acts_as_tree you have to get immediate children and then run more queries. Best, -- Marnen Laibow-Koser http://www.marnen.org marnen-sbuyVjPbboAdnm+yROfE0A@public.gmane.org -- Posted via http://www.ruby-forum.com/.
Thanks for your responses. Before I posted this question, I looked at awesome_nested_set. But I have two problems with it: (1) my entire application is largely a tree, but the nodes are of different classes at different levels, and the nested sets need all nodes to be of the same or a derived type, (2) the tree here is almost always flat, making the nested set solution way overkill. So I''m just looking to understand whether there''s a bug here or I''m misunderstanding how RoR handles a self-referential belongs_to association. Specifically, I''d like to know why an7 = an8.asset_node Didn''t actually return the object that an8.asset_node refers to? -Russ
RussK wrote:> Thanks for your responses. Before I posted this question, I looked at > awesome_nested_set. But I have two problems with it: (1) my entire > application is largely a tree, but the nodes are of different classes > at different levels, and the nested sets need all nodes to be of the > same or a derived type,That''s easily worked around. Don''t discount nested sets on that basis.> (2) the tree here is almost always flat,Um, how can a tree be flat?> making the nested set solution way overkill.It''s not overkill. It''s the fist structure you should think of when you have anything treelike. You''re trying to reinvent the square wheel with your approach, I think. That said, I''ll try to answer your question in more detail when I have time. Best, -- Marnen Laibow-Koser http://www.marnen.org marnen-sbuyVjPbboAdnm+yROfE0A@public.gmane.org -- Posted via http://www.ruby-forum.com/.
On Oct 15, 12:49 pm, RussK <r...-p0Ccooz5D+nR7s880joybQ@public.gmane.org> wrote:> I have a class that represents a simple tree (but usually it''s flat). > It appears that the belongs_to relationship is returning the wrong > ID. The class looks like: > > class AssetNode < ActiveRecord::Base > belongs_to :asset_node # parent > has_many :asset_nodes # children > has_and_belongs_to_many :assets # leaf nodes > > The schema looks like: > > CREATE TABLE "asset_nodes" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT > NULL, "node_type" integer, "impact_factor" float DEFAULT 1.0, > "asset_node_id" integer, "service_id" integer, "created_at" datetime, > "updated_at" datetime); > > and a query of the DB looks like: > > sqlite> select * from asset_nodes; > 833850115|1|88.8||1217222815|2009-10-15 12:52:26|2009-10-15 12:52:26 > 833850116|2|99.9|833850115||2009-10-15 12:52:26|2009-10-15 12:52:26 > > For a simple test, I fetch the asset node represented by that last > row, then fetch its parent, by following the asset_node association. > But it takes two passes to get the right node: > > def test_should_compute_impact_factor > asset = assets(:va2) > an8 = asset.asset_nodes[0]There''s definitely *something* weird going on. Can you try putting an8.reload here in the code and see if it changes the results? The only thing I can think of is the HABTM is somehow leaving incorrect data behind. --Matt Jones
> That''s easily worked around. Don''t discount nested sets on that basis.Working around that would be great for my application overall, as I''d like to turn it into one giant tree across multiple, unrelated classes. How might that be done?> > (2) the tree here is almost always flat, > > Um, how can a tree be flat?The tree typically has a height of zero (just a root).> > making the nested set solution way overkill. > > It''s not overkill. It''s the fist structure you should think of when you > have anything treelike. You''re trying to reinvent the square wheel with > your approach, I think. That said, I''ll try to answer your question in > more detail when I have time.Your point is well taken. There is another part of my application that has depth and, being ignorant of nested tree, I wrote that functionality and will definitely refactor with an awesome tree. Oddly, that code seems to work while this part does not. But my question is not about trees, it''s about why RoR is returning the wrong value for an association. -Russ
> There''s definitely *something* weird going on. Can you try putting > an8.reload here in the code and see if it changes the results? The > only thing I can think of is the HABTM is somehow leaving incorrect > data behind.Sure enough, if I add "an8.reload" then "an7 = an8.asset_node" correctly returns an7. But I noticed in the log that the set of SQL queries with and without reload is exactly the same. I expected reload to run another query, as the doc says it "Reloads the attributes of this object from the database", but it didn''t. -Russ
I found the solution to my problem. The situation is that class A has a habtm relationship to class B and also has a has_many and belongs_to relationship to itself. This results in two columns named a_id in the DB, one in the join table and one in A''s table. When fetching the set of As via B, Rails apparently uses the a_id from the join table to construct instances of A. This causes A''s belongs_to relationship to point to itself instead of its real A owner. To fix this, I changed A''s habtm to "has_and_belongs_to_many :bs, :foreign_key => ''a_ref''" and B''s habtm to "has_and_belongs_to_many :as, :association_foreign_key => ''a_ref''", with the join table using a_ref instead of a_id. -Russ