An Introduction to Association Proxy


Feb 10, 2008
@post.comments << Comment.new(:title => 'something')
Comment.find(:all,  :conditions => ['post_id = ? AND title like ?', @post.id, 'something'])
Comment.count(:all, :conditions => ['post_id = ?', @post.id])

There is an easier way to do all of that. So, if you're still manipulating ActiveRecord associations by hand, stop; there is a better way.

AssociationProxy

When you request a collection of associated models, ActiveRecord extends that array with several helpful methods. You can treat that array like a model class, except all of the methods are automatically scoped to the association's parent. It responds to methods like find, create, new, count, etc, and, it's called AssociationProxy.

So, what if we wanted to create a comment, scoped to our post model?

@post.comments.create(params[:comment])

Making use of AssociationProxy, the code reads better, and requires fewer keystrokes. We can also use AssociationProxy to find elements in our collection.

@post.comments.find(:all, :conditions => {:title => 'title we are looking for'})

We can even use dynamic, attribute-based finders through AssociationProxy.

@post.comments.find_by_title 'title we are looking for'

It Gets Better

Since assoc proxy responds to many of the same methods as a model class, the two can be interchanged for many operations. In resource_controller, for example, nested controllers depend heavily on association proxies. Let me show you what I mean.

If we had, for example, a typical show action.

def show
  @comment = Comment.find(params[:id])
end

With very little effort, we can allow comments to be shown nested, or unnested.

def show
  @comment = model.find(params[:id])
end

private
  def model
    params[:post_id] ? Post.find(params[:post_id]).comments : Comment
  end

Since we know that AssociationProxy responds to other model class methods, we can base our create method on this technique, too.

def show
  @comment = model.find(params[:id])
end

def create
  @comment = model.new(params[:comment])
  if @comment.save
    redirect_to @comment
  else
    render :action => 'new'
  end
end

private
  def model
    params[:post_id] ? Post.find(params[:post_id]).comments : Comment
  end

In fact, this is nearly exactly how resource_controller is built.

So, association proxy is useful in simple and complex situations alike. It can save you a lot of code, and increase readability. It's a win-win really.