find_by_association

One of the greatest benefits of an Object-Relational-Mapping system like ActiveRecord, is the ability to express the associations between objects. We’ve all written code like this before:

class Category < ActiveRecord::Base
  has_many :posts
end
class Post < ActiveRecord::Base
  belongs_to :author, :class_name  => 'User', :foreign_key => :author_id
  belongs_to :category
end
class User < ActiveRecord::Base
  has_many :posts, :foreign_key => :author_id
end

Assume you have two instance variables: @category and @user. If you wanted to find all the Posts for the @user, it would be as simple as referencing @user.posts. If you wanted to find all the Posts for @category, you reference @category.posts. But how do you get an intersection of the two collections?

The find_by_association plugin is designed to make it easy to reference associations in finder conditions hashes and dynamic finders. For example, to get all the Posts for a specific category and author, you can use a dynamic finder, a conditions hash, or a combination:

# Conditions Hash
Post.find(:all, :conditions => {:author => @author, :category => @category})

# Dynamic Finder
Post.find_all_by_category_and_author(@category, @author)

# Combination
Post.find_all_by_category(@category, :conditions => {:author => @author})

Pretty neat trick, but it gets a lot neater. What if you wanted to find all Posts where the author’s first name was ‘Joe’? find_by_association gives us the ability to include references to the associated objects’ attributes:

# Conditions Hash
Post.find(:all, :conditions => {:author => {:first_name => 'Joe'}})

# Dynamic Finder
Post.find_all_by_author_having_first_name('Joe')

Notice in the Dynamic Finder example, the keyword having expresses that we are looking for at the first_name attribute on the referenced author object. Cool!

We can even nest our conditions further — Lets say we wanted to find all the Categories that Users with first_name of ‘Joe’ have written Posts about:

# Conditions Hash
Category.find(:all, :conditions => { :posts => { :author => {:first_name => 'Joe'}}})

# Dynamic Finder
Category.find_all_by_posts_having_author_having_first_name('Joe')

Notice that we referenced a has_many association, posts, in the same way we referenced the belongs_to association author. find_by_association can handle all the standard association macros: has_many, has_many :through, has_and_belongs_to_many, belongs_to, and has_one.

The only place where you run into some trickiness, is trying to nest conditions that reference a belongs_to association that with a :polymorphic => true option. You can do so, but you have to provide a foreign_type value. For example:

class Authorship < ActiveRecord::Base
  belongs_to :author, :class_name => 'User', :foreign_key => :author_id
  belongs_to :work, :polymorphic => true
end
class Article < ActiveRecord::Base
  has_many :authorships, :as => :work
  has_many :authors, :through => :authorships
end
class Book < ActiveRecord::Base
  has_many :authorships, :as => :work
  has_many :authors, :through => :authorships
end

# Notice I must provide the :work_type key to the conditions
# to inform find_by_association what model to include in the
# subquery to find a title attribute.
Authorship.find :all, :conditions => {
  :work => {:title => 'Writing Cool Rails Plugins'},
  :work_type => 'Book'
}

To try out find_by_association yourself, check it out from the public repository at:

http://usergenic.com/svn/public/ruby/rails/plugins/find_by_association/trunk

You can have a look at more usage examples in the test directory, and hopefully this page will grow with some more complete examples. Please drop me a line at brendan@usergenic.com or leave a comment on this page.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*