Rails: Inner joins by association names
We can enjoy eager loading with association names.
1 2 Member.find(:all, :include=>[:group, :profile])
But :joins option forces us to write raw SQL like this.
1 2 Member.count(:joins=>"INNER JOIN groups ON groups.id = members.group_id ...")
Why should we do it by hand although those AR classes know their reflections? It’s too boring and painful, and far from DRY.
expand join query
So, why don’t you put this code to ‘vendor/plugins/expandjoinquery/init.rb’?
1 2 class ActiveRecord::Base 3 class << self 4 private 5 def add_joins!(sql, options, scope = :auto) 6 scope = scope(:find) if :auto == scope 7 join = (scope && scope[:joins]) || options[:joins] 8 sql << " #{expand_join_query(join)} " if join 9 end 10 11 def expand_join_query(*joins) 12 joins.flatten.map{|join| 13 case join 14 when Symbol 15 ref = reflections[join] or 16 raise ActiveRecord::ActiveRecordError, "Could not find the source association :#{join} in model #{self}" 17 case ref.macro 18 when :belongs_to 19 "INNER JOIN %s ON %s.%s = %s.%s" % [ref.table_name, ref.table_name, primary_key, table_name, ref.primary_key_name] 20 else 21 "INNER JOIN %s ON %s.%s = %s.%s" % [ref.table_name, ref.table_name, ref.primary_key_name, table_name, primary_key] 22 end 23 else 24 join.to_s 25 end 26 }.join(" ") 27 end 28 end 29 end
Now, we can enjoy :joins option as same as :include one!
1 2 # simply inner join 3 Member.find(:first, :joins=>:group) 4 => SELECT * FROM members INNER JOIN groups ON groups.id = members.group_id LIMIT 1 5 6 # two inner joins 7 Member.count(:joins=>[:group, :profile]) 8 => SELECT count(*) AS count_all FROM members 9 INNER JOIN groups ON groups.id = members.group_id 10 INNER JOIN profiles ON profiles.member_id = members.id 11 12 # symbol and raw SQL 13 Member.find(:all, :joins=>[:group, "INNER JOIN addresses USING(address_id)"]) 14 => SELECT * FROM members 15 INNER JOIN groups ON groups.id = members.group_id 16 INNER JOIN addresses USING(address_id)
restrictions
This is just an idea and not deeply considered yet. So, there are following restrictions.
* cascading is not supported
* not work with :include option (i think)
I hope someone will polish this up :)