<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Kellogg Associates: Tag ActiveRecord</title>
    <link>http://kellogg-assoc.com/articles/tag/activerecord?tag=activerecord</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description></description>
    <item>
      <title>Eager Finder SQL</title>
      <description>&lt;p&gt;&lt;em&gt;EagerFinderSql&lt;/em&gt; allows custom &lt;span class="caps"&gt;SQL&lt;/span&gt; to be specified when doing eager loading of associations through the &lt;tt&gt;:include&lt;/tt&gt; option
to &lt;tt&gt;find&lt;/tt&gt;. This allows for purpose-constructed queries to be used and still result in a fully linked
object model.&lt;/p&gt;


	&lt;h3&gt;Background&lt;/h3&gt;


	&lt;p&gt;&lt;strong&gt;ActiveRecord&lt;/strong&gt; constructs &lt;span class="caps"&gt;SQL&lt;/span&gt; to satisfy the requirements of a &lt;tt&gt;find&lt;/tt&gt; request. &lt;em&gt;Associations&lt;/em&gt; allow for customized
&lt;span class="caps"&gt;SQL&lt;/span&gt; to be specified, using the &lt;tt&gt;:finder_sql&lt;/tt&gt; option, but this has not been available when performing eager loading
using the &lt;tt&gt;:include&lt;/tt&gt; option. The result is that a standardized query is constructed to bring in the associated
tables using &lt;tt&gt;&lt;span class="caps"&gt;LEFT OUTER JOIN&lt;/span&gt;&lt;/tt&gt;. For some queries, this can be result in expensive queries and potentially very
large result sets.&lt;/p&gt;


	&lt;h3&gt;Custom &lt;span class="caps"&gt;SQL&lt;/span&gt;&lt;/h3&gt;


	&lt;p&gt;&lt;em&gt;EagerFinderSql&lt;/em&gt; addresses this problem by allowing &lt;tt&gt;:finder_sql&lt;/tt&gt; to be added
to &lt;tt&gt;find&lt;/tt&gt; options when the &lt;tt&gt;:include&lt;/tt&gt; option is also specified.&lt;/p&gt;&lt;p&gt;Columns in the result set are mapped to attributes in the resuting object model through the
&lt;tt&gt;:column_mapping&lt;/tt&gt; option.&lt;/p&gt;


	&lt;p&gt;The &lt;tt&gt;:column_mapping&lt;/tt&gt; option specifies a hash containing the following entries:&lt;/p&gt;


	&lt;ul&gt;
	&lt;li&gt;&lt;tt&gt;primary_key&lt;/tt&gt; &amp;#8211; indicates the column alias associated with the classes &lt;em&gt;id&lt;/em&gt; attribute.&lt;/li&gt;
		&lt;li&gt;&lt;tt&gt;columns&lt;/tt&gt; &amp;#8211; is a hash of attribute to column alias associations.&lt;/li&gt;
		&lt;li&gt;&lt;tt&gt;associatios&lt;/tt&gt; &amp;#8211; is a hash of association mappings for each directly included model. The &lt;em&gt;key&lt;/em&gt; for each entry is the name of the association, while the value is a hash similar to the hash for the parent class.&lt;/li&gt;
	&lt;/ul&gt;


	&lt;h3&gt;Example&lt;/h3&gt;


Consider the following query relating authors with many books:
&lt;pre&gt;&lt;code&gt;
Author.find(:all, :include =&amp;gt; :books)
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;This would likely produce the following &lt;span class="caps"&gt;SQL&lt;/span&gt;:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;
SELECT
    authors.id AS t0_c0, authors.name AS t0_c1,
    books.id AS t1_c0, books.author_id AS t1_c1, books.title AS t1_c2
  FROM authors
  LEFT OUTER JOIN books ON books.author_id = authors.id
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;The query might be written with books as the driver, rather than authors as follows:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;
 SELECT
    authors.id, authors.name,
    books.id AS book_id, books.name AS book_name
  FROM books
  JOIN authors ON authors.id = books.author_id
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;The Rails query would then be written as follows:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;
Author.find(:all, :include =&amp;gt; books,
           :finder_sql =&amp;gt; " 
                SELECT
                    authors.id, authors.name,
                    books.id AS book_id, books.name AS book_name
                  FROM books
                  JOIN authors ON authors.id = books.author_id".
           :column_mapping =&amp;gt; {
             :primary_key =&amp;gt; 'id',
             :columns =&amp;gt; {
                  'id'  =&amp;gt; 'id',
                  'name =&amp;gt; 'name'
              },
              :associations =&amp;gt; {
                :books =&amp;gt; {
                  :primary_key =&amp;gt; book_id,
                  :columns =&amp;gt; {
                      'id'        =&amp;gt; 'book_id',
                      'author_id' =&amp;gt; 'id',
                      'name'      =&amp;gt; 'book_name
                  }
                }
              }
            })

&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;This is more verbose, but allows for absolute control of the &lt;span class="caps"&gt;SQL&lt;/span&gt; used to return results across multiple model associations.&lt;/p&gt;


	&lt;p&gt;A more complicated example would be the following:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;
QUERY = " 
SELECT
    a.id AS author_id,
    a.name AS author_name,
    p.id AS post_id,
    p.title AS post_title,
    p.body AS post_body,
    p.type AS post_type,
    c.id AS comment_id,
    c.body AS comment_body,
    c.type AS comment_type
  FROM authors a
  LEFT JOIN posts p ON p.author_id = a.id
  LEFT JOIN comments c ON c.post_id = p.id" 

MAPPING = {
  :primary_key =&amp;gt; 'author_id',
  :columns =&amp;gt; {
    'id' =&amp;gt; 'author_id',
    'name' =&amp;gt; 'author_name'
  },                         
  :associations=&amp;gt; {
    :posts =&amp;gt; {
      :primary_key  =&amp;gt; 'post_id',
      :columns =&amp;gt; {
        'id' =&amp;gt; 'post_id',
        'title' =&amp;gt; 'post_title',
        'author_id' =&amp;gt; 'author_id',
        'body' =&amp;gt; 'post_pody',
        'type' =&amp;gt; 'post_type'
      },
      :associations =&amp;gt; {
        :comments =&amp;gt; {
          :primary_key  =&amp;gt; 'comment_id',
          :columns =&amp;gt; {
            'id' =&amp;gt; 'comment_id',
            'post_id' =&amp;gt; 'post_id',
            'author_id' =&amp;gt; 'author_id',
            'body' =&amp;gt; 'comment_body',
            'type' =&amp;gt; 'comment_type'
          }
        }
      }
    }
  }
}
Author.find(:all,
            :include=&amp;gt;{:posts=&amp;gt;:comments},
            :order=&amp;gt;"authors.id",
            :finder_sql =&amp;gt; QUERY,
            :column_mapping =&amp;gt; MAPPING)
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;This example shows the recursive nature of associations. It can also be used to render deeper structures
that may reference the same model multiple times, such as the following:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;
Firm.find(:all,
          :include=&amp;gt;{:account=&amp;gt;{:firm=&amp;gt;:account}},
          :order=&amp;gt;"companies.id")
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;(Here, the query and mapping is left to the reader; or, you can look at the unit test which documents
many more possibilities).&lt;/p&gt;


	&lt;p&gt;Install using&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;
script/plugin install svn://rubyforge.org/var/svn/eagerfindersql
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;The plugin is managed on &lt;a href="http://rubyforge.org/projects/eagerfindersql/"&gt;RubyForge&lt;/a&gt;.
&lt;a href="/rdoc/eager_finder_sql/index.html"&gt;Rdoc&lt;/a&gt; is available.&lt;/p&gt;</description>
      <pubDate>Sun, 05 Nov 2006 19:31:00 -0600</pubDate>
      <guid isPermaLink="false">urn:uuid:b92f8540-e3ba-47f4-b3bf-2bfd95bc4e95</guid>
      <author>gregg@kellogg-assoc.com (Gregg Kellogg)</author>
      <link>http://kellogg-assoc.com/articles/2006/11/05/eager-finder-sql</link>
      <category>Ruby on Rails</category>
      <category>rails</category>
      <category>mixin</category>
      <category>ActiveRecord</category>
      <category>query</category>
      <category>eager loading</category>
      <trackback:ping>http://kellogg-assoc.com/articles/trackback/19</trackback:ping>
    </item>
  </channel>
</rss>
