Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

Matt Scilipoti

« Newer Snippets
Older Snippets »
Showing 1-1 of 1 total  RSS 

Rails: Inner joins by association names

From http://habtm.com/articles/2006/05/10/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 :)
« Newer Snippets
Older Snippets »
Showing 1-1 of 1 total  RSS