This post originated from an RSS feed registered with Ruby Buzz
by Obie Fernandez.
Original Post: Appending to an association more efficiently
Feed Title: Obie On Rails (Has It Been 9 Years Already?)
Feed URL: http://jroller.com/obie/feed/entries/rss
Feed Description: Obie Fernandez talks about life as a technologist, mostly as ramblings about software development and consulting. Nowadays it's pretty much all about Ruby and Ruby on Rails.
Rails changeset 5769 makes a pretty big change to the behavior of the << method on association collections. Pushing a record on an association collection doesn't unnecessarily load all the associated records anymore, which at least some people consider a major performance concern. I'm not sure yet if this change will be included in Rails 1.2
If you're not running EdgeRails, what can you do to establish a relationship using existing objects, without taking the aforementioned performance hit?
The answer is that you have to write your own little custom method for the association, basically a much simpler version of the built-in << method. Luckily it's very simple to do that, by using the ability to define extension methods in a block argument to the association.
First, open up association_collection.rb and take a look at the current implementation of the << method, which looks something like this (prior to revision 5769):
def <<(*records)
result = true
load_target
@owner.transaction do
flatten_deeper(records).each do |record|
raise_on_type_mismatch(record)
callback(:before_add, record)
result &&= insert_record(record) unless @owner.new_record?
@target << record
callback(:after_add, record)
end
end
result && self
end
Using the existing implementation as a starting point, you can pick and choose the lines that you want to include in your method. Most importantly, leave out the call to load_target!
In my example implementation, I chose to execute inside of a transaction, insert the join-table record, append the new record to @target (the association collection), and return self. Here is the code (and keep in mind that I left of the other options to habtm for the sake of clarity):
has_and_belongs_to_many :related do
def associate(record)
@owner.transaction do
insert_record(record) unless @owner.new_record?
@target << record
end
end
self
end
Disclaimers: The example is not taken from production code and your mileage may vary. This won't work for has_many :through associations. Also, it's not really necessary to worry about this unless you have large collections of related objects.