Wrong value for belongs_to?

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?

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.

Robert W. 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?

GitHub - rails/acts_as_tree: NOTICE: official repository moved to https://github.com/amerine/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
[email protected]

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
[email protected]

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

On Oct 15, 12:49 pm, RussK [email protected] wrote:

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 J.

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