# JoinDependency.instantiate & Non-unique association names

**URL:** <https://www.ruby-forum.com/t/joindependency-instantiate-non-unique-association-names/128861>\
**Category:** Rails\
**Created:** [February 12, 2008, 7:19am UTC](https://www.ruby-forum.com/t/joindependency-instantiate-non-unique-association-names/128861 "2008-02-12T07:19:34Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![tom\_302](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/e480ec/32.png) [@tom\_302](https://www.ruby-forum.com/u/tom_302)\
**Post date:** [February 12, 2008, 7:19am UTC](https://www.ruby-forum.com/t/joindependency-instantiate-non-unique-association-names/128861/1 "2008-02-12T07:19:34Z")

</div>

Hello,

I’m instantiating the results of a multi-table, find\_by\_sql query. I  
start by creating an association graph as an argument to  
JoinDependency. The instantiation fails if the graph contains any  
duplicate association names, but it works if the associations are  
unique. (The sql query executes correctly even when instantiation  
fails)

I can’t think of a reason for an association name to be unique across  
different models in a graph, so I’m wondering if this is a bug?

Thanks,  
Tom

Here’s an example of the problem:

#Create tables  
create table a\_foos(id int(11) DEFAULT NULL auto\_increment PRIMARY  
KEY, name varchar(255));  
create table b\_foos(id int(11) DEFAULT NULL auto\_increment PRIMARY  
KEY, name varchar(255), a\_foo\_id int, c\_foo\_id int, e\_foo\_id int);  
create table c\_foos(id int(11) DEFAULT NULL auto\_increment PRIMARY  
KEY, name varchar(255),type varchar(255));  
create table d\_foos(id int(11) DEFAULT NULL auto\_increment PRIMARY  
KEY, name varchar(255), c\_foo\_id int, e\_foo\_id int);  
create table e\_foos(id int(11) DEFAULT NULL auto\_increment PRIMARY  
KEY, name varchar(255));

#Define models  
class AFoo \< ActiveRecord::Base  
has\_many :b\_foos  
end  
class BFoo \< ActiveRecord::Base  
belongs\_to :a\_foo  
belongs\_to :c\_foo  
belongs\_to :e\_foo  
end  
class CFoo \< ActiveRecord::Base  
has\_many :b\_foos  
has\_many :d\_foos  
end  
class DFoo \< ActiveRecord::Base  
belongs\_to :c\_foo  
belongs\_to :d\_foo  
end  
class EFoo \< ActiveRecord::Base  
has\_many :b\_foos  
has\_many :d\_foos  
end

#Create partial data  
a\_foo = AFoo.new(:name=\>“some AFoo”)  
a\_foo.save!  
b\_foo = BFoo.new(:name=\>“some BFoo”, :a\_foo=\>a\_foo)  
b\_foo.save!

#Setup Join Dependency and create SQL Query  
assoc\_graph = [{:b\_foos=\>[{:c\_foo=\>[:d\_foos]}, {:e\_foo=\>[:d\_foos]}]}]  
#association, :d\_foos, exists on two models in graph  
join\_dep =  
ActiveRecord::Associations::ClassMethods::JoinDependency.new(AFoo,  
assoc\_graph, nil)

sql\_selects = []  
sql\_joins = []  
for i in 0…join\_dep.join\_associations.size-1  
assoc = join\_dep.join\_associations[i]  
sql\_selects += assoc.parent.column\_names\_with\_alias.collect{ |map|  
“#{assoc.parent.aliased\_table\_name}.`#{map[0]}` AS #{map[1]}” } if  
i==0 #base  
sql\_selects += assoc.column\_names\_with\_alias.collect{ |map|  
“#{assoc.aliased\_table\_name}.`#{map[0]}` AS #{map[1]}” }  
sql\_joins \<\< assoc.association\_join  
end  
query = "  
SELECT #{sql\_selects.join(",\n “)}  
FROM #{join\_dep.join\_associations[0].parent.table\_name}  
#{join\_dep.join\_associations[0].parent.aliased\_table\_name}  
#{sql\_joins.join(”\n")}  
"

#The query runs correctly  
#±------±----------±------±----------±------+…  
#| t0\_r0 | t0\_r1 | t1\_r0 | t1\_r1 | t1\_r2 |…  
#±------±----------±------±----------±------+…  
#| 1 | some AFoo | 1 | some BFoo | 1 |…  
#±------±----------±------±----------±------+…

results = AFoo.find\_by\_sql(query)  
=\> [#]

eager\_afoos = join\_dep.instantiate(results)  
NoMethodError: undefined method `d_foos' for #<BFoo:0x2a27978> from /tmp/Main/vendor/rails/activerecord/lib/active_record/ attribute_methods.rb:205:in`method\_missing’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1486:in `send' from /tmp/Main/vendor/rails/activerecord/lib/active_record/ associations.rb:1486:in`construct\_association’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1475:in `construct' from /tmp/Main/vendor/rails/activerecord/lib/active_record/ associations.rb:1474:in`each’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1474:in `construct' from /tmp/Main/vendor/rails/activerecord/lib/active_record/ associations.rb:1471:in`construct’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1470:in `each' from /tmp/Main/vendor/rails/activerecord/lib/active_record/ associations.rb:1470:in`construct’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1476:in `construct' from /tmp/Main/vendor/rails/activerecord/lib/active_record/ associations.rb:1474:in`each’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1474:in `construct' from /tmp/Main/vendor/rails/activerecord/lib/active_record/ associations.rb:1471:in`construct’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1470:in `each' from /tmp/Main/vendor/rails/activerecord/lib/active_record/ associations.rb:1470:in`construct’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1402:in `instantiate' from (irb):41:in`each\_with\_index’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1397:in `each' from /tmp/Main/vendor/rails/activerecord/lib/active_record/ associations.rb:1397:in`each\_with\_index’  
from /tmp/Main/vendor/rails/activerecord/lib/active\_record/  
associations.rb:1397:in `instantiate’  
from (irb):41  
from :0\>\>

Instantiation works if I use an assoc\_graph without multiple :d\_foos  
associations:  
assoc\_graph = [{:b\_foos=\>[{:c\_foo=\>[:d\_foos]}, {:e\_foo=\>[:d\_foos]}]}]  
vs  
assoc\_graph = [{:b\_foos=\>[:c\_foo, {:e\_foo=\>[:d\_foos]}]}]

---

<div class="post-metadata">

**Author:** ![tom\_302](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/e480ec/32.png) [@tom\_302](https://www.ruby-forum.com/u/tom_302)\
**Post date:** [February 13, 2008, 2:43am UTC](https://www.ruby-forum.com/t/joindependency-instantiate-non-unique-association-names/128861/2 "2008-02-13T02:43:24Z")

</div>

Ok, this has nothing to do with the duplicate :d\_foos association.

The problem has to do with the fact that no c/d/e\_foo records exist.

associations.rb:  
construct\_association() returns nil when  
row[join.aliased\_primary\_key].nil, meaning there is no data from the  
left outer join.

I think construct() should call joins.shift N times when  
construct\_association() returns nil, where N is the number of  
descendent associations.

def construct(parent, associations, joins, row)  
…  
when Hash  
associations.keys.sort{|a,b|a.to\_s\<=\>b.to\_s}.each do |name|  
association = construct\_association(parent, joins.shift, row)  
construct(association, associations[name], joins, row) if  
association #else we might need to shift joins?  
end  
else  
…

---

<div class="post-metadata">

**Author:** ![tom\_302](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/e480ec/32.png) [@tom\_302](https://www.ruby-forum.com/u/tom_302)\
**Post date:** [February 14, 2008, 2:45pm UTC](https://www.ruby-forum.com/t/joindependency-instantiate-non-unique-association-names/128861/3 "2008-02-14T14:45:23Z")

</div>

tom\_302 wrote:

> construct\_association() returns nil, where N is the number of  
> else  
> …

May I ask why you are needing to use find\_by\_sql and custom  
association construction? Is it because the query cannot  
be expressed as a normal AR find, or is it because you want to  
narrow the attributes selected?

If the former, can you do the eager loading by properly aliasing  
the fields? e.g. [http://mrj.bpa.nu/eager\_custom\_sql\_rails\_1.2.rb](http://mrj.bpa.nu/eager_custom_sql_rails_1.2.rb)

If the latter, you may want to check out my plugin that allows  
field/attribute selection for eager-loaded associations:

[http://dev.rubyonrails.org/attachment/ticket/7147/init.5.rb](http://dev.rubyonrails.org/attachment/ticket/7147/init.5.rb)

The construct method in this plugin does what you suggest above,  
following the recursion even for absent associations.

–  
We develop, watch us RoR, in numbers too big to ignore.
