<?xml version="1.0" encoding="UTF-8"?>
<wiki>
  <body>&lt;p&gt;This module extends the behavior of ActiveRecord::Base to support using association names&lt;br /&gt;
directly in conditions hashes for finders.  Because this patches sanitize_sql_hash&lt;em&gt;for&lt;/em&gt;conditions]&lt;br /&gt;
it &amp;#8220;extends elegantly to private the means of combination&amp;#8221; (to quote Hal Abelson) meaning, among&lt;br /&gt;
other benefits, that conditions hashes may therefore be nested as deep as your database engine&amp;#8217;s&lt;br /&gt;
support for nested subqueries allows.&lt;/p&gt;
&lt;p&gt;One of the greatest benefits of an Object-Relational-Mapping system like ActiveRecord, &lt;br /&gt;
is the ability to express the associations between objects. We&#8217;ve all written code &lt;br /&gt;
like this before:&lt;/p&gt;
class Category &amp;lt; ActiveRecord::Base
has_many :posts
end
class Post &amp;lt; ActiveRecord::Base
belongs_to :author, :class_name  =&amp;gt; &amp;#8216;User&amp;#8217;, :foreign_key =&amp;gt; :author_id
belongs_to :category
end
class User &amp;lt; ActiveRecord::Base
has_many :posts, :foreign_key =&amp;gt; :author_id
end
&lt;p&gt;Assume you have two instance variables: @category and @user. If you wanted to find all &lt;br /&gt;
the Posts for the @user, it would be as simple as referencing @user.posts. If you wanted&lt;br /&gt;
to find all the Posts for @category, you reference @category.posts. But how do you get&lt;br /&gt;
an intersection of the two collections?&lt;/p&gt;
&lt;p&gt;The find_by_association plugin is designed to make it easy to reference associations in&lt;br /&gt;
finder conditions hashes and dynamic finders. For example, to get all the Posts for a &lt;br /&gt;
specific category and author, you can use a dynamic finder, a conditions hash, or a &lt;br /&gt;
combination:&lt;/p&gt;
Post.find(:all, :conditions =&amp;gt; {:author =&amp;gt; @author, :category =&amp;gt; @category}) # &amp;lt;= hash
Post.find_all_by_category_and_author(@category, @author) # &amp;lt;= dynamic finder
Post.find_all_by_category(@category, :conditions =&amp;gt; {:author =&amp;gt; @author}) # &amp;lt;= combo
&lt;p&gt;Pretty neat trick, but it gets a lot neater. What if you wanted to find all Posts where &lt;br /&gt;
the author&#8217;s first name was &#8216;Joe&#8217;? find_by_association gives us the ability to include&lt;br /&gt;
references to the associated objects&#8217; attributes:&lt;/p&gt;
Post.find(:all, :conditions =&amp;gt; {:author =&amp;gt; {:first_name =&amp;gt; &amp;#8217;Joe&amp;#8217;}}) # &amp;lt;= hash
Post.find_all_by_author_having_first_name(&amp;#8216;Joe&amp;#8217;) # &amp;lt;= dynamic finder
&lt;p&gt;Notice in the Dynamic Finder example, the keyword having expresses that we are looking&lt;br /&gt;
for at the first_name attribute on the referenced author object. Cool!&lt;/p&gt;
&lt;p&gt;We can even nest our conditions further &#8212; Lets say we wanted to find all the Categories&lt;br /&gt;
that Users with first_name of &#8216;Joe&#8217; have written Posts about:&lt;/p&gt;
Category.find(:all, :conditions =&amp;gt; { :posts =&amp;gt; { :author =&amp;gt; {:first_name =&amp;gt; &amp;#8217;Joe&amp;#8217;}}}) # &amp;lt;= hash
Category.find_all_by_posts_having_author_having_first_name(&amp;#8216;Joe&amp;#8217;) # &amp;lt;= dynamic finder
&lt;p&gt;Notice that we referenced a has_many association, posts, in the same way we referenced the&lt;br /&gt;
belongs_to association author. find_by_association can handle all the standard association&lt;br /&gt;
macros: has_many, has_many :through, has_and_belongs_to_many, belongs_to, and has_one.&lt;/p&gt;
&lt;p&gt;The only place where you run into some trickiness, is trying to nest conditions that reference&lt;br /&gt;
a belongs_to association that with a :polymorphic =&amp;gt; true option. You can do so, but you have&lt;br /&gt;
to provide a foreign_type value. For example:&lt;/p&gt;
class Authorship &amp;lt; ActiveRecord::Base
belongs_to :author, :class_name =&amp;gt; &amp;#8216;User&amp;#8217;, :foreign_key =&amp;gt; :author_id
belongs_to :work, :polymorphic =&amp;gt; true
end
class Article &amp;lt; ActiveRecord::Base
has_many :authorships, :as =&amp;gt; :work
has_many :authors, :through =&amp;gt; :authorships
end
class Book &amp;lt; ActiveRecord::Base
has_many :authorships, :as =&amp;gt; :work
has_many :authors, :through =&amp;gt; :authorships
end
&lt;p&gt;Notice I must provide the :work_type key to the conditions&lt;br /&gt;
to inform find_by_association what model to include in the&lt;br /&gt;
subquery to find a title attribute.&lt;/p&gt;
Authorship.find :all, :conditions =&amp;gt; {
:work =&amp;gt; {:title =&amp;gt; &amp;#8216;Writing Cool Rails Plugins&amp;#8217;},
:work_type =&amp;gt; &amp;#8216;Book&amp;#8217;
}
&lt;p&gt;Below is a schema, with some model definitions, and some additional example&lt;br /&gt;
uses, which I will leave here as documentation, until such time as I am able&lt;br /&gt;
to provide &lt;strong&gt;actual&lt;/strong&gt; documentation.  Please forgive me.&lt;/p&gt;
&lt;p&gt;Migration/Schema.rb table definitions:&lt;/p&gt;
create_table :customers do |t|
t.string :name
end
create_table :products do |t|
t.string :name
end
create_table :orders do |t|
t.references :customer
end
create_table :order_items do |t|
t.references :product, :order
t.integer :quantity
end
&lt;p&gt;Define the Model Classes:&lt;/p&gt;
class Customer &amp;lt; ActiveRecord::Base
has_many :orders
end
class Order &amp;lt; ActiveRecord::Base
belongs_to :customer
has_many :items, :class_name =&amp;gt; &amp;#8216;OrderItem&amp;#8217;
has_many :products, :through =&amp;gt; :items
end
class OrderItem &amp;lt; ActiveRecord::Base
belongs_to :order
belongs_to :product
end
class Product &amp;lt; ActiveRecord::Base
has_many :order_items
has_many :orders, :through =&amp;gt; :order_items
end
&lt;p&gt;Create a dataset:&lt;/p&gt;
banana = Product.create(:name =&amp;gt; &amp;#8220;Electric Banana&amp;#8221;)
flower = Product.create(:name =&amp;gt; &amp;#8220;Squirting Flower&amp;#8221;)
wocka = Product.create(:name =&amp;gt; &amp;#8220;Wocka-Wocka-Wocka&amp;#8221;)
fozzy = Customer.create(:name =&amp;gt; &amp;#8220;Fozzy&amp;#8221;)
order1 = fozzy.orders.create
order1.items.create :product =&amp;gt; banana, :quantity =&amp;gt; 1
order1.items.create :product =&amp;gt; wocka, :quantity =&amp;gt; 3
order2 = fozzy.orders.create
order2.items.create :product =&amp;gt; wocka, :quantity =&amp;gt; 5
order2.items.create :product =&amp;gt; flower, :quantity =&amp;gt; 1
&lt;p&gt;Lets &amp;#8220;Find-by-association&amp;#8221;!&lt;/p&gt;
&lt;p&gt;Find all of the Customers who ordered a Product named &amp;#8220;Squirting Flower&amp;#8221;&amp;#8230;&lt;br /&gt;
  Customer.find(:all, :conditions =&amp;gt; {:orders =&amp;gt; {:products =&amp;gt; {:name =&amp;gt; &amp;#8220;Squirting Flower&amp;#8221;}}}) # &amp;lt;= hash&lt;br /&gt;
  Customer.find_all_by_orders_having_products_having_name(&amp;#8220;Squirting Flower&amp;#8221;) # &amp;lt;= dynamic finder&lt;br /&gt;
  =&amp;gt; [#&amp;lt;Customer id: 1, name: &amp;quot;Fozzy&amp;quot;&amp;gt;]&lt;/p&gt;
&lt;p&gt;Whoa&amp;#8212; did you see what happened there?  We got nested a reference to the :name column in a&lt;br /&gt;
has_many :through inside a has_many association on Customer.  And we delegated all the work&lt;br /&gt;
to the database in a single statement!  Yee-haw.  What&amp;#8217;s the &lt;span class=&quot;caps&quot;&gt;SQL&lt;/span&gt; look like? (indented for&lt;br /&gt;
readability.)&lt;/p&gt;
&lt;span class=&quot;caps&quot;&gt;SELECT&lt;/span&gt; * &lt;span class=&quot;caps&quot;&gt;FROM&lt;/span&gt; customers &lt;span class=&quot;caps&quot;&gt;WHERE&lt;/span&gt; ( customers.&amp;#8220;id&amp;#8221; IN (
&lt;span class=&quot;caps&quot;&gt;SELECT&lt;/span&gt; customer_id &lt;span class=&quot;caps&quot;&gt;FROM&lt;/span&gt; orders &lt;span class=&quot;caps&quot;&gt;WHERE&lt;/span&gt; ( orders.&amp;#8220;id&amp;#8221; IN (
&lt;span class=&quot;caps&quot;&gt;SELECT&lt;/span&gt; order_id &lt;span class=&quot;caps&quot;&gt;FROM&lt;/span&gt; order_items &lt;span class=&quot;caps&quot;&gt;WHERE&lt;/span&gt; ( order_items.&amp;#8220;product_id&amp;#8221; IN (
&lt;span class=&quot;caps&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;caps&quot;&gt;FROM&lt;/span&gt; products &lt;span class=&quot;caps&quot;&gt;WHERE&lt;/span&gt; (products.&amp;#8220;name&amp;#8221; = &amp;#8216;Squirting Flower&amp;#8217;)))))))
&lt;p&gt;You think that&amp;#8217;s nice, you should see what happens when you combine FindByAssociation with&lt;br /&gt;
the rest of Entrails&amp;#8217; ActiveRecord extensions, like&amp;#8230; [[BetterConditions]]&lt;/p&gt;
&lt;p&gt;The subqueries generated need to be optimized to reference the id column outside the subquery so that the temporary tables that many db engines use to produce the subset references are significantly reduced.  &lt;strong&gt;coming soon&lt;/strong&gt; &amp;#8212;Brendan&lt;/p&gt;</body>
  <created-at type="datetime">2008-03-13T10:31:00-07:00</created-at>
  <id type="integer">4318</id>
  <permalink>findbyassociation</permalink>
  <repository-id type="integer">3563</repository-id>
  <title>FindByAssociation</title>
  <updated-at type="datetime">2008-03-15T15:45:41-07:00</updated-at>
  <user-id type="integer">578</user-id>
</wiki>
